Kea  2.1.7-git
mysql_connection.h
Go to the documentation of this file.
1 // Copyright (C) 2012-2022 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 
210  ~MySqlTransaction();
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 
305  void clearStatements();
306 
314  void openDatabase();
315 
323 
329  static
330  void convertToDatabaseTime(const time_t input_time, MYSQL_TIME& output_time);
331 
351  static
352  void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
353  MYSQL_TIME& expire);
354 
372  static
373  void convertFromDatabaseTime(const MYSQL_TIME& expire,
374  uint32_t valid_lifetime, time_t& cltt);
376 
390  void startTransaction();
391 
395  bool isTransactionStarted() const;
396 
424  template<typename StatementIndex>
425  void selectQuery(const StatementIndex& index,
426  const MySqlBindingCollection& in_bindings,
427  MySqlBindingCollection& out_bindings,
428  ConsumeResultFun process_result) {
429  checkUnusable();
430  // Extract native input bindings.
431  std::vector<MYSQL_BIND> in_bind_vec;
432  for (MySqlBindingPtr in_binding : in_bindings) {
433  in_bind_vec.push_back(in_binding->getMySqlBinding());
434  }
435 
436  int status = 0;
437  if (!in_bind_vec.empty()) {
438  // Bind parameters to the prepared statement.
439  status = mysql_stmt_bind_param(statements_[index],
440  in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
441  checkError(status, index, "unable to bind parameters for select");
442  }
443 
444  // Bind variables that will receive results as well.
445  std::vector<MYSQL_BIND> out_bind_vec;
446  for (MySqlBindingPtr out_binding : out_bindings) {
447  out_bind_vec.push_back(out_binding->getMySqlBinding());
448  }
449  if (!out_bind_vec.empty()) {
450  status = mysql_stmt_bind_result(statements_[index], &out_bind_vec[0]);
451  checkError(status, index, "unable to bind result parameters for select");
452  }
453 
454  // Execute query.
455  status = MysqlExecuteStatement(statements_[index]);
456  checkError(status, index, "unable to execute");
457 
458  status = mysql_stmt_store_result(statements_[index]);
459  checkError(status, index, "unable to set up for storing all results");
460 
461  // Fetch results.
462  MySqlFreeResult fetch_release(statements_[index]);
463  while ((status = mysql_stmt_fetch(statements_[index])) ==
465  try {
466  // For each returned row call user function which should
467  // consume the row and copy the data to a safe place.
468  process_result(out_bindings);
469 
470  } catch (const std::exception& ex) {
471  // Rethrow the exception with a bit more data.
472  isc_throw(BadValue, ex.what() << ". Statement is <" <<
473  text_statements_[index] << ">");
474  }
475  }
476 
477  // How did the fetch end?
478  // If mysql_stmt_fetch return value is equal to 1 an error occurred.
479  if (status == MLM_MYSQL_FETCH_FAILURE) {
480  // Error - unable to fetch results
481  checkError(status, index, "unable to fetch results");
482 
483  } else if (status == MYSQL_DATA_TRUNCATED) {
484  // Data truncated - throw an exception indicating what was at fault
485  isc_throw(DataTruncated, text_statements_[index]
486  << " returned truncated data");
487  }
488  }
489 
504  template<typename StatementIndex>
505  void insertQuery(const StatementIndex& index,
506  const MySqlBindingCollection& in_bindings) {
507  checkUnusable();
508  std::vector<MYSQL_BIND> in_bind_vec;
509  for (MySqlBindingPtr in_binding : in_bindings) {
510  in_bind_vec.push_back(in_binding->getMySqlBinding());
511  }
512 
513  // Bind the parameters to the statement
514  int status = mysql_stmt_bind_param(statements_[index],
515  in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
516  checkError(status, index, "unable to bind parameters");
517 
518  // Execute the statement
519  status = MysqlExecuteStatement(statements_[index]);
520 
521  if (status != 0) {
522  // Failure: check for the special case of duplicate entry.
523  if (mysql_errno(mysql_) == ER_DUP_ENTRY) {
524  isc_throw(DuplicateEntry, "Database duplicate entry error");
525  }
526  // Failure: check for the special case of WHERE returning NULL.
527  if (mysql_errno(mysql_) == ER_BAD_NULL_ERROR) {
528  isc_throw(NullKeyError, "Database bad NULL error");
529  }
530  checkError(status, index, "unable to execute");
531  }
532  }
533 
548  template<typename StatementIndex>
549  uint64_t updateDeleteQuery(const StatementIndex& index,
550  const MySqlBindingCollection& in_bindings) {
551  checkUnusable();
552  std::vector<MYSQL_BIND> in_bind_vec;
553  for (MySqlBindingPtr in_binding : in_bindings) {
554  in_bind_vec.push_back(in_binding->getMySqlBinding());
555  }
556 
557  // Bind the parameters to the statement
558  int status = mysql_stmt_bind_param(statements_[index],
559  in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
560  checkError(status, index, "unable to bind parameters");
561 
562  // Execute the statement
563  status = MysqlExecuteStatement(statements_[index]);
564 
565  if (status != 0) {
566  // Failure: check for the special case of duplicate entry.
567  if ((mysql_errno(mysql_) == ER_DUP_ENTRY)
568 #ifdef ER_FOREIGN_DUPLICATE_KEY
569  || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY)
570 #endif
571 #ifdef ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO
572  || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO)
573 #endif
574 #ifdef ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO
575  || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO)
576 #endif
577  ) {
578  isc_throw(DuplicateEntry, "Database duplicate entry error");
579  }
580  checkError(status, index, "unable to execute");
581  }
582 
583  // Let's return how many rows were affected.
584  return (static_cast<uint64_t>(mysql_stmt_affected_rows(statements_[index])));
585  }
586 
597  void commit();
598 
609  void rollback();
610 
639  template<typename StatementIndex>
640  void checkError(const int status, const StatementIndex& index,
641  const char* what) {
642  if (status != 0) {
643  switch(mysql_errno(mysql_)) {
644  // These are the ones we consider fatal. Remember this method is
645  // used to check errors of API calls made subsequent to successfully
646  // connecting. Errors occurring while attempting to connect are
647  // checked in the connection code. An alternative would be to call
648  // mysql_ping() - assuming autoreconnect is off. If that fails
649  // then we know connection is toast.
650  case CR_SERVER_GONE_ERROR:
651  case CR_SERVER_LOST:
652  case CR_OUT_OF_MEMORY:
653  case CR_CONNECTION_ERROR: {
655  .arg(what)
656  .arg(text_statements_[static_cast<int>(index)])
657  .arg(mysql_error(mysql_))
658  .arg(mysql_errno(mysql_));
659 
660  // Mark this connection as no longer usable.
661  markUnusable();
662 
663  // Start the connection recovery.
664  startRecoverDbConnection();
665 
666  // We still need to throw so caller can error out of the current
667  // processing.
669  "fatal database error or connectivity lost");
670  }
671  default:
672  // Connection is ok, so it must be an SQL error
673  isc_throw(db::DbOperationError, what << " for <"
674  << text_statements_[static_cast<int>(index)]
675  << ">, reason: "
676  << mysql_error(mysql_) << " (error code "
677  << mysql_errno(mysql_) << ")");
678  }
679  }
680  }
681 
688  if (callback_) {
689  if (!io_service_ && io_service_accessor_) {
690  io_service_ = (*io_service_accessor_)();
691  io_service_accessor_.reset();
692  }
693 
694  if (io_service_) {
695  io_service_->post(std::bind(callback_, reconnectCtl()));
696  }
697  }
698  }
699 
703  bool getTls() const {
704  return (tls_);
705  }
706 
710  std::string getTlsCipher() {
711  const char* cipher = mysql_get_ssl_cipher(mysql_);
712  return (cipher ? std::string(cipher) : "");
713  }
714 
719  std::vector<MYSQL_STMT*> statements_;
720 
725  std::vector<std::string> text_statements_;
726 
732 
741 
744 
752 
754  bool tls_;
755 };
756 
757 } // end of isc::db namespace
758 } // end of isc namespace
759 
760 #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.
std::function< bool(util::ReconnectCtlPtr db_reconnect_ctl)> DbCallback
Defines a callback prototype for propagating events upward.
MySqlHolder mysql_
MySQL connection handle.
Fetch and Release MySQL Results.
void insertQuery(const StatementIndex &index, const MySqlBindingCollection &in_bindings)
Executes INSERT prepared statement.
Data is truncated.
Definition: db_exceptions.h:23
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.
int transaction_ref_count_
Reference counter for transactions.
MySqlFreeResult(MYSQL_STMT *statement)
Constructor.
Common database connection class.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
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.
bool getTls() const
Get the TLS flag.
int MysqlQuery(MYSQL *mysql, const char *stmt)
Execute a literal statement.
std::string getTlsCipher()
Get the TLS cipher.
int retryOnDeadlock(Fun &fun, Args... args)
Retry on InnoDB deadlock.
~MySqlFreeResult()
Destructor.
const int MLM_MYSQL_FETCH_SUCCESS
check for bool size
Defines the logger used by the top-level component of kea-lfc.
uint64_t updateDeleteQuery(const StatementIndex &index, const MySqlBindingCollection &in_bindings)
Executes UPDATE or DELETE prepared statement and returns the number of affected rows.
DB_LOG & arg(T first, Args... args)
Pass parameters to replace logger placeholders.
Definition: db_log.h:141
MySQL Handle Holder.
std::vector< MYSQL_STMT * > statements_
Prepared statements.
const int MLM_MYSQL_FETCH_FAILURE
MySQL fetch failure code.
bool tls_
TLS flag (true when TLS was required, false otherwise).
MySqlHolder()
Constructor.
RAII object representing MySQL transaction.
MySqlConnection(const ParameterMap &parameters, IOServiceAccessorPtr io_accessor=IOServiceAccessorPtr(), DbCallback callback=DbCallback())
Constructor.
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...
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.
std::map< std::string, std::string > ParameterMap
Database configuration parameter map.
~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:37
boost::shared_ptr< IOServiceAccessor > IOServiceAccessorPtr
Pointer to an instance of IOServiceAccessor.
Database duplicate entry error.
Definition: db_exceptions.h:30
Common MySQL Connector Pool.