Kea  2.5.2
mysql_connection.h
Go to the documentation of this file.
1 // Copyright (C) 2012-2023 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:
165  static int atexit_;
166 
168  MYSQL* mysql_;
169 };
170 
172 class MySqlConnection;
173 
194 class MySqlTransaction : public boost::noncopyable {
195 public:
196 
206 
211 
213  void commit();
214 
215 private:
216 
218  MySqlConnection& conn_;
219 
224  bool committed_;
225 };
226 
227 
236 public:
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 
269  static std::pair<uint32_t, uint32_t>
270  getVersion(const ParameterMap& parameters);
271 
285  void prepareStatement(uint32_t index, const char* text);
286 
301  void prepareStatements(const TaggedStatement* start_statement,
302  const TaggedStatement* end_statement);
303 
313  template<typename StatementIndex>
314  MYSQL_STMT* getStatement(StatementIndex index) const {
315  if (statements_[index]->mysql == 0) {
317  "MySQL pointer for the prepared statement is NULL as a result of connectivity loss");
318  }
319  return (statements_[index]);
320  }
321 
329  void openDatabase();
330 
338 
344  static
345  void convertToDatabaseTime(const time_t input_time, MYSQL_TIME& output_time);
346 
366  static
367  void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
368  MYSQL_TIME& expire);
369 
387  static
388  void convertFromDatabaseTime(const MYSQL_TIME& expire,
389  uint32_t valid_lifetime, time_t& cltt);
391 
405  void startTransaction();
406 
410  bool isTransactionStarted() const;
411 
439  template<typename StatementIndex>
440  void selectQuery(const StatementIndex& index,
441  const MySqlBindingCollection& in_bindings,
442  MySqlBindingCollection& out_bindings,
443  ConsumeResultFun process_result) {
444  checkUnusable();
445  // Extract native input bindings.
446  std::vector<MYSQL_BIND> in_bind_vec;
447  for (MySqlBindingPtr in_binding : in_bindings) {
448  in_bind_vec.push_back(in_binding->getMySqlBinding());
449  }
450 
451  int status = 0;
452  if (!in_bind_vec.empty()) {
453  // Bind parameters to the prepared statement.
454  status = mysql_stmt_bind_param(getStatement(index),
455  in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
456  checkError(status, index, "unable to bind parameters for select");
457  }
458 
459  // Bind variables that will receive results as well.
460  std::vector<MYSQL_BIND> out_bind_vec;
461  for (MySqlBindingPtr out_binding : out_bindings) {
462  out_bind_vec.push_back(out_binding->getMySqlBinding());
463  }
464  if (!out_bind_vec.empty()) {
465  status = mysql_stmt_bind_result(getStatement(index), &out_bind_vec[0]);
466  checkError(status, index, "unable to bind result parameters for select");
467  }
468 
469  // Execute query.
470  status = MysqlExecuteStatement(getStatement(index));
471  checkError(status, index, "unable to execute");
472 
473  status = mysql_stmt_store_result(getStatement(index));
474  checkError(status, index, "unable to set up for storing all results");
475 
476  // Fetch results.
477  MySqlFreeResult fetch_release(getStatement(index));
478  while ((status = mysql_stmt_fetch(getStatement(index))) ==
480  try {
481  // For each returned row call user function which should
482  // consume the row and copy the data to a safe place.
483  process_result(out_bindings);
484 
485  } catch (const std::exception& ex) {
486  // Rethrow the exception with a bit more data.
487  isc_throw(BadValue, ex.what() << ". Statement is <" <<
488  text_statements_[index] << ">");
489  }
490  }
491 
492  // How did the fetch end?
493  // If mysql_stmt_fetch return value is equal to 1 an error occurred.
494  if (status == MLM_MYSQL_FETCH_FAILURE) {
495  // Error - unable to fetch results
496  checkError(status, index, "unable to fetch results");
497 
498  } else if (status == MYSQL_DATA_TRUNCATED) {
499  // Data truncated - throw an exception indicating what was at fault
501  << " returned truncated data");
502  }
503  }
504 
519  template<typename StatementIndex>
520  void insertQuery(const StatementIndex& index,
521  const MySqlBindingCollection& in_bindings) {
522  checkUnusable();
523  std::vector<MYSQL_BIND> in_bind_vec;
524  for (MySqlBindingPtr in_binding : in_bindings) {
525  in_bind_vec.push_back(in_binding->getMySqlBinding());
526  }
527 
528  // Bind the parameters to the statement
529  int status = mysql_stmt_bind_param(getStatement(index),
530  in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
531  checkError(status, index, "unable to bind parameters");
532 
533  // Execute the statement
534  status = MysqlExecuteStatement(getStatement(index));
535 
536  if (status != 0) {
537  // Failure: check for the special case of duplicate entry.
538  if (mysql_errno(mysql_) == ER_DUP_ENTRY) {
539  isc_throw(DuplicateEntry, "Database duplicate entry error");
540  }
541  // Failure: check for the special case of WHERE returning NULL.
542  if (mysql_errno(mysql_) == ER_BAD_NULL_ERROR) {
543  isc_throw(NullKeyError, "Database bad NULL error");
544  }
545  checkError(status, index, "unable to execute");
546  }
547  }
548 
563  template<typename StatementIndex>
564  uint64_t updateDeleteQuery(const StatementIndex& index,
565  const MySqlBindingCollection& in_bindings) {
566  checkUnusable();
567  std::vector<MYSQL_BIND> in_bind_vec;
568  for (MySqlBindingPtr in_binding : in_bindings) {
569  in_bind_vec.push_back(in_binding->getMySqlBinding());
570  }
571 
572  // Bind the parameters to the statement
573  int status = mysql_stmt_bind_param(getStatement(index),
574  in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
575  checkError(status, index, "unable to bind parameters");
576 
577  // Execute the statement
578  status = MysqlExecuteStatement(getStatement(index));
579 
580  if (status != 0) {
581  // Failure: check for the special case of duplicate entry.
582  if ((mysql_errno(mysql_) == ER_DUP_ENTRY)
583 #ifdef ER_FOREIGN_DUPLICATE_KEY
584  || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY)
585 #endif
586 #ifdef ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO
587  || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO)
588 #endif
589 #ifdef ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO
590  || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO)
591 #endif
592  ) {
593  isc_throw(DuplicateEntry, "Database duplicate entry error");
594  }
595  checkError(status, index, "unable to execute");
596  }
597 
598  // Let's return how many rows were affected.
599  return (static_cast<uint64_t>(mysql_stmt_affected_rows(getStatement(index))));
600  }
601 
612  void commit();
613 
624  void rollback();
625 
654  template<typename StatementIndex>
655  void checkError(const int status, const StatementIndex& index,
656  const char* what) {
657  if (status != 0) {
658  switch(mysql_errno(mysql_)) {
659  // These are the ones we consider fatal. Remember this method is
660  // used to check errors of API calls made subsequent to successfully
661  // connecting. Errors occurring while attempting to connect are
662  // checked in the connection code. An alternative would be to call
663  // mysql_ping() - assuming autoreconnect is off. If that fails
664  // then we know connection is toast.
665  case CR_SERVER_GONE_ERROR:
666  case CR_SERVER_LOST:
667  case CR_OUT_OF_MEMORY:
668  case CR_CONNECTION_ERROR: {
670  .arg(what)
671  .arg(text_statements_[static_cast<int>(index)])
672  .arg(mysql_error(mysql_))
673  .arg(mysql_errno(mysql_));
674 
675  // Mark this connection as no longer usable.
676  markUnusable();
677 
678  // Start the connection recovery.
680 
681  // We still need to throw so caller can error out of the current
682  // processing.
684  "fatal database error or connectivity lost");
685  }
686  default:
687  // Connection is ok, so it must be an SQL error
688  isc_throw(db::DbOperationError, what << " for <"
689  << text_statements_[static_cast<int>(index)]
690  << ">, reason: "
691  << mysql_error(mysql_) << " (error code "
692  << mysql_errno(mysql_) << ")");
693  }
694  }
695  }
696 
703  if (callback_) {
705  io_service_ = (*io_service_accessor_)();
706  io_service_accessor_.reset();
707  }
708 
709  if (io_service_) {
710  io_service_->post(std::bind(callback_, reconnectCtl()));
711  }
712  }
713  }
714 
718  bool getTls() const {
719  return (tls_);
720  }
721 
725  std::string getTlsCipher() {
726  const char* cipher = mysql_get_ssl_cipher(mysql_);
727  return (cipher ? std::string(cipher) : "");
728  }
729 
730 private:
731 
746  template<typename T>
747  void setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value);
748 
753  std::vector<MYSQL_STMT*> statements_;
754 
755 public:
756 
761  std::vector<std::string> text_statements_;
762 
768 
777 
780 
788 
790  bool tls_;
791 };
792 
793 } // end of isc::db namespace
794 } // end of isc namespace
795 
796 #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.
Definition: db_exceptions.h:23
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.
Definition: db_exceptions.h:30
Common MySQL Connector Pool.
isc::asiolink::IOServicePtr io_service_
IOService object, used for all ASIO operations.
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.
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.
uint64_t updateDeleteQuery(const StatementIndex &index, const MySqlBindingCollection &in_bindings)
Executes UPDATE or DELETE prepared statement and returns the number of affected rows.
void openDatabase()
Open Database.
std::string getTlsCipher()
Get the TLS cipher.
void prepareStatements(const TaggedStatement *start_statement, const TaggedStatement *end_statement)
Prepare statements.
static std::pair< uint32_t, uint32_t > getVersion(const ParameterMap &parameters)
Get the schema version.
int transaction_ref_count_
Reference counter for transactions.
void startTransaction()
Starts new transaction.
virtual ~MySqlConnection()
Destructor.
std::function< void(MySqlBindingCollection &)> ConsumeResultFun
Function invoked to process fetched row.
void checkError(const int status, const StatementIndex &index, const char *what)
Check Error and Throw Exception.
bool getTls() const
Get the TLS flag.
void selectQuery(const StatementIndex &index, const MySqlBindingCollection &in_bindings, MySqlBindingCollection &out_bindings, ConsumeResultFun process_result)
Executes SELECT query using prepared statement.
MYSQL_STMT * getStatement(StatementIndex index) const
Returns a prepared statement by an index.
void rollback()
Rollbacks current transaction.
Fetch and Release MySQL Results.
MySqlFreeResult(MYSQL_STMT *statement)
Constructor.
MySQL Handle Holder.
MySqlHolder()
Constructor.
RAII object representing MySQL transaction.
void commit()
Commits transaction.
MySqlTransaction(MySqlConnection &conn)
Constructor.
Key is NULL but was specified NOT NULL.
Definition: db_exceptions.h:37
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:63
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:142
MySQL Selection Statements.