Kea  1.9.9-git
mysql_connection.h
Go to the documentation of this file.
1 // Copyright (C) 2012-2021 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>
12 #include <database/db_exceptions.h>
13 #include <database/db_log.h>
14 #include <exceptions/exceptions.h>
15 #include <mysql/mysql_binding.h>
16 #include <mysql/mysql_constants.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 
25 namespace isc {
26 namespace db {
27 
28 
42 
44 public:
45 
56  MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement)
57  {}
58 
63  (void) mysql_stmt_free_result(statement_);
64  }
65 
66 private:
67  MYSQL_STMT* statement_;
68 };
69 
75  uint32_t index;
76  const char* text;
77 };
78 
88 template <typename Fun, typename... Args>
89 int 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 
106 inline int MysqlExecuteStatement(MYSQL_STMT* stmt) {
107  return (retryOnDeadlock(mysql_stmt_execute, stmt));
108 }
109 
117 inline int MysqlQuery(MYSQL* mysql, const char* stmt) {
118  return (retryOnDeadlock(mysql_query, mysql, stmt));
119 }
120 
132 class MySqlHolder : public boost::noncopyable {
133 public:
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 
163 private:
164  static bool atexit_;
165 
166  MYSQL* mysql_;
167 };
168 
170 class MySqlConnection;
171 
192 class MySqlTransaction : public boost::noncopyable {
193 public:
194 
204 
209 
211  void commit();
212 
213 private:
214 
216  MySqlConnection& conn_;
217 
222  bool committed_;
223 };
224 
225 
234 public:
235 
237  typedef std::function<void(MySqlBindingCollection&)> ConsumeResultFun;
238 
246  MySqlConnection(const ParameterMap& parameters,
248  DbCallback callback = DbCallback())
249  : DatabaseConnection(parameters, callback),
250  io_service_accessor_(io_accessor), io_service_() {
251  }
252 
254  virtual ~MySqlConnection();
255 
266  static std::pair<uint32_t, uint32_t>
267  getVersion(const ParameterMap& parameters);
268 
282  void prepareStatement(uint32_t index, const char* text);
283 
298  void prepareStatements(const TaggedStatement* start_statement,
299  const TaggedStatement* end_statement);
300 
302  void clearStatements();
303 
311  void openDatabase();
312 
320 
326  static
327  void convertToDatabaseTime(const time_t input_time, MYSQL_TIME& output_time);
328 
348  static
349  void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
350  MYSQL_TIME& expire);
351 
369  static
370  void convertFromDatabaseTime(const MYSQL_TIME& expire,
371  uint32_t valid_lifetime, time_t& cltt);
373 
375  void startTransaction();
376 
404  template<typename StatementIndex>
405  void selectQuery(const StatementIndex& index,
406  const MySqlBindingCollection& in_bindings,
407  MySqlBindingCollection& out_bindings,
408  ConsumeResultFun process_result) {
409  checkUnusable();
410  // Extract native input bindings.
411  std::vector<MYSQL_BIND> in_bind_vec;
412  for (MySqlBindingPtr in_binding : in_bindings) {
413  in_bind_vec.push_back(in_binding->getMySqlBinding());
414  }
415 
416  int status = 0;
417  if (!in_bind_vec.empty()) {
418  // Bind parameters to the prepared statement.
419  status = mysql_stmt_bind_param(statements_[index],
420  in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
421  checkError(status, index, "unable to bind parameters for select");
422  }
423 
424  // Bind variables that will receive results as well.
425  std::vector<MYSQL_BIND> out_bind_vec;
426  for (MySqlBindingPtr out_binding : out_bindings) {
427  out_bind_vec.push_back(out_binding->getMySqlBinding());
428  }
429  if (!out_bind_vec.empty()) {
430  status = mysql_stmt_bind_result(statements_[index], &out_bind_vec[0]);
431  checkError(status, index, "unable to bind result parameters for select");
432  }
433 
434  // Execute query.
435  status = MysqlExecuteStatement(statements_[index]);
436  checkError(status, index, "unable to execute");
437 
438  status = mysql_stmt_store_result(statements_[index]);
439  checkError(status, index, "unable to set up for storing all results");
440 
441  // Fetch results.
442  MySqlFreeResult fetch_release(statements_[index]);
443  while ((status = mysql_stmt_fetch(statements_[index])) ==
445  try {
446  // For each returned row call user function which should
447  // consume the row and copy the data to a safe place.
448  process_result(out_bindings);
449 
450  } catch (const std::exception& ex) {
451  // Rethrow the exception with a bit more data.
452  isc_throw(BadValue, ex.what() << ". Statement is <" <<
453  text_statements_[index] << ">");
454  }
455  }
456 
457  // How did the fetch end?
458  // If mysql_stmt_fetch return value is equal to 1 an error occurred.
459  if (status == MLM_MYSQL_FETCH_FAILURE) {
460  // Error - unable to fetch results
461  checkError(status, index, "unable to fetch results");
462 
463  } else if (status == MYSQL_DATA_TRUNCATED) {
464  // Data truncated - throw an exception indicating what was at fault
466  << " returned truncated data");
467  }
468  }
469 
484  template<typename StatementIndex>
485  void insertQuery(const StatementIndex& index,
486  const MySqlBindingCollection& in_bindings) {
487  checkUnusable();
488  std::vector<MYSQL_BIND> in_bind_vec;
489  for (MySqlBindingPtr in_binding : in_bindings) {
490  in_bind_vec.push_back(in_binding->getMySqlBinding());
491  }
492 
493  // Bind the parameters to the statement
494  int status = mysql_stmt_bind_param(statements_[index],
495  in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
496  checkError(status, index, "unable to bind parameters");
497 
498  // Execute the statement
499  status = MysqlExecuteStatement(statements_[index]);
500 
501  if (status != 0) {
502  // Failure: check for the special case of duplicate entry.
503  if (mysql_errno(mysql_) == ER_DUP_ENTRY) {
504  isc_throw(DuplicateEntry, "Database duplicate entry error");
505  }
506  // Failure: check for the special case of WHERE returning NULL.
507  if (mysql_errno(mysql_) == ER_BAD_NULL_ERROR) {
508  isc_throw(NullKeyError, "Database bad NULL error");
509  }
510  checkError(status, index, "unable to execute");
511  }
512  }
513 
528  template<typename StatementIndex>
529  uint64_t updateDeleteQuery(const StatementIndex& index,
530  const MySqlBindingCollection& in_bindings) {
531  checkUnusable();
532  std::vector<MYSQL_BIND> in_bind_vec;
533  for (MySqlBindingPtr in_binding : in_bindings) {
534  in_bind_vec.push_back(in_binding->getMySqlBinding());
535  }
536 
537  // Bind the parameters to the statement
538  int status = mysql_stmt_bind_param(statements_[index],
539  in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
540  checkError(status, index, "unable to bind parameters");
541 
542  // Execute the statement
543  status = MysqlExecuteStatement(statements_[index]);
544 
545  if (status != 0) {
546  // Failure: check for the special case of duplicate entry.
547  if ((mysql_errno(mysql_) == ER_DUP_ENTRY)
548 #ifdef ER_FOREIGN_DUPLICATE_KEY
549  || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY)
550 #endif
551 #ifdef ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO
552  || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO)
553 #endif
554 #ifdef ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO
555  || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO)
556 #endif
557  ) {
558  isc_throw(DuplicateEntry, "Database duplicate entry error");
559  }
560  checkError(status, index, "unable to execute");
561  }
562 
563  // Let's return how many rows were affected.
564  return (static_cast<uint64_t>(mysql_stmt_affected_rows(statements_[index])));
565  }
566 
573  void commit();
574 
581  void rollback();
582 
611  template<typename StatementIndex>
612  void checkError(const int status, const StatementIndex& index,
613  const char* what) {
614  if (status != 0) {
615  switch(mysql_errno(mysql_)) {
616  // These are the ones we consider fatal. Remember this method is
617  // used to check errors of API calls made subsequent to successfully
618  // connecting. Errors occurring while attempting to connect are
619  // checked in the connection code. An alternative would be to call
620  // mysql_ping() - assuming autoreconnect is off. If that fails
621  // then we know connection is toast.
622  case CR_SERVER_GONE_ERROR:
623  case CR_SERVER_LOST:
624  case CR_OUT_OF_MEMORY:
625  case CR_CONNECTION_ERROR: {
627  .arg(what)
628  .arg(text_statements_[static_cast<int>(index)])
629  .arg(mysql_error(mysql_))
630  .arg(mysql_errno(mysql_));
631 
632  // Mark this connection as no longer usable.
633  markUnusable();
634 
635  // Start the connection recovery.
637 
638  // We still need to throw so caller can error out of the current
639  // processing.
641  "fatal database error or connectivity lost");
642  }
643  default:
644  // Connection is ok, so it must be an SQL error
645  isc_throw(db::DbOperationError, what << " for <"
646  << text_statements_[static_cast<int>(index)]
647  << ">, reason: "
648  << mysql_error(mysql_) << " (error code "
649  << mysql_errno(mysql_) << ")");
650  }
651  }
652  }
653 
660  if (callback_) {
662  io_service_ = (*io_service_accessor_)();
663  io_service_accessor_.reset();
664  }
665 
666  if (io_service_) {
667  io_service_->post(std::bind(callback_, reconnectCtl()));
668  }
669  }
670  }
671 
676  std::vector<MYSQL_STMT*> statements_;
677 
682  std::vector<std::string> text_statements_;
683 
689 
698 
701 };
702 
703 } // end of isc::db namespace
704 } // end of isc namespace
705 
706 #endif // MYSQL_CONNECTION_H
std::vector< std::string > text_statements_
Raw text of statements.
We want to reuse the database backend connection and exchange code for other uses, in particular for hook libraries.
void selectQuery(const StatementIndex &index, const MySqlBindingCollection &in_bindings, MySqlBindingCollection &out_bindings, ConsumeResultFun process_result)
Executes SELECT query using prepared statement.
MySqlHolder mysql_
MySQL connection handle.
Fetch and Release MySQL Results.
void insertQuery(const StatementIndex &index, const MySqlBindingCollection &in_bindings)
Executes INSERT prepared statement.
static void convertToDatabaseTime(const time_t input_time, MYSQL_TIME &output_time)
Convert time_t value to database time.
Data is truncated.
Definition: db_exceptions.h:35
DB_LOG & arg(T first, Args...args)
Pass parameters to replace logger placeholders.
Definition: db_log.h:144
static std::pair< uint32_t, uint32_t > getVersion(const ParameterMap &parameters)
Get the schema version.
void commit()
Commits transaction.
MySqlTransaction(MySqlConnection &conn)
Constructor.
std::function< void(MySqlBindingCollection &)> ConsumeResultFun
Function invoked to process fetched row.
ReconnectCtlPtr reconnectCtl()
The reconnect settings.
void checkError(const int status, const StatementIndex &index, const char *what)
Check Error and Throw Exception.
int retryOnDeadlock(Fun &fun, Args...args)
Retry on InnoDB deadlock.
MySqlFreeResult(MYSQL_STMT *statement)
Constructor.
Common database connection class.
static void convertFromDatabaseTime(const MYSQL_TIME &expire, uint32_t valid_lifetime, time_t &cltt)
Convert Database Time to Lease Times.
void checkUnusable()
Throws an exception if the connection is not usable.
Exception thrown on failure to open database.
int MysqlExecuteStatement(MYSQL_STMT *stmt)
Execute a prepared statement.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
isc::asiolink::IOServicePtr io_service_
IOService object, used for all ASIO operations.
int MysqlQuery(MYSQL *mysql, const char *stmt)
Execute a literal statement.
~MySqlFreeResult()
Destructor.
void clearStatements()
Clears prepared statements and text statements.
virtual ~MySqlConnection()
Destructor.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
const int MLM_MYSQL_FETCH_SUCCESS
check for bool size
Defines the logger used by the top-level component of kea-dhcp-ddns.
std::function< bool(ReconnectCtlPtr db_reconnect_ctl)> DbCallback
Defines a callback prototype for propagating events upward.
uint64_t updateDeleteQuery(const StatementIndex &index, const MySqlBindingCollection &in_bindings)
Executes UPDATE or DELETE prepared statement and returns the number of affected rows.
MySQL Handle Holder.
std::vector< MYSQL_STMT * > statements_
Prepared statements.
const int MLM_MYSQL_FETCH_FAILURE
MySQL fetch failure code.
MySqlHolder()
Constructor.
RAII object representing MySQL transaction.
void startTransaction()
Starts Transaction.
MySqlConnection(const ParameterMap &parameters, IOServiceAccessorPtr io_accessor=IOServiceAccessorPtr(), DbCallback callback=DbCallback())
Constructor.
void markUnusable()
Sets the unusable flag to true.
void startRecoverDbConnection()
The recover connection.
std::vector< MySqlBindingPtr > MySqlBindingCollection
Collection of bindings.
Exception thrown when a specific connection has been rendered unusable either through loss of connect...
void rollback()
Rollback Transactions.
IOServiceAccessorPtr io_service_accessor_
Accessor function which returns the IOService that can be used to recover the connection.
boost::shared_ptr< MySqlBinding > MySqlBindingPtr
Shared pointer to the Binding class.
void commit()
Commit Transactions.
std::map< std::string, std::string > ParameterMap
Database configuration parameter map.
void prepareStatement(uint32_t index, const char *text)
Prepare Single Statement.
void prepareStatements(const TaggedStatement *start_statement, const TaggedStatement *end_statement)
Prepare statements.
void openDatabase()
Open Database.
~MySqlHolder()
Destructor.
MySQL Selection Statements.
Exception thrown on failure to execute a database function.
Key is NULL but was specified NOT NULL.
Definition: db_exceptions.h:49
boost::shared_ptr< IOServiceAccessor > IOServiceAccessorPtr
Pointer to an instance of IOServiceAccessor.
DbCallback callback_
The callback used to recover the connection.
Database duplicate entry error.
Definition: db_exceptions.h:42
Common MySQL Connector Pool.