Kea 1.5.0
mysql_connection.cc
Go to the documentation of this file.
1// Copyright (C) 2012-2018 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#include <config.h>
8
9#include <database/db_log.h>
12
13#include <boost/lexical_cast.hpp>
14
15#include <algorithm>
16#include <stdint.h>
17#include <string>
18#include <limits>
19
20using namespace isc;
21using namespace std;
22
23namespace isc {
24namespace db {
25
27const int MYSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
28
30 : conn_(conn), committed_(false) {
31 conn_.startTransaction();
32}
33
35 // Rollback if the MySqlTransaction::commit wasn't explicitly
36 // called.
37 if (!committed_) {
38 conn_.rollback();
39 }
40}
41
42void
44 conn_.commit();
45 committed_ = true;
46}
47
48
49// Open the database using the parameters passed to the constructor.
50
51void
53
54 // Set up the values of the parameters
55 const char* host = "localhost";
56 string shost;
57 try {
58 shost = getParameter("host");
59 host = shost.c_str();
60 } catch (...) {
61 // No host. Fine, we'll use "localhost"
62 }
63
64 unsigned int port = 0;
65 string sport;
66 try {
67 sport = getParameter("port");
68 } catch (...) {
69 // No port parameter, we are going to use the default port.
70 sport = "";
71 }
72
73 if (sport.size() > 0) {
74 // Port was given, so try to convert it to an integer.
75
76 try {
77 port = boost::lexical_cast<unsigned int>(sport);
78 } catch (...) {
79 // Port given but could not be converted to an unsigned int.
80 // Just fall back to the default value.
81 port = 0;
82 }
83
84 // The port is only valid when it is in the 0..65535 range.
85 // Again fall back to the default when the given value is invalid.
86 if (port > numeric_limits<uint16_t>::max()) {
87 port = 0;
88 }
89 }
90
91 const char* user = NULL;
92 string suser;
93 try {
94 suser = getParameter("user");
95 user = suser.c_str();
96 } catch (...) {
97 // No user. Fine, we'll use NULL
98 }
99
100 const char* password = NULL;
101 string spassword;
102 try {
103 spassword = getParameter("password");
104 password = spassword.c_str();
105 } catch (...) {
106 // No password. Fine, we'll use NULL
107 }
108
109 const char* name = NULL;
110 string sname;
111 try {
112 sname = getParameter("name");
113 name = sname.c_str();
114 } catch (...) {
115 // No database name. Throw a "NoName" exception
116 isc_throw(NoDatabaseName, "must specify a name for the database");
117 }
118
119 unsigned int connect_timeout = MYSQL_DEFAULT_CONNECTION_TIMEOUT;
120 string stimeout;
121 try {
122 stimeout = getParameter("connect-timeout");
123 } catch (...) {
124 // No timeout parameter, we are going to use the default timeout.
125 stimeout = "";
126 }
127
128 if (stimeout.size() > 0) {
129 // Timeout was given, so try to convert it to an integer.
130
131 try {
132 connect_timeout = boost::lexical_cast<unsigned int>(stimeout);
133 } catch (...) {
134 // Timeout given but could not be converted to an unsigned int. Set
135 // the connection timeout to an invalid value to trigger throwing
136 // of an exception.
137 connect_timeout = 0;
138 }
139
140 // The timeout is only valid if greater than zero, as depending on the
141 // database, a zero timeout might signify something like "wait
142 // indefinitely".
143 //
144 // The check below also rejects a value greater than the maximum
145 // integer value. The lexical_cast operation used to obtain a numeric
146 // value from a string can get confused if trying to convert a negative
147 // integer to an unsigned int: instead of throwing an exception, it may
148 // produce a large positive value.
149 if ((connect_timeout == 0) ||
150 (connect_timeout > numeric_limits<int>::max())) {
151 isc_throw(DbInvalidTimeout, "database connection timeout (" <<
152 stimeout << ") must be an integer greater than 0");
153 }
154 }
155
156 // Set options for the connection:
157 //
158 // Set options for the connection:
159 // Make sure auto_reconnect is OFF! Enabling it leaves us with an unusable
160 // connection after a reconnect as among other things, it drops all our
161 // pre-compiled statements.
162 my_bool auto_reconnect = MLM_FALSE;
163 int result = mysql_options(mysql_, MYSQL_OPT_RECONNECT, &auto_reconnect);
164 if (result != 0) {
165 isc_throw(DbOpenError, "unable to set auto-reconnect option: " <<
166 mysql_error(mysql_));
167 }
168
169 // Make sure we have a large idle time window ... say 30 days...
170 const char *wait_time = "SET SESSION wait_timeout = 30 * 86400";
171 result = mysql_options(mysql_, MYSQL_INIT_COMMAND, wait_time);
172 if (result != 0) {
173 isc_throw(DbOpenError, "unable to set wait_timeout " <<
174 mysql_error(mysql_));
175 }
176
177 // Set SQL mode options for the connection: SQL mode governs how what
178 // constitutes insertable data for a given column, and how to handle
179 // invalid data. We want to ensure we get the strictest behavior and
180 // to reject invalid data with an error.
181 const char *sql_mode = "SET SESSION sql_mode ='STRICT_ALL_TABLES'";
182 result = mysql_options(mysql_, MYSQL_INIT_COMMAND, sql_mode);
183 if (result != 0) {
184 isc_throw(DbOpenError, "unable to set SQL mode options: " <<
185 mysql_error(mysql_));
186 }
187
188 // Connection timeout, the amount of time taken for the client to drop
189 // the connection if the server is not responding.
190 result = mysql_options(mysql_, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
191 if (result != 0) {
192 isc_throw(DbOpenError, "unable to set database connection timeout: " <<
193 mysql_error(mysql_));
194 }
195
196 // Open the database.
197 //
198 // The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
199 // the affected rows are the number of rows found that match the
200 // WHERE clause of the SQL statement, not the rows changed. The reason
201 // here is that MySQL apparently does not update a row if data has not
202 // changed and so the "affected rows" (retrievable from MySQL) is zero.
203 // This makes it hard to distinguish whether the UPDATE changed no rows
204 // because no row matching the WHERE clause was found, or because a
205 // row was found but no data was altered.
206 MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
207 port, NULL, CLIENT_FOUND_ROWS);
208 if (status != mysql_) {
209 isc_throw(DbOpenError, mysql_error(mysql_));
210 }
211}
212
213
214// Prepared statement setup. The textual form of an SQL statement is stored
215// in a vector of strings (text_statements_) and is used in the output of
216// error messages. The SQL statement is also compiled into a "prepared
217// statement" (stored in statements_), which avoids the overhead of compilation
218// during use. As prepared statements have resources allocated to them, the
219// class destructor explicitly destroys them.
220
221void
222MySqlConnection::prepareStatement(uint32_t index, const char* text) {
223 // Validate that there is space for the statement in the statements array
224 // and that nothing has been placed there before.
225 if ((index >= statements_.size()) || (statements_[index] != NULL)) {
226 isc_throw(InvalidParameter, "invalid prepared statement index (" <<
227 static_cast<int>(index) << ") or indexed prepared " <<
228 "statement is not null");
229 }
230
231 // All OK, so prepare the statement
232 text_statements_[index] = std::string(text);
233 statements_[index] = mysql_stmt_init(mysql_);
234 if (statements_[index] == NULL) {
235 isc_throw(DbOperationError, "unable to allocate MySQL prepared "
236 "statement structure, reason: " << mysql_error(mysql_));
237 }
238
239 int status = mysql_stmt_prepare(statements_[index], text, strlen(text));
240 if (status != 0) {
241 isc_throw(DbOperationError, "unable to prepare MySQL statement <" <<
242 text << ">, reason: " << mysql_error(mysql_));
243 }
244}
245
246void
248 const TaggedStatement* end_statement) {
249 // Created the MySQL prepared statements for each DML statement.
250 for (const TaggedStatement* tagged_statement = start_statement;
251 tagged_statement != end_statement; ++tagged_statement) {
252 if (tagged_statement->index >= statements_.size()) {
253 statements_.resize(tagged_statement->index + 1, NULL);
254 text_statements_.resize(tagged_statement->index + 1,
255 std::string(""));
256 }
257 prepareStatement(tagged_statement->index,
258 tagged_statement->text);
259 }
260}
261
263 statements_.clear();
264 text_statements_.clear();
265}
266
269 // Free up the prepared statements, ignoring errors. (What would we do
270 // about them? We're destroying this object and are not really concerned
271 // with errors on a database connection that is about to go away.)
272 for (int i = 0; i < statements_.size(); ++i) {
273 if (statements_[i] != NULL) {
274 (void) mysql_stmt_close(statements_[i]);
275 statements_[i] = NULL;
276 }
277 }
278 statements_.clear();
279 text_statements_.clear();
280}
281
282// Time conversion methods.
283//
284// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
285// from the current timezone to UTC for storage, and from UTC to the current
286// timezone for retrieval.
287//
288// This causes no problems providing that:
289// a) cltt is given in local time
290// b) We let the system take care of timezone conversion when converting
291// from a time read from the database into a local time.
292void
294 MYSQL_TIME& output_time) {
295 MySqlBinding::convertToDatabaseTime(input_time, output_time);
296}
297
298void
300 const uint32_t valid_lifetime,
301 MYSQL_TIME& expire) {
302 MySqlBinding::convertToDatabaseTime(cltt, valid_lifetime, expire);
303}
304
305void
307 uint32_t valid_lifetime, time_t& cltt) {
308 MySqlBinding::convertFromDatabaseTime(expire, valid_lifetime, cltt);
309}
310
311void
314 // We create prepared statements for all other queries, but MySQL
315 // don't support prepared statements for START TRANSACTION.
316 int status = mysql_query(mysql_, "START TRANSACTION");
317 if (status != 0) {
318 isc_throw(DbOperationError, "unable to start transaction, "
319 "reason: " << mysql_error(mysql_));
320 }
321}
322
323void
326 if (mysql_commit(mysql_) != 0) {
327 isc_throw(DbOperationError, "commit failed: "
328 << mysql_error(mysql_));
329 }
330}
331
332void
335 if (mysql_rollback(mysql_) != 0) {
336 isc_throw(DbOperationError, "rollback failed: "
337 << mysql_error(mysql_));
338 }
339}
340
341
342} // namespace isc::db
343} // namespace isc
A generic exception that is thrown if a parameter given to a method or function is considered invalid...
std::string getParameter(const std::string &name) const
Returns value of a connection parameter.
Exception thrown on failure to open database.
Exception thrown on failure to execute a database function.
static void convertFromDatabaseTime(const MYSQL_TIME &expire, uint32_t valid_lifetime, time_t &cltt)
Converts Database Time to Lease Times.
static void convertToDatabaseTime(const time_t input_time, MYSQL_TIME &output_time)
Converts time_t value to database time.
Common MySQL Connector Pool.
MySqlHolder mysql_
MySQL connection handle.
void prepareStatement(uint32_t index, const char *text)
Prepare Single Statement.
std::vector< MYSQL_STMT * > statements_
Prepared statements.
std::vector< std::string > text_statements_
Raw text of statements.
static void convertToDatabaseTime(const time_t input_time, MYSQL_TIME &output_time)
Convert time_t value to database time.
static void convertFromDatabaseTime(const MYSQL_TIME &expire, uint32_t valid_lifetime, time_t &cltt)
Convert Database Time to Lease Times.
void commit()
Commit Transactions.
void openDatabase()
Open Database.
void prepareStatements(const TaggedStatement *start_statement, const TaggedStatement *end_statement)
Prepare statements.
void startTransaction()
Starts Transaction.
virtual ~MySqlConnection()
Destructor.
void clearStatements()
Clears prepared statements and text statements.
void rollback()
Rollback Transactions.
void commit()
Commits transaction.
MySqlTransaction(MySqlConnection &conn)
Constructor.
Exception thrown if name of database is not specified.
We want to reuse the database backend connection and exchange code for other uses,...
#define DB_LOG_DEBUG(LEVEL, MESSAGE)
Macros.
Definition: db_log.h:115
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
const int DB_DBG_TRACE_DETAIL
Database logging levels.
Definition: db_log.h:40
const my_bool MLM_FALSE
MySQL false value.
const int MYSQL_DEFAULT_CONNECTION_TIMEOUT
@ MYSQL_START_TRANSACTION
Definition: db_log.h:61
@ MYSQL_ROLLBACK
Definition: db_log.h:63
@ MYSQL_COMMIT
Definition: db_log.h:62
Defines the logger used by the top-level component of kea-dhcp-ddns.
MySQL Selection Statements.