mysql_connection.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. // Copyright (C) 2012-2016 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. #ifndef MYSQL_CONNECTION_H
  7. #define MYSQL_CONNECTION_H
  8. #include <dhcpsrv/database_connection.h>
  9. #include <dhcpsrv/dhcpsrv_log.h>
  10. #include <exceptions/exceptions.h>
  11. #include <boost/scoped_ptr.hpp>
  12. #include <mysql.h>
  13. #include <mysqld_error.h>
  14. #include <errmsg.h>
  15. #include <vector>
  16. #include <stdint.h>
  17. namespace isc {
  18. namespace dhcp {
  19. /// @name MySQL constants.
  20. ///
  21. //@{
  22. /// @brief MySQL false value.
  23. extern const my_bool MLM_FALSE;
  24. /// @brief MySQL true value.
  25. extern const my_bool MLM_TRUE;
  26. /// @brief MySQL fetch success code.
  27. extern const int MLM_MYSQL_FETCH_SUCCESS;
  28. /// @brief MySQL fetch failure code.
  29. extern const int MLM_MYSQL_FETCH_FAILURE;
  30. //@}
  31. /// @name Current database schema version values.
  32. //@{
  33. const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 4;
  34. const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 2;
  35. //@}
  36. /// @brief Fetch and Release MySQL Results
  37. ///
  38. /// When a MySQL statement is expected, to fetch the results the function
  39. /// mysql_stmt_fetch() must be called. As well as getting data, this
  40. /// allocates internal state. Subsequent calls to mysql_stmt_fetch can be
  41. /// made, but when all the data is retrieved, mysql_stmt_free_result must be
  42. /// called to free up the resources allocated.
  43. ///
  44. /// Created prior to the first fetch, this class's destructor calls
  45. /// mysql_stmt_free_result, so eliminating the need for an explicit release
  46. /// in the method calling mysql_stmt_free_result. In this way, it guarantees
  47. /// that the resources are released even if the MySqlLeaseMgr method concerned
  48. /// exits via an exception.
  49. class MySqlFreeResult {
  50. public:
  51. /// @brief Constructor
  52. ///
  53. /// Store the pointer to the statement for which data is being fetched.
  54. ///
  55. /// Note that according to the MySQL documentation, mysql_stmt_free_result
  56. /// only releases resources if a cursor has been allocated for the
  57. /// statement. This implies that it is a no-op if none have been. Either
  58. /// way, any error from mysql_stmt_free_result is ignored. (Generating
  59. /// an exception is not much help, as it will only confuse things if the
  60. /// method calling mysql_stmt_fetch is exiting via an exception.)
  61. MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement)
  62. {}
  63. /// @brief Destructor
  64. ///
  65. /// Frees up fetch context if a fetch has been successfully executed.
  66. ~MySqlFreeResult() {
  67. (void) mysql_stmt_free_result(statement_);
  68. }
  69. private:
  70. MYSQL_STMT* statement_; ///< Statement for which results are freed
  71. };
  72. /// @brief MySQL Selection Statements
  73. ///
  74. /// Each statement is associated with an index, which is used to reference the
  75. /// associated prepared statement.
  76. struct TaggedStatement {
  77. uint32_t index;
  78. const char* text;
  79. };
  80. /// @brief MySQL Handle Holder
  81. ///
  82. /// Small RAII object for safer initialization, will close the database
  83. /// connection upon destruction. This means that if an exception is thrown
  84. /// during database initialization, resources allocated to the database are
  85. /// guaranteed to be freed.
  86. ///
  87. /// It makes no sense to copy an object of this class. After the copy, both
  88. /// objects would contain pointers to the same MySql context object. The
  89. /// destruction of one would invalid the context in the remaining object.
  90. /// For this reason, the class is declared noncopyable.
  91. class MySqlHolder : public boost::noncopyable {
  92. public:
  93. /// @brief Constructor
  94. ///
  95. /// Initialize MySql and store the associated context object.
  96. ///
  97. /// @throw DbOpenError Unable to initialize MySql handle.
  98. MySqlHolder() : mysql_(mysql_init(NULL)) {
  99. if (mysql_ == NULL) {
  100. isc_throw(DbOpenError, "unable to initialize MySQL");
  101. }
  102. }
  103. /// @brief Destructor
  104. ///
  105. /// Frees up resources allocated by the initialization of MySql.
  106. ~MySqlHolder() {
  107. if (mysql_ != NULL) {
  108. mysql_close(mysql_);
  109. }
  110. // The library itself shouldn't be needed anymore
  111. mysql_library_end();
  112. }
  113. /// @brief Conversion Operator
  114. ///
  115. /// Allows the MySqlHolder object to be passed as the context argument to
  116. /// mysql_xxx functions.
  117. operator MYSQL*() const {
  118. return (mysql_);
  119. }
  120. private:
  121. MYSQL* mysql_; ///< Initialization context
  122. };
  123. /// @brief Forward declaration to @ref MySqlConnection.
  124. class MySqlConnection;
  125. /// @brief RAII object representing MySQL transaction.
  126. ///
  127. /// An instance of this class should be created in a scope where multiple
  128. /// INSERT statements should be executed within a single transaction. The
  129. /// transaction is started when the constructor of this class is invoked.
  130. /// The transaction is ended when the @ref MySqlTransaction::commit is
  131. /// explicitly called or when the instance of this class is destroyed.
  132. /// The @ref MySqlTransaction::commit commits changes to the database
  133. /// and the changes remain in the database when the instance of the
  134. /// class is destroyed. If the class instance is destroyed before the
  135. /// @ref MySqlTransaction::commit is called, the transaction is rolled
  136. /// back. The rollback on destruction guarantees that partial data is
  137. /// not stored in the database when there is an error during any
  138. /// of the operations belonging to a transaction.
  139. ///
  140. /// The default MySQL backend configuration enables 'autocommit'.
  141. /// Starting a transaction overrides 'autocommit' setting for this
  142. /// particular transaction only. It does not affect the global 'autocommit'
  143. /// setting for the database connection, i.e. all modifications to the
  144. /// database which don't use transactions will still be auto committed.
  145. class MySqlTransaction : public boost::noncopyable {
  146. public:
  147. /// @brief Constructor.
  148. ///
  149. /// Starts transaction by making a "START TRANSACTION" query.
  150. ///
  151. /// @param conn MySQL connection to use for the transaction. This
  152. /// connection will be later used to commit or rollback changes.
  153. ///
  154. /// @throw DbOperationError if "START TRANSACTION" query fails.
  155. MySqlTransaction(MySqlConnection& conn);
  156. /// @brief Destructor.
  157. ///
  158. /// Rolls back the transaction if changes haven't been committed.
  159. ~MySqlTransaction();
  160. /// @brief Commits transaction.
  161. void commit();
  162. private:
  163. /// @brief Holds reference to the MySQL database connection.
  164. MySqlConnection& conn_;
  165. /// @brief Boolean flag indicating if the transaction has been committed.
  166. ///
  167. /// This flag is used in the class destructor to assess if the
  168. /// transaction should be rolled back.
  169. bool committed_;
  170. };
  171. /// @brief Common MySQL Connector Pool
  172. ///
  173. /// This class provides common operations for MySQL database connection
  174. /// used by both MySqlLeaseMgr and MySqlHostDataSource. It manages connecting
  175. /// to the database and preparing compiled statements. Its fields are
  176. /// public, because they are used (both set and retrieved) in classes
  177. /// that use instances of MySqlConnection.
  178. class MySqlConnection : public DatabaseConnection {
  179. public:
  180. /// @brief Constructor
  181. ///
  182. /// Initialize MySqlConnection object with parameters needed for connection.
  183. MySqlConnection(const ParameterMap& parameters)
  184. : DatabaseConnection(parameters) {
  185. }
  186. /// @brief Destructor
  187. virtual ~MySqlConnection();
  188. /// @brief Prepare Single Statement
  189. ///
  190. /// Creates a prepared statement from the text given and adds it to the
  191. /// statements_ vector at the given index.
  192. ///
  193. /// @param index Index into the statements_ vector into which the text
  194. /// should be placed. The vector must be big enough for the index
  195. /// to be valid, else an exception will be thrown.
  196. /// @param text Text of the SQL statement to be prepared.
  197. ///
  198. /// @throw isc::dhcp::DbOperationError An operation on the open database has
  199. /// failed.
  200. /// @throw isc::InvalidParameter 'index' is not valid for the vector.
  201. void prepareStatement(uint32_t index, const char* text);
  202. /// @brief Prepare statements
  203. ///
  204. /// Creates the prepared statements for all of the SQL statements used
  205. /// by the MySQL backend.
  206. /// @param tagged_statements an array of statements to be compiled
  207. /// @param num_statements number of statements in tagged_statements
  208. ///
  209. /// @throw isc::dhcp::DbOperationError An operation on the open database has
  210. /// failed.
  211. /// @throw isc::InvalidParameter 'index' is not valid for the vector. This
  212. /// represents an internal error within the code.
  213. void prepareStatements(const TaggedStatement tagged_statements[],
  214. size_t num_statements);
  215. /// @brief Open Database
  216. ///
  217. /// Opens the database using the information supplied in the parameters
  218. /// passed to the constructor.
  219. ///
  220. /// @throw NoDatabaseName Mandatory database name not given
  221. /// @throw DbOpenError Error opening the database
  222. void openDatabase();
  223. ///@{
  224. /// The following methods are used to convert between times and time
  225. /// intervals stored in the Lease object, and the times stored in the
  226. /// database. The reason for the difference is because in the DHCP server,
  227. /// the cltt (Client Time Since Last Transmission) is the natural data; in
  228. /// the lease file - which may be read by the user - it is the expiry time
  229. /// of the lease.
  230. /// @brief Convert time_t value to database time.
  231. ///
  232. /// @param input_time A time_t value representing time.
  233. /// @param output_time Reference to MYSQL_TIME object where converted time
  234. /// will be put.
  235. static
  236. void convertToDatabaseTime(const time_t input_time, MYSQL_TIME& output_time);
  237. /// @brief Convert Lease Time to Database Times
  238. ///
  239. /// Within the DHCP servers, times are stored as client last transmit time
  240. /// and valid lifetime. In the database, the information is stored as
  241. /// valid lifetime and "expire" (time of expiry of the lease). They are
  242. /// related by the equation:
  243. ///
  244. /// - expire = client last transmit time + valid lifetime
  245. ///
  246. /// This method converts from the times in the lease object into times
  247. /// able to be added to the database.
  248. ///
  249. /// @param cltt Client last transmit time
  250. /// @param valid_lifetime Valid lifetime
  251. /// @param expire Reference to MYSQL_TIME object where the expiry time of
  252. /// the lease will be put.
  253. ///
  254. /// @throw isc::BadValue if the sum of the calculated expiration time is
  255. /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
  256. static
  257. void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
  258. MYSQL_TIME& expire);
  259. /// @brief Convert Database Time to Lease Times
  260. ///
  261. /// Within the database, time is stored as "expire" (time of expiry of the
  262. /// lease) and valid lifetime. In the DHCP server, the information is
  263. /// stored client last transmit time and valid lifetime. These are related
  264. /// by the equation:
  265. ///
  266. /// - client last transmit time = expire - valid_lifetime
  267. ///
  268. /// This method converts from the times in the database into times
  269. /// able to be inserted into the lease object.
  270. ///
  271. /// @param expire Reference to MYSQL_TIME object from where the expiry
  272. /// time of the lease is taken.
  273. /// @param valid_lifetime lifetime of the lease.
  274. /// @param cltt Reference to location where client last transmit time
  275. /// is put.
  276. static
  277. void convertFromDatabaseTime(const MYSQL_TIME& expire,
  278. uint32_t valid_lifetime, time_t& cltt);
  279. ///@}
  280. /// @brief Starts Transaction
  281. void startTransaction();
  282. /// @brief Commit Transactions
  283. ///
  284. /// Commits all pending database operations. On databases that don't
  285. /// support transactions, this is a no-op.
  286. ///
  287. /// @throw DbOperationError If the commit failed.
  288. void commit();
  289. /// @brief Rollback Transactions
  290. ///
  291. /// Rolls back all pending database operations. On databases that don't
  292. /// support transactions, this is a no-op.
  293. ///
  294. /// @throw DbOperationError If the rollback failed.
  295. void rollback();
  296. /// @brief Check Error and Throw Exception
  297. ///
  298. /// Virtually all MySQL functions return a status which, if non-zero,
  299. /// indicates an error. This function centralizes the error checking
  300. /// code.
  301. ///
  302. /// It is used to determine whether or not the function succeeded, and
  303. /// in the event of failures, decide whether or not those failures are
  304. /// recoverable.
  305. ///
  306. /// If the error is recoverable, the method will throw a DbOperationError.
  307. /// In the error is deemed unrecoverable, such as a loss of connectivity
  308. /// with the server, this method will log the error and call exit(-1);
  309. ///
  310. /// @todo Calling exit() is viewed as a short term solution for Kea 1.0.
  311. /// Two tickets are likely to alter this behavior, first is #3639, which
  312. /// calls for the ability to attempt to reconnect to the database. The
  313. /// second ticket, #4087 which calls for the implementation of a generic,
  314. /// FatalException class which will propagate outward.
  315. ///
  316. /// @param status Status code: non-zero implies an error
  317. /// @param index Index of statement that caused the error
  318. /// @param what High-level description of the error
  319. ///
  320. /// @tparam Enumeration representing index of a statement to which an
  321. /// error pertains.
  322. ///
  323. /// @throw isc::dhcp::DbOperationError An operation on the open database has
  324. /// failed.
  325. template<typename StatementIndex>
  326. void checkError(const int status, const StatementIndex& index,
  327. const char* what) const {
  328. if (status != 0) {
  329. switch(mysql_errno(mysql_)) {
  330. // These are the ones we consider fatal. Remember this method is
  331. // used to check errors of API calls made subsequent to successfully
  332. // connecting. Errors occuring while attempting to connect are
  333. // checked in the connection code. An alternative would be to call
  334. // mysql_ping() - assuming autoreconnect is off. If that fails
  335. // then we know connection is toast.
  336. case CR_SERVER_GONE_ERROR:
  337. case CR_SERVER_LOST:
  338. case CR_OUT_OF_MEMORY:
  339. case CR_CONNECTION_ERROR:
  340. // We're exiting on fatal
  341. LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_FATAL_ERROR)
  342. .arg(what)
  343. .arg(text_statements_[static_cast<int>(index)])
  344. .arg(mysql_error(mysql_))
  345. .arg(mysql_errno(mysql_));
  346. exit (-1);
  347. default:
  348. // Connection is ok, so it must be an SQL error
  349. isc_throw(DbOperationError, what << " for <"
  350. << text_statements_[static_cast<int>(index)]
  351. << ">, reason: "
  352. << mysql_error(mysql_) << " (error code "
  353. << mysql_errno(mysql_) << ")");
  354. }
  355. }
  356. }
  357. /// @brief Prepared statements
  358. ///
  359. /// This field is public, because it is used heavily from MySqlConnection
  360. /// and will be from MySqlHostDataSource.
  361. std::vector<MYSQL_STMT*> statements_;
  362. /// @brief Raw text of statements
  363. ///
  364. /// This field is public, because it is used heavily from MySqlConnection
  365. /// and will be from MySqlHostDataSource.
  366. std::vector<std::string> text_statements_;
  367. /// @brief MySQL connection handle
  368. ///
  369. /// This field is public, because it is used heavily from MySqlConnection
  370. /// and will be from MySqlHostDataSource.
  371. MySqlHolder mysql_;
  372. };
  373. }; // end of isc::dhcp namespace
  374. }; // end of isc namespace
  375. #endif // MYSQL_CONNECTION_H