Kea 2.7.5
mysql_connection.h
Go to the documentation of this file.
1// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#ifndef MYSQL_CONNECTION_H
8#define MYSQL_CONNECTION_H
9
10#include <asiolink/io_service.h>
13#include <database/db_log.h>
15#include <mysql/mysql_binding.h>
17#include <boost/scoped_ptr.hpp>
18#include <mysql.h>
19#include <mysqld_error.h>
20#include <errmsg.h>
21#include <functional>
22#include <vector>
23#include <stdint.h>
24
25namespace isc {
26namespace db {
27
28
42
44public:
45
56 MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement)
57 {}
58
63 (void) mysql_stmt_free_result(statement_);
64 }
65
66private:
67 MYSQL_STMT* statement_;
68};
69
75 uint32_t index;
76 const char* text;
77};
78
88template <typename Fun, typename... Args>
89int retryOnDeadlock(Fun& fun, Args... args) {
90 int status;
91 for (unsigned count = 0; count < 5; ++count) {
92 status = fun(args...);
93 if (status != ER_LOCK_DEADLOCK) {
94 break;
95 }
96 }
97 return (status);
98}
99
106inline int MysqlExecuteStatement(MYSQL_STMT* stmt) {
107 return (retryOnDeadlock(mysql_stmt_execute, stmt));
108}
109
117inline int MysqlQuery(MYSQL* mysql, const char* stmt) {
118 return (retryOnDeadlock(mysql_query, mysql, stmt));
119}
120
132class MySqlHolder : public boost::noncopyable {
133public:
134
140 MySqlHolder() : mysql_(mysql_init(NULL)) {
141 if (mysql_ == NULL) {
142 isc_throw(db::DbOpenError, "unable to initialize MySQL");
143 }
144 }
145
150 if (mysql_ != NULL) {
151 mysql_close(mysql_);
152 }
153 }
154
159 operator MYSQL*() const {
160 return (mysql_);
161 }
162
163private:
165 static int atexit_;
166
168 MYSQL* mysql_;
169};
170
172class MySqlConnection;
173
194class MySqlTransaction : public boost::noncopyable {
195public:
196
206
211
213 void commit();
214
215private:
216
218 MySqlConnection& conn_;
219
224 bool committed_;
225};
226
227
236public:
237
239 typedef std::function<void(MySqlBindingCollection&)> ConsumeResultFun;
240
248 MySqlConnection(const ParameterMap& parameters,
250 DbCallback callback = DbCallback())
251 : DatabaseConnection(parameters, callback),
252 io_service_accessor_(io_accessor), io_service_(),
253 transaction_ref_count_(0), tls_(false) {
254 }
255
257 virtual ~MySqlConnection();
258
264 static std::vector<std::string>
265 toKeaAdminParameters(ParameterMap const& params);
266
280 static std::pair<uint32_t, uint32_t>
281 getVersion(const ParameterMap& parameters,
283 const DbCallback& cb = DbCallback(),
284 const std::string& timer_name = std::string());
285
298 static void
299 ensureSchemaVersion(const ParameterMap& parameters,
300 const DbCallback& cb = DbCallback(),
301 const std::string& timer_name = std::string());
302
309 static void
310 initializeSchema(const ParameterMap& parameters);
311
325 void prepareStatement(uint32_t index, const char* text);
326
341 void prepareStatements(const TaggedStatement* start_statement,
342 const TaggedStatement* end_statement);
343
353 template<typename StatementIndex>
354 MYSQL_STMT* getStatement(StatementIndex index) const {
355 if (statements_[index]->mysql == 0) {
357 "MySQL pointer for the prepared statement is NULL as a result of connectivity loss");
358 }
359 return (statements_[index]);
360 }
361
369 void openDatabase();
370
378
384 static
385 void convertToDatabaseTime(const time_t input_time, MYSQL_TIME& output_time);
386
406 static
407 void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
408 MYSQL_TIME& expire);
409
427 static
428 void convertFromDatabaseTime(const MYSQL_TIME& expire,
429 uint32_t valid_lifetime, time_t& cltt);
431
445 void startTransaction();
446
450 bool isTransactionStarted() const;
451
479 template<typename StatementIndex>
480 void selectQuery(const StatementIndex& index,
481 const MySqlBindingCollection& in_bindings,
482 MySqlBindingCollection& out_bindings,
483 ConsumeResultFun process_result) {
485 // Extract native input bindings.
486 std::vector<MYSQL_BIND> in_bind_vec;
487 for (const MySqlBindingPtr& in_binding : in_bindings) {
488 in_bind_vec.push_back(in_binding->getMySqlBinding());
489 }
490
491 int status = 0;
492 if (!in_bind_vec.empty()) {
493 // Bind parameters to the prepared statement.
494 status = mysql_stmt_bind_param(getStatement(index),
495 in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
496 checkError(status, index, "unable to bind parameters for select");
497 }
498
499 // Bind variables that will receive results as well.
500 std::vector<MYSQL_BIND> out_bind_vec;
501 for (const MySqlBindingPtr& out_binding : out_bindings) {
502 out_bind_vec.push_back(out_binding->getMySqlBinding());
503 }
504 if (!out_bind_vec.empty()) {
505 status = mysql_stmt_bind_result(getStatement(index), &out_bind_vec[0]);
506 checkError(status, index, "unable to bind result parameters for select");
507 }
508
509 // Execute query.
510 status = MysqlExecuteStatement(getStatement(index));
511 checkError(status, index, "unable to execute");
512
513 status = mysql_stmt_store_result(getStatement(index));
514 checkError(status, index, "unable to set up for storing all results");
515
516 // Fetch results.
517 MySqlFreeResult fetch_release(getStatement(index));
518 while ((status = mysql_stmt_fetch(getStatement(index))) ==
520 try {
521 // For each returned row call user function which should
522 // consume the row and copy the data to a safe place.
523 process_result(out_bindings);
524
525 } catch (const std::exception& ex) {
526 // Rethrow the exception with a bit more data.
527 isc_throw(BadValue, ex.what() << ". Statement is <" <<
528 text_statements_[index] << ">");
529 }
530 }
531
532 // How did the fetch end?
533 // If mysql_stmt_fetch return value is equal to 1 an error occurred.
534 if (status == MLM_MYSQL_FETCH_FAILURE) {
535 // Error - unable to fetch results
536 checkError(status, index, "unable to fetch results");
537
538 } else if (status == MYSQL_DATA_TRUNCATED) {
539 // Data truncated - throw an exception indicating what was at fault
541 << " returned truncated data");
542 }
543 }
544
559 template<typename StatementIndex>
560 void insertQuery(const StatementIndex& index,
561 const MySqlBindingCollection& in_bindings) {
563 std::vector<MYSQL_BIND> in_bind_vec;
564 for (const MySqlBindingPtr& in_binding : in_bindings) {
565 in_bind_vec.push_back(in_binding->getMySqlBinding());
566 }
567
568 // Bind the parameters to the statement
569 int status = mysql_stmt_bind_param(getStatement(index),
570 in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
571 checkError(status, index, "unable to bind parameters");
572
573 // Execute the statement
574 status = MysqlExecuteStatement(getStatement(index));
575
576 if (status != 0) {
577 // Failure: check for the special case of duplicate entry.
578 if (mysql_errno(mysql_) == ER_DUP_ENTRY) {
579 isc_throw(DuplicateEntry, "Database duplicate entry error");
580 }
581 // Failure: check for the special case of WHERE returning NULL.
582 if (mysql_errno(mysql_) == ER_BAD_NULL_ERROR) {
583 isc_throw(NullKeyError, "Database bad NULL error");
584 }
585 checkError(status, index, "unable to execute");
586 }
587 }
588
603 template<typename StatementIndex>
604 uint64_t updateDeleteQuery(const StatementIndex& index,
605 const MySqlBindingCollection& in_bindings) {
607 std::vector<MYSQL_BIND> in_bind_vec;
608 for (const MySqlBindingPtr& in_binding : in_bindings) {
609 in_bind_vec.push_back(in_binding->getMySqlBinding());
610 }
611
612 // Bind the parameters to the statement
613 int status = mysql_stmt_bind_param(getStatement(index),
614 in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
615 checkError(status, index, "unable to bind parameters");
616
617 // Execute the statement
618 status = MysqlExecuteStatement(getStatement(index));
619
620 if (status != 0) {
621 // Failure: check for the special case of duplicate entry.
622 if ((mysql_errno(mysql_) == ER_DUP_ENTRY)
623#ifdef ER_FOREIGN_DUPLICATE_KEY
624 || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY)
625#endif
626#ifdef ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO
627 || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO)
628#endif
629#ifdef ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO
630 || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO)
631#endif
632 ) {
633 isc_throw(DuplicateEntry, "Database duplicate entry error");
634 }
635 checkError(status, index, "unable to execute");
636 }
637
638 // Let's return how many rows were affected.
639 return (static_cast<uint64_t>(mysql_stmt_affected_rows(getStatement(index))));
640 }
641
652 void commit();
653
664 void rollback();
665
694 template<typename StatementIndex>
695 void checkError(const int status, const StatementIndex& index,
696 const char* what) {
697 if (status != 0) {
698 switch(mysql_errno(mysql_)) {
699 // These are the ones we consider fatal. Remember this method is
700 // used to check errors of API calls made subsequent to successfully
701 // connecting. Errors occurring while attempting to connect are
702 // checked in the connection code. An alternative would be to call
703 // mysql_ping() - assuming autoreconnect is off. If that fails
704 // then we know connection is toast.
705 case CR_SERVER_GONE_ERROR:
706 case CR_SERVER_LOST:
707 case CR_OUT_OF_MEMORY:
708 case CR_CONNECTION_ERROR: {
710 .arg(what)
711 .arg(text_statements_[static_cast<int>(index)])
712 .arg(mysql_error(mysql_))
713 .arg(mysql_errno(mysql_));
714
715 // Mark this connection as no longer usable.
716 markUnusable();
717
718 // Start the connection recovery.
720
721 // We still need to throw so caller can error out of the current
722 // processing.
724 "fatal database error or connectivity lost");
725 }
726 default:
727 // Connection is ok, so it must be an SQL error
728 isc_throw(db::DbOperationError, what << " for <"
729 << text_statements_[static_cast<int>(index)]
730 << ">, reason: "
731 << mysql_error(mysql_) << " (error code "
732 << mysql_errno(mysql_) << ")");
733 }
734 }
735 }
736
743 if (callback_) {
745 io_service_ = (*io_service_accessor_)();
746 io_service_accessor_.reset();
747 }
748
749 if (io_service_) {
750 io_service_->post(std::bind(callback_, reconnectCtl()));
751 }
752 }
753 }
754
758 bool getTls() const {
759 return (tls_);
760 }
761
765 std::string getTlsCipher() {
766 const char* cipher = mysql_get_ssl_cipher(mysql_);
767 return (cipher ? std::string(cipher) : "");
768 }
769
770private:
771
786 template<typename T>
787 void setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value);
788
793 std::vector<MYSQL_STMT*> statements_;
794
795public:
796
801 std::vector<std::string> text_statements_;
802
808
817
820
828
830 bool tls_;
831
834 static std::string KEA_ADMIN_;
835};
836
837} // end of isc::db namespace
838} // end of isc namespace
839
840#endif // MYSQL_CONNECTION_H
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Data is truncated.
Common database connection class.
util::ReconnectCtlPtr reconnectCtl()
The reconnect settings.
void markUnusable()
Sets the unusable flag to true.
void checkUnusable()
Throws an exception if the connection is not usable.
std::map< std::string, std::string > ParameterMap
Database configuration parameter map.
DbCallback callback_
The callback used to recover the connection.
Exception thrown when a specific connection has been rendered unusable either through loss of connect...
Exception thrown on failure to open database.
Exception thrown on failure to execute a database function.
Database duplicate entry error.
Common MySQL Connector Pool.
isc::asiolink::IOServicePtr io_service_
IOService object, used for all ASIO operations.
static std::string KEA_ADMIN_
Holds location to kea-admin.
MySqlHolder mysql_
MySQL connection handle.
void prepareStatement(uint32_t index, const char *text)
Prepare Single Statement.
bool isTransactionStarted() const
Checks if there is a transaction in progress.
std::vector< std::string > text_statements_
Raw text of statements.
void insertQuery(const StatementIndex &index, const MySqlBindingCollection &in_bindings)
Executes INSERT prepared statement.
bool tls_
TLS flag (true when TLS was required, false otherwise).
static void convertToDatabaseTime(const time_t input_time, MYSQL_TIME &output_time)
Convert time_t value to database time.
static std::pair< uint32_t, uint32_t > getVersion(const ParameterMap &parameters, const IOServiceAccessorPtr &ac=IOServiceAccessorPtr(), const DbCallback &cb=DbCallback(), const std::string &timer_name=std::string())
Get the schema version.
IOServiceAccessorPtr io_service_accessor_
Accessor function which returns the IOService that can be used to recover the connection.
static void convertFromDatabaseTime(const MYSQL_TIME &expire, uint32_t valid_lifetime, time_t &cltt)
Convert Database Time to Lease Times.
void commit()
Commits current transaction.
MySqlConnection(const ParameterMap &parameters, IOServiceAccessorPtr io_accessor=IOServiceAccessorPtr(), DbCallback callback=DbCallback())
Constructor.
void startRecoverDbConnection()
The recover connection.
static void initializeSchema(const ParameterMap &parameters)
Initialize schema.
uint64_t updateDeleteQuery(const StatementIndex &index, const MySqlBindingCollection &in_bindings)
Executes UPDATE or DELETE prepared statement and returns the number of affected rows.
static std::vector< std::string > toKeaAdminParameters(ParameterMap const &params)
Convert MySQL library parameters to kea-admin parameters.
void openDatabase()
Open Database.
std::string getTlsCipher()
Get the TLS cipher.
void prepareStatements(const TaggedStatement *start_statement, const TaggedStatement *end_statement)
Prepare statements.
std::function< void(MySqlBindingCollection &) ConsumeResultFun)
Function invoked to process fetched row.
int transaction_ref_count_
Reference counter for transactions.
void startTransaction()
Starts new transaction.
virtual ~MySqlConnection()
Destructor.
void checkError(const int status, const StatementIndex &index, const char *what)
Check Error and Throw Exception.
bool getTls() const
Get the TLS flag.
MYSQL_STMT * getStatement(StatementIndex index) const
Returns a prepared statement by an index.
void selectQuery(const StatementIndex &index, const MySqlBindingCollection &in_bindings, MySqlBindingCollection &out_bindings, ConsumeResultFun process_result)
Executes SELECT query using prepared statement.
void rollback()
Rollbacks current transaction.
static void ensureSchemaVersion(const ParameterMap &parameters, const DbCallback &cb=DbCallback(), const std::string &timer_name=std::string())
Retrieve schema version, validate it against the hardcoded version, and attempt to initialize the sch...
Fetch and Release MySQL Results.
MySqlFreeResult(MYSQL_STMT *statement)
Constructor.
MySQL Handle Holder.
RAII object representing MySQL transaction.
void commit()
Commits transaction.
MySqlTransaction(MySqlConnection &conn)
Constructor.
Key is NULL but was specified NOT NULL.
We want to reuse the database backend connection and exchange code for other uses,...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< MySqlBinding > MySqlBindingPtr
Shared pointer to the Binding class.
@ MYSQL_FATAL_ERROR
Definition db_log.h:65
boost::shared_ptr< IOServiceAccessor > IOServiceAccessorPtr
Pointer to an instance of IOServiceAccessor.
const int MLM_MYSQL_FETCH_FAILURE
MySQL fetch failure code.
int MysqlQuery(MYSQL *mysql, const char *stmt)
Execute a literal statement.
std::vector< MySqlBindingPtr > MySqlBindingCollection
Collection of bindings.
const int MLM_MYSQL_FETCH_SUCCESS
check for bool size
int retryOnDeadlock(Fun &fun, Args... args)
Retry on InnoDB deadlock.
std::function< bool(util::ReconnectCtlPtr db_reconnect_ctl)> DbCallback
Defines a callback prototype for propagating events upward.
int MysqlExecuteStatement(MYSQL_STMT *stmt)
Execute a prepared statement.
Defines the logger used by the top-level component of kea-lfc.
DB_LOG & arg(T first, Args... args)
Pass parameters to replace logger placeholders.
Definition db_log.h:144
MySQL Selection Statements.