File: | home/fedora/workspace/kea-dev/clang-static-analyzer/src/lib/mysql/mysql_connection.cc |
Warning: | line 65, column 17 Value stored to 'host' during its initialization is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | // Copyright (C) 2012-2024 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 <asiolink/io_service.h> |
10 | #include <asiolink/process_spawn.h> |
11 | #include <database/database_connection.h> |
12 | #include <database/db_log.h> |
13 | #include <exceptions/exceptions.h> |
14 | #include <mysql/mysql_connection.h> |
15 | #include <util/filesystem.h> |
16 | |
17 | #include <boost/lexical_cast.hpp> |
18 | |
19 | #include <cstdint> |
20 | #include <exception> |
21 | #include <limits> |
22 | #include <string> |
23 | #include <unordered_map> |
24 | |
25 | using namespace isc; |
26 | using namespace isc::asiolink; |
27 | using namespace std; |
28 | |
29 | namespace isc { |
30 | namespace db { |
31 | |
32 | std::string MySqlConnection::KEA_ADMIN_ = KEA_ADMIN"/home/fedora/workspace/kea-dev/clang-static-analyzer/kea-bin/sbin/kea-admin"; |
33 | |
34 | int MySqlHolder::atexit_ = [] { |
35 | return atexit([] { mysql_library_endmysql_server_end(); }); |
36 | }(); |
37 | |
38 | /// @todo: Migrate this default value to src/bin/dhcpX/simple_parserX.cc |
39 | const int MYSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds |
40 | |
41 | MySqlTransaction::MySqlTransaction(MySqlConnection& conn) |
42 | : conn_(conn), committed_(false) { |
43 | conn_.startTransaction(); |
44 | } |
45 | |
46 | MySqlTransaction::~MySqlTransaction() { |
47 | // Rollback if the MySqlTransaction::commit wasn't explicitly |
48 | // called. |
49 | if (!committed_) { |
50 | conn_.rollback(); |
51 | } |
52 | } |
53 | |
54 | void |
55 | MySqlTransaction::commit() { |
56 | conn_.commit(); |
57 | committed_ = true; |
58 | } |
59 | |
60 | // Open the database using the parameters passed to the constructor. |
61 | |
62 | void |
63 | MySqlConnection::openDatabase() { |
64 | // Set up the values of the parameters |
65 | const char* host = "localhost"; |
Value stored to 'host' during its initialization is never read | |
66 | string shost; |
67 | try { |
68 | shost = getParameter("host"); |
69 | host = shost.c_str(); |
70 | } catch (...) { |
71 | // No host. Fine, we'll use "localhost" |
72 | } |
73 | |
74 | unsigned int port = 0; |
75 | try { |
76 | setIntParameterValue("port", 0, numeric_limits<uint16_t>::max(), port); |
77 | |
78 | } catch (const std::exception& ex) { |
79 | isc_throw(DbInvalidPort, ex.what())do { std::ostringstream oss__; oss__ << ex.what(); throw DbInvalidPort("mysql_connection.cc", 79, oss__.str().c_str() ); } while (1); |
80 | } |
81 | |
82 | const char* user = NULL__null; |
83 | string suser; |
84 | try { |
85 | suser = getParameter("user"); |
86 | user = suser.c_str(); |
87 | } catch (...) { |
88 | // No user. Fine, we'll use NULL |
89 | } |
90 | |
91 | const char* password = NULL__null; |
92 | string spassword; |
93 | try { |
94 | spassword = getParameter("password"); |
95 | password = spassword.c_str(); |
96 | } catch (...) { |
97 | // No password. Fine, we'll use NULL |
98 | } |
99 | |
100 | const char* name = NULL__null; |
101 | string sname; |
102 | try { |
103 | sname = getParameter("name"); |
104 | name = sname.c_str(); |
105 | } catch (...) { |
106 | // No database name. Throw a "NoName" exception |
107 | isc_throw(NoDatabaseName, "must specify a name for the database")do { std::ostringstream oss__; oss__ << "must specify a name for the database" ; throw NoDatabaseName("mysql_connection.cc", 107, oss__.str( ).c_str()); } while (1); |
108 | } |
109 | |
110 | unsigned int connect_timeout = MYSQL_DEFAULT_CONNECTION_TIMEOUT; |
111 | unsigned int read_timeout = 0; |
112 | unsigned int write_timeout = 0; |
113 | try { |
114 | // The timeout is only valid if greater than zero, as depending on the |
115 | // database, a zero timeout might signify something like "wait |
116 | // indefinitely". |
117 | setIntParameterValue("connect-timeout", 1, numeric_limits<int>::max(), connect_timeout); |
118 | // Other timeouts can be 0, meaning that the database client will follow a default |
119 | // behavior. Earlier MySQL versions didn't have these parameters, so we allow 0 |
120 | // to skip setting them. |
121 | setIntParameterValue("read-timeout", 0, numeric_limits<int>::max(), read_timeout); |
122 | setIntParameterValue("write-timeout", 0, numeric_limits<int>::max(), write_timeout); |
123 | |
124 | } catch (const std::exception& ex) { |
125 | isc_throw(DbInvalidTimeout, ex.what())do { std::ostringstream oss__; oss__ << ex.what(); throw DbInvalidTimeout("mysql_connection.cc", 125, oss__.str().c_str ()); } while (1); |
126 | } |
127 | |
128 | const char* ca_file(0); |
129 | const char* ca_dir(0); |
130 | string sca; |
131 | try { |
132 | sca = getParameter("trust-anchor"); |
133 | tls_ = true; |
134 | if (util::file::isDir(sca)) { |
135 | ca_dir = sca.c_str(); |
136 | } else { |
137 | ca_file = sca.c_str(); |
138 | } |
139 | } catch (...) { |
140 | // No trust anchor |
141 | } |
142 | |
143 | const char* cert_file(0); |
144 | string scert; |
145 | try { |
146 | scert = getParameter("cert-file"); |
147 | tls_ = true; |
148 | cert_file = scert.c_str(); |
149 | } catch (...) { |
150 | // No client certificate file |
151 | } |
152 | |
153 | const char* key_file(0); |
154 | string skey; |
155 | try { |
156 | skey = getParameter("key-file"); |
157 | tls_ = true; |
158 | key_file = skey.c_str(); |
159 | } catch (...) { |
160 | // No private key file |
161 | } |
162 | |
163 | const char* cipher_list(0); |
164 | string scipher; |
165 | try { |
166 | scipher = getParameter("cipher-list"); |
167 | tls_ = true; |
168 | cipher_list = scipher.c_str(); |
169 | } catch (...) { |
170 | // No cipher list |
171 | } |
172 | |
173 | // Set options for the connection: |
174 | // |
175 | int result; |
176 | #ifdef HAS_MYSQL_OPT_RECONNECT |
177 | // Though still supported by Mariadb (as of 11.5.0), MYSQL_OPT_RECONNECT is |
178 | // deprecated as of MySQL 8.0.34. Where it is still supported we should |
179 | // continue to ensure it is off. Enabling it leaves us with an unusable |
180 | // connection after a reconnect as among other things, it drops all our |
181 | // pre-compiled statements. |
182 | my_bool auto_reconnect = MLM_FALSE; |
183 | result = mysql_options(mysql_, MYSQL_OPT_RECONNECT, &auto_reconnect); |
184 | if (result != 0) { |
185 | isc_throw(DbOpenError, "unable to set auto-reconnect option: " <<do { std::ostringstream oss__; oss__ << "unable to set auto-reconnect option: " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 186, oss__.str().c_str()); } while (1) |
186 | mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set auto-reconnect option: " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 186, oss__.str().c_str()); } while (1); |
187 | } |
188 | #endif |
189 | |
190 | // Make sure we have a large idle time window ... say 30 days... |
191 | const char *wait_time = "SET SESSION wait_timeout = 30 * 86400"; |
192 | result = mysql_options(mysql_, MYSQL_INIT_COMMAND, wait_time); |
193 | if (result != 0) { |
194 | isc_throw(DbOpenError, "unable to set wait_timeout " <<do { std::ostringstream oss__; oss__ << "unable to set wait_timeout " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 195, oss__.str().c_str()); } while (1) |
195 | mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set wait_timeout " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 195, oss__.str().c_str()); } while (1); |
196 | } |
197 | |
198 | // Set SQL mode options for the connection: SQL mode governs how what |
199 | // constitutes insertable data for a given column, and how to handle |
200 | // invalid data. We want to ensure we get the strictest behavior and |
201 | // to reject invalid data with an error. |
202 | const char *sql_mode = "SET SESSION sql_mode ='STRICT_ALL_TABLES'"; |
203 | result = mysql_options(mysql_, MYSQL_INIT_COMMAND, sql_mode); |
204 | if (result != 0) { |
205 | isc_throw(DbOpenError, "unable to set SQL mode options: " <<do { std::ostringstream oss__; oss__ << "unable to set SQL mode options: " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 206, oss__.str().c_str()); } while (1) |
206 | mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set SQL mode options: " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 206, oss__.str().c_str()); } while (1); |
207 | } |
208 | |
209 | // Connection timeout, the amount of time taken for the client to drop |
210 | // the connection if the server is not responding. |
211 | result = mysql_options(mysql_, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout); |
212 | if (result != 0) { |
213 | isc_throw(DbOpenError, "unable to set database connection timeout: " <<do { std::ostringstream oss__; oss__ << "unable to set database connection timeout: " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 214, oss__.str().c_str()); } while (1) |
214 | mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set database connection timeout: " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 214, oss__.str().c_str()); } while (1); |
215 | } |
216 | |
217 | // Set the read timeout if it has been specified. Otherwise, the timeout is |
218 | // not used. |
219 | if (read_timeout > 0) { |
220 | result = mysql_options(mysql_, MYSQL_OPT_READ_TIMEOUT, &read_timeout); |
221 | if (result != 0) { |
222 | isc_throw(DbOpenError, "unable to set database read timeout: " <<do { std::ostringstream oss__; oss__ << "unable to set database read timeout: " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 223, oss__.str().c_str()); } while (1) |
223 | mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set database read timeout: " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 223, oss__.str().c_str()); } while (1); |
224 | } |
225 | } |
226 | |
227 | // Set the write timeout if it has been specified. Otherwise, the timeout |
228 | // is not used. |
229 | if (write_timeout > 0) { |
230 | result = mysql_options(mysql_, MYSQL_OPT_WRITE_TIMEOUT, &write_timeout); |
231 | if (result != 0) { |
232 | isc_throw(DbOpenError, "unable to set database write timeout: " <<do { std::ostringstream oss__; oss__ << "unable to set database write timeout: " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 233, oss__.str().c_str()); } while (1) |
233 | mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set database write timeout: " << mysql_error(mysql_); throw DbOpenError("mysql_connection.cc" , 233, oss__.str().c_str()); } while (1); |
234 | } |
235 | } |
236 | |
237 | // If TLS is enabled set it. If something should go wrong it will happen |
238 | // later at the mysql_real_connect call. |
239 | if (tls_) { |
240 | mysql_ssl_set(mysql_, key_file, cert_file, ca_file, ca_dir, |
241 | cipher_list); |
242 | } |
243 | |
244 | // Open the database. |
245 | // |
246 | // The option CLIENT_FOUND_ROWS is specified so that in an UPDATE, |
247 | // the affected rows are the number of rows found that match the |
248 | // WHERE clause of the SQL statement, not the rows changed. The reason |
249 | // here is that MySQL apparently does not update a row if data has not |
250 | // changed and so the "affected rows" (retrievable from MySQL) is zero. |
251 | // This makes it hard to distinguish whether the UPDATE changed no rows |
252 | // because no row matching the WHERE clause was found, or because a |
253 | // row was found but no data was altered. |
254 | MYSQL* status = mysql_real_connect(mysql_, host, user, password, name, |
255 | port, NULL__null, CLIENT_FOUND_ROWS2); |
256 | if (status != mysql_) { |
257 | std::string error_message = mysql_error(mysql_); |
258 | |
259 | auto const& rec = reconnectCtl(); |
260 | if (rec && DatabaseConnection::retry_) { |
261 | // Start the connection recovery. |
262 | startRecoverDbConnection(); |
263 | |
264 | std::ostringstream s; |
265 | |
266 | s << " (scheduling retry " << rec->retryIndex() + 1 << " of " << rec->maxRetries() << " in " << rec->retryInterval() << " milliseconds)"; |
267 | |
268 | error_message += s.str(); |
269 | |
270 | isc_throw(DbOpenErrorWithRetry, error_message)do { std::ostringstream oss__; oss__ << error_message; throw DbOpenErrorWithRetry("mysql_connection.cc", 270, oss__.str() .c_str()); } while (1); |
271 | } |
272 | |
273 | isc_throw(DbOpenError, error_message)do { std::ostringstream oss__; oss__ << error_message; throw DbOpenError("mysql_connection.cc", 273, oss__.str().c_str()) ; } while (1); |
274 | } |
275 | |
276 | // Enable autocommit. In case transaction is explicitly used, this |
277 | // setting will be overwritten for the transaction. However, there are |
278 | // cases when lack of autocommit could cause transactions to hang |
279 | // until commit or rollback is explicitly called. This already |
280 | // caused issues for some unit tests which were unable to cleanup |
281 | // the database after the test because of pending transactions. |
282 | // Use of autocommit will eliminate this problem. |
283 | my_bool autocommit_result = mysql_autocommit(mysql_, 1); |
284 | if (autocommit_result != 0) { |
285 | isc_throw(DbOperationError, mysql_error(mysql_))do { std::ostringstream oss__; oss__ << mysql_error(mysql_ ); throw DbOperationError("mysql_connection.cc", 285, oss__.str ().c_str()); } while (1); |
286 | } |
287 | |
288 | // To avoid a flush to disk on every commit, the global parameter |
289 | // innodb_flush_log_at_trx_commit should be set to 2. This will cause the |
290 | // changes to be written to the log, but flushed to disk in the background |
291 | // every second. Setting the parameter to that value will speed up the |
292 | // system, but at the risk of losing data if the system crashes. |
293 | } |
294 | |
295 | // Get schema version. |
296 | |
297 | std::pair<uint32_t, uint32_t> |
298 | MySqlConnection::getVersion(const ParameterMap& parameters, |
299 | const IOServiceAccessorPtr& ac, |
300 | const DbCallback& cb, |
301 | const string& timer_name) { |
302 | // Get a connection. |
303 | MySqlConnection conn(parameters, ac, cb); |
304 | |
305 | if (!timer_name.empty()) { |
306 | conn.makeReconnectCtl(timer_name); |
307 | } |
308 | |
309 | // Open the database. |
310 | conn.openDatabase(); |
311 | |
312 | // Allocate a new statement. |
313 | MYSQL_STMT *stmt = mysql_stmt_init(conn.mysql_); |
314 | if (stmt == NULL__null) { |
315 | isc_throw(DbOperationError, "unable to allocate MySQL prepared "do { std::ostringstream oss__; oss__ << "unable to allocate MySQL prepared " "statement structure, reason: " << mysql_error(conn.mysql_ ); throw DbOperationError("mysql_connection.cc", 316, oss__.str ().c_str()); } while (1) |
316 | "statement structure, reason: " << mysql_error(conn.mysql_))do { std::ostringstream oss__; oss__ << "unable to allocate MySQL prepared " "statement structure, reason: " << mysql_error(conn.mysql_ ); throw DbOperationError("mysql_connection.cc", 316, oss__.str ().c_str()); } while (1); |
317 | } |
318 | |
319 | try { |
320 | |
321 | // Prepare the statement from SQL text. |
322 | const char* version_sql = "SELECT version, minor FROM schema_version"; |
323 | int status = mysql_stmt_prepare(stmt, version_sql, strlen(version_sql)); |
324 | if (status != 0) { |
325 | isc_throw(DbOperationError, "unable to prepare MySQL statement <"do { std::ostringstream oss__; oss__ << "unable to prepare MySQL statement <" << version_sql << ">, reason: " << mysql_error (conn.mysql_); throw DbOperationError("mysql_connection.cc", 327 , oss__.str().c_str()); } while (1) |
326 | << version_sql << ">, reason: "do { std::ostringstream oss__; oss__ << "unable to prepare MySQL statement <" << version_sql << ">, reason: " << mysql_error (conn.mysql_); throw DbOperationError("mysql_connection.cc", 327 , oss__.str().c_str()); } while (1) |
327 | << mysql_error(conn.mysql_))do { std::ostringstream oss__; oss__ << "unable to prepare MySQL statement <" << version_sql << ">, reason: " << mysql_error (conn.mysql_); throw DbOperationError("mysql_connection.cc", 327 , oss__.str().c_str()); } while (1); |
328 | } |
329 | |
330 | // Execute the prepared statement. |
331 | if (MysqlExecuteStatement(stmt) != 0) { |
332 | isc_throw(DbOperationError, "cannot execute schema version query <"do { std::ostringstream oss__; oss__ << "cannot execute schema version query <" << version_sql << ">, reason: " << mysql_errno (conn.mysql_); throw DbOperationError("mysql_connection.cc", 334 , oss__.str().c_str()); } while (1) |
333 | << version_sql << ">, reason: "do { std::ostringstream oss__; oss__ << "cannot execute schema version query <" << version_sql << ">, reason: " << mysql_errno (conn.mysql_); throw DbOperationError("mysql_connection.cc", 334 , oss__.str().c_str()); } while (1) |
334 | << mysql_errno(conn.mysql_))do { std::ostringstream oss__; oss__ << "cannot execute schema version query <" << version_sql << ">, reason: " << mysql_errno (conn.mysql_); throw DbOperationError("mysql_connection.cc", 334 , oss__.str().c_str()); } while (1); |
335 | } |
336 | |
337 | // Bind the output of the statement to the appropriate variables. |
338 | MYSQL_BIND bind[2]; |
339 | memset(bind, 0, sizeof(bind)); |
340 | |
341 | uint32_t version; |
342 | bind[0].buffer_type = MYSQL_TYPE_LONG; |
343 | bind[0].is_unsigned = 1; |
344 | bind[0].buffer = &version; |
345 | bind[0].buffer_length = sizeof(version); |
346 | |
347 | uint32_t minor; |
348 | bind[1].buffer_type = MYSQL_TYPE_LONG; |
349 | bind[1].is_unsigned = 1; |
350 | bind[1].buffer = &minor; |
351 | bind[1].buffer_length = sizeof(minor); |
352 | |
353 | if (mysql_stmt_bind_result(stmt, bind)) { |
354 | isc_throw(DbOperationError, "unable to bind result set for <"do { std::ostringstream oss__; oss__ << "unable to bind result set for <" << version_sql << ">, reason: " << mysql_errno (conn.mysql_); throw DbOperationError("mysql_connection.cc", 356 , oss__.str().c_str()); } while (1) |
355 | << version_sql << ">, reason: "do { std::ostringstream oss__; oss__ << "unable to bind result set for <" << version_sql << ">, reason: " << mysql_errno (conn.mysql_); throw DbOperationError("mysql_connection.cc", 356 , oss__.str().c_str()); } while (1) |
356 | << mysql_errno(conn.mysql_))do { std::ostringstream oss__; oss__ << "unable to bind result set for <" << version_sql << ">, reason: " << mysql_errno (conn.mysql_); throw DbOperationError("mysql_connection.cc", 356 , oss__.str().c_str()); } while (1); |
357 | } |
358 | |
359 | // Fetch the data. |
360 | if (mysql_stmt_fetch(stmt)) { |
361 | isc_throw(DbOperationError, "unable to bind result set for <"do { std::ostringstream oss__; oss__ << "unable to bind result set for <" << version_sql << ">, reason: " << mysql_errno (conn.mysql_); throw DbOperationError("mysql_connection.cc", 363 , oss__.str().c_str()); } while (1) |
362 | << version_sql << ">, reason: "do { std::ostringstream oss__; oss__ << "unable to bind result set for <" << version_sql << ">, reason: " << mysql_errno (conn.mysql_); throw DbOperationError("mysql_connection.cc", 363 , oss__.str().c_str()); } while (1) |
363 | << mysql_errno(conn.mysql_))do { std::ostringstream oss__; oss__ << "unable to bind result set for <" << version_sql << ">, reason: " << mysql_errno (conn.mysql_); throw DbOperationError("mysql_connection.cc", 363 , oss__.str().c_str()); } while (1); |
364 | } |
365 | |
366 | // Discard the statement and its resources |
367 | mysql_stmt_close(stmt); |
368 | return (std::make_pair(version, minor)); |
369 | |
370 | } catch (const std::exception&) { |
371 | // Avoid a memory leak on error. |
372 | mysql_stmt_close(stmt); |
373 | |
374 | // Send the exception to the caller. |
375 | throw; |
376 | } |
377 | } |
378 | |
379 | void |
380 | MySqlConnection::ensureSchemaVersion(const ParameterMap& parameters, |
381 | const DbCallback& cb, |
382 | const string& timer_name) { |
383 | // retry-on-startup? |
384 | bool const retry(parameters.count("retry-on-startup") && |
385 | parameters.at("retry-on-startup") == "true"); |
386 | |
387 | IOServiceAccessorPtr ac(new IOServiceAccessor(&DatabaseConnection::getIOService)); |
388 | pair<uint32_t, uint32_t> schema_version; |
389 | try { |
390 | schema_version = getVersion(parameters, ac, cb, retry ? timer_name : string()); |
391 | } catch (DbOpenError const& exception) { |
392 | throw; |
393 | } catch (DbOpenErrorWithRetry const& exception) { |
394 | throw; |
395 | } catch (exception const& exception) { |
396 | // This failure may occur for a variety of reasons. We are looking at |
397 | // initializing schema as the only potential mitigation. We could narrow |
398 | // down on the error that would suggest an uninitialized schema |
399 | // which would sound something along the lines of |
400 | // "table schema_version does not exist", but we do not necessarily have |
401 | // to. If the error had another cause, it will fail again during |
402 | // initialization or during the subsequent version retrieval and that is |
403 | // fine, and the error should still be relevant. |
404 | initializeSchema(parameters); |
405 | |
406 | // Retrieve again because the initial retrieval failed. |
407 | schema_version = getVersion(parameters, ac, cb, retry ? timer_name : string()); |
408 | } |
409 | |
410 | // Check that the versions match. |
411 | pair<uint32_t, uint32_t> const expected_version(MYSQL_SCHEMA_VERSION_MAJOR, |
412 | MYSQL_SCHEMA_VERSION_MINOR); |
413 | if (schema_version != expected_version) { |
414 | isc_throw(DbOpenError, "MySQL schema version mismatch: expected version: "do { std::ostringstream oss__; oss__ << "MySQL schema version mismatch: expected version: " << expected_version.first << "." << expected_version .second << ", found version: " << schema_version. first << "." << schema_version.second; throw DbOpenError ("mysql_connection.cc", 417, oss__.str().c_str()); } while (1 ) |
415 | << expected_version.first << "." << expected_version.seconddo { std::ostringstream oss__; oss__ << "MySQL schema version mismatch: expected version: " << expected_version.first << "." << expected_version .second << ", found version: " << schema_version. first << "." << schema_version.second; throw DbOpenError ("mysql_connection.cc", 417, oss__.str().c_str()); } while (1 ) |
416 | << ", found version: " << schema_version.first << "."do { std::ostringstream oss__; oss__ << "MySQL schema version mismatch: expected version: " << expected_version.first << "." << expected_version .second << ", found version: " << schema_version. first << "." << schema_version.second; throw DbOpenError ("mysql_connection.cc", 417, oss__.str().c_str()); } while (1 ) |
417 | << schema_version.second)do { std::ostringstream oss__; oss__ << "MySQL schema version mismatch: expected version: " << expected_version.first << "." << expected_version .second << ", found version: " << schema_version. first << "." << schema_version.second; throw DbOpenError ("mysql_connection.cc", 417, oss__.str().c_str()); } while (1 ); |
418 | } |
419 | } |
420 | |
421 | void |
422 | MySqlConnection::initializeSchema(const ParameterMap& parameters) { |
423 | if (parameters.count("readonly") && parameters.at("readonly") == "true") { |
424 | // The readonly flag is historically used for host backends. Still, if |
425 | // enabled, it is a strong indication that we should not meDDLe with it. |
426 | return; |
427 | } |
428 | |
429 | if (!isc::util::file::isFile(KEA_ADMIN_)) { |
430 | // It can happen for kea-admin to not exist, especially with |
431 | // packages that install it in a separate package. |
432 | return; |
433 | } |
434 | |
435 | // Convert parameters. |
436 | vector<string> kea_admin_parameters(toKeaAdminParameters(parameters)); |
437 | ProcessEnvVars const vars; |
438 | kea_admin_parameters.insert(kea_admin_parameters.begin(), "db-init"); |
439 | |
440 | // Run. |
441 | ProcessSpawn kea_admin(ProcessSpawn::SYNC, KEA_ADMIN_, kea_admin_parameters, vars, |
442 | /* inherit_env = */ true); |
443 | DB_LOG_INFO(MYSQL_INITIALIZE_SCHEMA).arg(kea_admin.getCommandLine()); |
444 | pid_t const pid(kea_admin.spawn()); |
445 | if (kea_admin.isRunning(pid)) { |
446 | isc_throw(SchemaInitializationFailed, "kea-admin still running")do { std::ostringstream oss__; oss__ << "kea-admin still running" ; throw SchemaInitializationFailed("mysql_connection.cc", 446 , oss__.str().c_str()); } while (1); |
447 | } |
448 | int const exit_code(kea_admin.getExitStatus(pid)); |
449 | if (exit_code != 0) { |
450 | isc_throw(SchemaInitializationFailed, "Expected exit code 0 for kea-admin. Got " << exit_code)do { std::ostringstream oss__; oss__ << "Expected exit code 0 for kea-admin. Got " << exit_code; throw SchemaInitializationFailed("mysql_connection.cc" , 450, oss__.str().c_str()); } while (1); |
451 | } |
452 | } |
453 | |
454 | vector<string> |
455 | MySqlConnection::toKeaAdminParameters(ParameterMap const& params) { |
456 | vector<string> result{"mysql"}; |
457 | for (auto const& p : params) { |
458 | string const& keyword(p.first); |
459 | string const& value(p.second); |
460 | |
461 | // These Kea parameters are the same as the kea-admin parameters. |
462 | if (keyword == "user" || |
463 | keyword == "password" || |
464 | keyword == "host" || |
465 | keyword == "port" || |
466 | keyword == "name") { |
467 | result.push_back("--" + keyword); |
468 | result.push_back(value); |
469 | continue; |
470 | } |
471 | |
472 | // These Kea parameters do not have a direct kea-admin equivalent. |
473 | // But they do have a mariadb client flag equivalent. |
474 | // We pass them to kea-admin using the --extra flag. |
475 | static unordered_map<string, string> conversions{ |
476 | {"connect-timeout", "connect_timeout"}, |
477 | {"cipher-list", "ssl-cipher"}, |
478 | {"cert-file", "ssl-cert"}, |
479 | {"key-file", "ssl-key"}, |
480 | {"trust-anchor", "ssl-ca"}, |
481 | // {"read-timeout", "--net-read-timeout"}, // available in docs, but client says unknown variable? |
482 | // {"write-timeout", "--net-write-timeout"}, // available in docs, but client says unknown variable? |
483 | }; |
484 | if (conversions.count(keyword)) { |
485 | result.push_back("--extra"); |
486 | result.push_back("--" + conversions.at(keyword) + " " + value); |
487 | } |
488 | } |
489 | return result; |
490 | } |
491 | |
492 | // Prepared statement setup. The textual form of an SQL statement is stored |
493 | // in a vector of strings (text_statements_) and is used in the output of |
494 | // error messages. The SQL statement is also compiled into a "prepared |
495 | // statement" (stored in statements_), which avoids the overhead of compilation |
496 | // during use. As prepared statements have resources allocated to them, the |
497 | // class destructor explicitly destroys them. |
498 | |
499 | void |
500 | MySqlConnection::prepareStatement(uint32_t index, const char* text) { |
501 | // Validate that there is space for the statement in the statements array |
502 | // and that nothing has been placed there before. |
503 | if ((index >= statements_.size()) || (statements_[index] != NULL__null)) { |
504 | isc_throw(InvalidParameter, "invalid prepared statement index (" <<do { std::ostringstream oss__; oss__ << "invalid prepared statement index (" << static_cast<int>(index) << ") or indexed prepared " << "statement is not null"; throw InvalidParameter("mysql_connection.cc" , 506, oss__.str().c_str()); } while (1) |
505 | static_cast<int>(index) << ") or indexed prepared " <<do { std::ostringstream oss__; oss__ << "invalid prepared statement index (" << static_cast<int>(index) << ") or indexed prepared " << "statement is not null"; throw InvalidParameter("mysql_connection.cc" , 506, oss__.str().c_str()); } while (1) |
506 | "statement is not null")do { std::ostringstream oss__; oss__ << "invalid prepared statement index (" << static_cast<int>(index) << ") or indexed prepared " << "statement is not null"; throw InvalidParameter("mysql_connection.cc" , 506, oss__.str().c_str()); } while (1); |
507 | } |
508 | |
509 | // All OK, so prepare the statement |
510 | text_statements_[index] = std::string(text); |
511 | statements_[index] = mysql_stmt_init(mysql_); |
512 | if (statements_[index] == NULL__null) { |
513 | isc_throw(DbOperationError, "unable to allocate MySQL prepared "do { std::ostringstream oss__; oss__ << "unable to allocate MySQL prepared " "statement structure, reason: " << mysql_error(mysql_) ; throw DbOperationError("mysql_connection.cc", 514, oss__.str ().c_str()); } while (1) |
514 | "statement structure, reason: " << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to allocate MySQL prepared " "statement structure, reason: " << mysql_error(mysql_) ; throw DbOperationError("mysql_connection.cc", 514, oss__.str ().c_str()); } while (1); |
515 | } |
516 | |
517 | int status = mysql_stmt_prepare(statements_[index], text, strlen(text)); |
518 | if (status != 0) { |
519 | isc_throw(DbOperationError, "unable to prepare MySQL statement <" <<do { std::ostringstream oss__; oss__ << "unable to prepare MySQL statement <" << text << ">, reason: " << mysql_error (mysql_); throw DbOperationError("mysql_connection.cc", 520, oss__ .str().c_str()); } while (1) |
520 | text << ">, reason: " << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to prepare MySQL statement <" << text << ">, reason: " << mysql_error (mysql_); throw DbOperationError("mysql_connection.cc", 520, oss__ .str().c_str()); } while (1); |
521 | } |
522 | } |
523 | |
524 | void |
525 | MySqlConnection::prepareStatements(const TaggedStatement* start_statement, |
526 | const TaggedStatement* end_statement) { |
527 | // Created the MySQL prepared statements for each DML statement. |
528 | for (const TaggedStatement* tagged_statement = start_statement; |
529 | tagged_statement != end_statement; ++tagged_statement) { |
530 | if (tagged_statement->index >= statements_.size()) { |
531 | statements_.resize(tagged_statement->index + 1, NULL__null); |
532 | text_statements_.resize(tagged_statement->index + 1, |
533 | std::string("")); |
534 | } |
535 | prepareStatement(tagged_statement->index, |
536 | tagged_statement->text); |
537 | } |
538 | } |
539 | |
540 | /// @brief Destructor |
541 | MySqlConnection::~MySqlConnection() { |
542 | // Free up the prepared statements, ignoring errors. (What would we do |
543 | // about them? We're destroying this object and are not really concerned |
544 | // with errors on a database connection that is about to go away.) |
545 | for (size_t i = 0; i < statements_.size(); ++i) { |
546 | if (statements_[i] != NULL__null) { |
547 | (void) mysql_stmt_close(statements_[i]); |
548 | statements_[i] = NULL__null; |
549 | } |
550 | } |
551 | statements_.clear(); |
552 | text_statements_.clear(); |
553 | } |
554 | |
555 | // Time conversion methods. |
556 | // |
557 | // Note that the MySQL TIMESTAMP data type (used for "expire") converts data |
558 | // from the current timezone to UTC for storage, and from UTC to the current |
559 | // timezone for retrieval. |
560 | // |
561 | // This causes no problems providing that: |
562 | // a) cltt is given in local time |
563 | // b) We let the system take care of timezone conversion when converting |
564 | // from a time read from the database into a local time. |
565 | void |
566 | MySqlConnection::convertToDatabaseTime(const time_t input_time, |
567 | MYSQL_TIME& output_time) { |
568 | MySqlBinding::convertToDatabaseTime(input_time, output_time); |
569 | } |
570 | |
571 | void |
572 | MySqlConnection::convertToDatabaseTime(const time_t cltt, |
573 | const uint32_t valid_lifetime, |
574 | MYSQL_TIME& expire) { |
575 | MySqlBinding::convertToDatabaseTime(cltt, valid_lifetime, expire); |
576 | } |
577 | |
578 | void |
579 | MySqlConnection::convertFromDatabaseTime(const MYSQL_TIME& expire, |
580 | uint32_t valid_lifetime, time_t& cltt) { |
581 | MySqlBinding::convertFromDatabaseTime(expire, valid_lifetime, cltt); |
582 | } |
583 | |
584 | void |
585 | MySqlConnection::startTransaction() { |
586 | // If it is nested transaction, do nothing. |
587 | if (++transaction_ref_count_ > 1) { |
588 | return; |
589 | } |
590 | |
591 | DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_START_TRANSACTION); |
592 | checkUnusable(); |
593 | // We create prepared statements for all other queries, but MySQL |
594 | // don't support prepared statements for START TRANSACTION. |
595 | int status = mysql_query(mysql_, "START TRANSACTION"); |
596 | if (status != 0) { |
597 | isc_throw(DbOperationError, "unable to start transaction, "do { std::ostringstream oss__; oss__ << "unable to start transaction, " "reason: " << mysql_error(mysql_); throw DbOperationError ("mysql_connection.cc", 598, oss__.str().c_str()); } while (1 ) |
598 | "reason: " << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to start transaction, " "reason: " << mysql_error(mysql_); throw DbOperationError ("mysql_connection.cc", 598, oss__.str().c_str()); } while (1 ); |
599 | } |
600 | } |
601 | |
602 | bool |
603 | MySqlConnection::isTransactionStarted() const { |
604 | return (transaction_ref_count_ > 0); |
605 | } |
606 | |
607 | void |
608 | MySqlConnection::commit() { |
609 | if (transaction_ref_count_ <= 0) { |
610 | isc_throw(Unexpected, "commit called for not started transaction - coding error")do { std::ostringstream oss__; oss__ << "commit called for not started transaction - coding error" ; throw Unexpected("mysql_connection.cc", 610, oss__.str().c_str ()); } while (1); |
611 | } |
612 | |
613 | // When committing nested transaction, do nothing. |
614 | if (--transaction_ref_count_ > 0) { |
615 | return; |
616 | } |
617 | DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_COMMIT); |
618 | checkUnusable(); |
619 | if (mysql_commit(mysql_) != 0) { |
620 | isc_throw(DbOperationError, "commit failed: "do { std::ostringstream oss__; oss__ << "commit failed: " << mysql_error(mysql_); throw DbOperationError("mysql_connection.cc" , 621, oss__.str().c_str()); } while (1) |
621 | << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "commit failed: " << mysql_error(mysql_); throw DbOperationError("mysql_connection.cc" , 621, oss__.str().c_str()); } while (1); |
622 | } |
623 | } |
624 | |
625 | void |
626 | MySqlConnection::rollback() { |
627 | if (transaction_ref_count_ <= 0) { |
628 | isc_throw(Unexpected, "rollback called for not started transaction - coding error")do { std::ostringstream oss__; oss__ << "rollback called for not started transaction - coding error" ; throw Unexpected("mysql_connection.cc", 628, oss__.str().c_str ()); } while (1); |
629 | } |
630 | |
631 | // When rolling back nested transaction, do nothing. |
632 | if (--transaction_ref_count_ > 0) { |
633 | return; |
634 | } |
635 | DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_ROLLBACK); |
636 | checkUnusable(); |
637 | if (mysql_rollback(mysql_) != 0) { |
638 | isc_throw(DbOperationError, "rollback failed: "do { std::ostringstream oss__; oss__ << "rollback failed: " << mysql_error(mysql_); throw DbOperationError("mysql_connection.cc" , 639, oss__.str().c_str()); } while (1) |
639 | << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "rollback failed: " << mysql_error(mysql_); throw DbOperationError("mysql_connection.cc" , 639, oss__.str().c_str()); } while (1); |
640 | } |
641 | } |
642 | |
643 | template<typename T> |
644 | void |
645 | MySqlConnection::setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value) { |
646 | string svalue; |
647 | try { |
648 | svalue = getParameter(name); |
649 | } catch (...) { |
650 | // Do nothing if the parameter is not present. |
651 | } |
652 | if (svalue.empty()) { |
653 | return; |
654 | } |
655 | try { |
656 | // Try to convert the value. |
657 | auto parsed_value = boost::lexical_cast<T>(svalue); |
658 | // Check if the value is within the specified range. |
659 | if ((parsed_value < min) || (parsed_value > max)) { |
660 | isc_throw(BadValue, "bad " << svalue << " value")do { std::ostringstream oss__; oss__ << "bad " << svalue << " value"; throw BadValue("mysql_connection.cc" , 660, oss__.str().c_str()); } while (1); |
661 | } |
662 | // Everything is fine. Return the parsed value. |
663 | value = parsed_value; |
664 | |
665 | } catch (...) { |
666 | // We may end up here when lexical_cast fails or when the |
667 | // parsed value is not within the desired range. In both |
668 | // cases let's throw the same general error. |
669 | isc_throw(BadValue, name << " parameter (" <<do { std::ostringstream oss__; oss__ << name << " parameter (" << svalue << ") must be an integer between " << min << " and " << max; throw BadValue("mysql_connection.cc" , 671, oss__.str().c_str()); } while (1) |
670 | svalue << ") must be an integer between "do { std::ostringstream oss__; oss__ << name << " parameter (" << svalue << ") must be an integer between " << min << " and " << max; throw BadValue("mysql_connection.cc" , 671, oss__.str().c_str()); } while (1) |
671 | << min << " and " << max)do { std::ostringstream oss__; oss__ << name << " parameter (" << svalue << ") must be an integer between " << min << " and " << max; throw BadValue("mysql_connection.cc" , 671, oss__.str().c_str()); } while (1); |
672 | } |
673 | } |
674 | |
675 | } // namespace db |
676 | } // namespace isc |