File: | home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-private/tmphs1g5w79/../../../src/lib/pgsql/pgsql_connection.cc |
Warning: | line 418, column 9 Value stored to 'tls' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | // Copyright (C) 2016-2025 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 <cc/default_credentials.h> |
12 | #include <database/database_connection.h> |
13 | #include <database/db_exceptions.h> |
14 | #include <database/db_log.h> |
15 | #include <pgsql/pgsql_connection.h> |
16 | #include <util/filesystem.h> |
17 | |
18 | #include <exception> |
19 | #include <sstream> |
20 | #include <unordered_map> |
21 | |
22 | using namespace isc::asiolink; |
23 | using namespace isc::data; |
24 | using namespace std; |
25 | |
26 | namespace isc { |
27 | namespace db { |
28 | |
29 | std::string PgSqlConnection::KEA_ADMIN_ = KEA_ADMIN"/usr/local/sbin/kea-admin"; |
30 | |
31 | // Default connection timeout |
32 | |
33 | /// @todo: migrate this default timeout to src/bin/dhcpX/simple_parserX.cc |
34 | const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds |
35 | |
36 | // Length of error codes |
37 | constexpr size_t PGSQL_STATECODE_LEN = 5; |
38 | |
39 | // Error codes from https://www.postgresql.org/docs/current/errcodes-appendix.html or utils/errcodes.h |
40 | const char PgSqlConnection::DUPLICATE_KEY[] = "23505"; |
41 | const char PgSqlConnection::NULL_KEY[] = "23502"; |
42 | |
43 | bool PgSqlConnection::warned_about_tls = false; |
44 | |
45 | PgSqlResult::PgSqlResult(PGresult *result) |
46 | : result_(result), rows_(0), cols_(0) { |
47 | if (!result) { |
48 | // Certain failures, like a loss of connectivity, can return a |
49 | // null PGresult and we still need to be able to create a PgSqlResult. |
50 | // We'll set row and col counts to -1 to prevent anyone going off the |
51 | // rails. |
52 | rows_ = -1; |
53 | cols_ = -1; |
54 | } else { |
55 | rows_ = PQntuples(result); |
56 | cols_ = PQnfields(result); |
57 | } |
58 | } |
59 | |
60 | void |
61 | PgSqlResult::rowCheck(int row) const { |
62 | if (row < 0 || row >= rows_) { |
63 | isc_throw (db::DbOperationError, "row: " << rowdo { std::ostringstream oss__; oss__ << "row: " << row << ", out of range: 0.." << rows_; throw db:: DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 64, oss__.str().c_str()); } while (1) |
64 | << ", out of range: 0.." << rows_)do { std::ostringstream oss__; oss__ << "row: " << row << ", out of range: 0.." << rows_; throw db:: DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 64, oss__.str().c_str()); } while (1); |
65 | } |
66 | } |
67 | |
68 | PgSqlResult::~PgSqlResult() { |
69 | if (result_) { |
70 | PQclear(result_); |
71 | } |
72 | } |
73 | |
74 | void |
75 | PgSqlResult::colCheck(int col) const { |
76 | if (col < 0 || col >= cols_) { |
77 | isc_throw (DbOperationError, "col: " << coldo { std::ostringstream oss__; oss__ << "col: " << col << ", out of range: 0.." << cols_; throw DbOperationError ("../../../src/lib/pgsql/pgsql_connection.cc", 78, oss__.str( ).c_str()); } while (1) |
78 | << ", out of range: 0.." << cols_)do { std::ostringstream oss__; oss__ << "col: " << col << ", out of range: 0.." << cols_; throw DbOperationError ("../../../src/lib/pgsql/pgsql_connection.cc", 78, oss__.str( ).c_str()); } while (1); |
79 | } |
80 | } |
81 | |
82 | void |
83 | PgSqlResult::rowColCheck(int row, int col) const { |
84 | rowCheck(row); |
85 | colCheck(col); |
86 | } |
87 | |
88 | std::string |
89 | PgSqlResult::getColumnLabel(const int col) const { |
90 | const char* label = NULL__null; |
91 | try { |
92 | colCheck(col); |
93 | label = PQfname(result_, col); |
94 | } catch (...) { |
95 | std::ostringstream os; |
96 | os << "Unknown column:" << col; |
97 | return (os.str()); |
98 | } |
99 | |
100 | return (label); |
101 | } |
102 | |
103 | PgSqlTransaction::PgSqlTransaction(PgSqlConnection& conn) |
104 | : conn_(conn), committed_(false) { |
105 | conn_.startTransaction(); |
106 | } |
107 | |
108 | PgSqlTransaction::~PgSqlTransaction() { |
109 | // If commit() wasn't explicitly called, rollback. |
110 | if (!committed_) { |
111 | conn_.rollback(); |
112 | } |
113 | } |
114 | |
115 | void |
116 | PgSqlTransaction::commit() { |
117 | conn_.commit(); |
118 | committed_ = true; |
119 | } |
120 | |
121 | PgSqlConnection::~PgSqlConnection() { |
122 | if (conn_ && !isUnusable()) { |
123 | // Deallocate the prepared queries. |
124 | if (PQstatus(conn_) == CONNECTION_OK) { |
125 | PgSqlResult r(PQexec(conn_, "DEALLOCATE all")); |
126 | if (PQresultStatus(r) != PGRES_COMMAND_OK) { |
127 | // Highly unlikely but we'll log it and go on. |
128 | DB_LOG_ERROR(PGSQL_DEALLOC_ERROR) |
129 | .arg(PQerrorMessage(conn_)); |
130 | } |
131 | } |
132 | } |
133 | } |
134 | |
135 | std::pair<uint32_t, uint32_t> |
136 | PgSqlConnection::getVersion(const ParameterMap& parameters, |
137 | const IOServiceAccessorPtr& ac, |
138 | const DbCallback& cb, |
139 | const string& timer_name, |
140 | unsigned int id) { |
141 | // Get a connection. |
142 | PgSqlConnection conn(parameters, ac, cb); |
143 | |
144 | if (!timer_name.empty()) { |
145 | conn.makeReconnectCtl(timer_name, id); |
146 | } |
147 | |
148 | // Open the database. |
149 | conn.openDatabaseInternal(false); |
150 | |
151 | const char* version_sql = "SELECT version, minor FROM schema_version;"; |
152 | PgSqlResult r(PQexec(conn.conn_, version_sql)); |
153 | if (PQresultStatus(r) != PGRES_TUPLES_OK) { |
154 | isc_throw(DbOperationError, "unable to execute PostgreSQL statement <"do { std::ostringstream oss__; oss__ << "unable to execute PostgreSQL statement <" << version_sql << ", reason: " << PQerrorMessage (conn.conn_); throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 155, oss__.str().c_str()); } while (1) |
155 | << version_sql << ", reason: " << PQerrorMessage(conn.conn_))do { std::ostringstream oss__; oss__ << "unable to execute PostgreSQL statement <" << version_sql << ", reason: " << PQerrorMessage (conn.conn_); throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 155, oss__.str().c_str()); } while (1); |
156 | } |
157 | |
158 | uint32_t version; |
159 | PgSqlExchange::getColumnValue(r, 0, 0, version); |
160 | |
161 | uint32_t minor; |
162 | PgSqlExchange::getColumnValue(r, 0, 1, minor); |
163 | |
164 | return (make_pair(version, minor)); |
165 | } |
166 | |
167 | void |
168 | PgSqlConnection::ensureSchemaVersion(const ParameterMap& parameters, |
169 | const DbCallback& cb, |
170 | const string& timer_name) { |
171 | // retry-on-startup? |
172 | bool const retry(parameters.count("retry-on-startup") && |
173 | parameters.at("retry-on-startup") == "true"); |
174 | |
175 | IOServiceAccessorPtr ac(new IOServiceAccessor(&DatabaseConnection::getIOService)); |
176 | pair<uint32_t, uint32_t> schema_version; |
177 | try { |
178 | schema_version = getVersion(parameters, ac, cb, retry ? timer_name : string()); |
179 | } catch (DbOpenError const& exception) { |
180 | throw; |
181 | } catch (DbOpenErrorWithRetry const& exception) { |
182 | throw; |
183 | } catch (exception const& exception) { |
184 | // Disable the recovery mechanism in test mode. |
185 | if (DatabaseConnection::test_mode_) { |
186 | throw; |
187 | } |
188 | // This failure may occur for a variety of reasons. We are looking at |
189 | // initializing schema as the only potential mitigation. We could narrow |
190 | // down on the error that would suggest an uninitialized schema |
191 | // which would sound something along the lines of |
192 | // "table schema_version does not exist", but we do not necessarily have |
193 | // to. If the error had another cause, it will fail again during |
194 | // initialization or during the subsequent version retrieval and that is |
195 | // fine, and the error should still be relevant. |
196 | initializeSchema(parameters); |
197 | |
198 | // Retrieve again because the initial retrieval failed. |
199 | schema_version = getVersion(parameters, ac, cb, retry ? timer_name : string()); |
200 | } |
201 | |
202 | // Check that the versions match. |
203 | pair<uint32_t, uint32_t> const expected_version(PGSQL_SCHEMA_VERSION_MAJOR, |
204 | PGSQL_SCHEMA_VERSION_MINOR); |
205 | if (schema_version != expected_version) { |
206 | isc_throw(DbOpenError, "PostgreSQL schema version mismatch: expected version: "do { std::ostringstream oss__; oss__ << "PostgreSQL schema version mismatch: expected version: " << expected_version.first << "." << expected_version .second << ", found version: " << schema_version. first << "." << schema_version.second; throw DbOpenError ("../../../src/lib/pgsql/pgsql_connection.cc", 209, oss__.str ().c_str()); } while (1) |
207 | << expected_version.first << "." << expected_version.seconddo { std::ostringstream oss__; oss__ << "PostgreSQL schema version mismatch: expected version: " << expected_version.first << "." << expected_version .second << ", found version: " << schema_version. first << "." << schema_version.second; throw DbOpenError ("../../../src/lib/pgsql/pgsql_connection.cc", 209, oss__.str ().c_str()); } while (1) |
208 | << ", found version: " << schema_version.first << "."do { std::ostringstream oss__; oss__ << "PostgreSQL schema version mismatch: expected version: " << expected_version.first << "." << expected_version .second << ", found version: " << schema_version. first << "." << schema_version.second; throw DbOpenError ("../../../src/lib/pgsql/pgsql_connection.cc", 209, oss__.str ().c_str()); } while (1) |
209 | << schema_version.second)do { std::ostringstream oss__; oss__ << "PostgreSQL schema version mismatch: expected version: " << expected_version.first << "." << expected_version .second << ", found version: " << schema_version. first << "." << schema_version.second; throw DbOpenError ("../../../src/lib/pgsql/pgsql_connection.cc", 209, oss__.str ().c_str()); } while (1); |
210 | } |
211 | } |
212 | |
213 | void |
214 | PgSqlConnection::initializeSchema(const ParameterMap& parameters) { |
215 | if (parameters.count("readonly") && parameters.at("readonly") == "true") { |
216 | // The readonly flag is historically used for host backends. Still, if |
217 | // enabled, it is a strong indication that we should not meDDLe with it. |
218 | return; |
219 | } |
220 | |
221 | if (!isc::util::file::isFile(KEA_ADMIN_)) { |
222 | // It can happen for kea-admin to not exist, especially with |
223 | // packages that install it in a separate package. |
224 | return; |
225 | } |
226 | |
227 | // Convert parameters. |
228 | auto const tupl(toKeaAdminParameters(parameters)); |
229 | vector<string> kea_admin_parameters(get<0>(tupl)); |
230 | ProcessEnvVars const vars(get<1>(tupl)); |
231 | kea_admin_parameters.insert(kea_admin_parameters.begin(), "db-init"); |
232 | |
233 | // Run. |
234 | ProcessSpawn kea_admin(ProcessSpawn::SYNC, KEA_ADMIN_, kea_admin_parameters, vars, |
235 | /* inherit_env = */ true); |
236 | DB_LOG_INFO(PGSQL_INITIALIZE_SCHEMA) |
237 | .arg(kea_admin.getCommandLine(std::unordered_set<std::string>{"--password"})); |
238 | pid_t const pid(kea_admin.spawn()); |
239 | if (kea_admin.isRunning(pid)) { |
240 | isc_throw(SchemaInitializationFailed, "kea-admin still running")do { std::ostringstream oss__; oss__ << "kea-admin still running" ; throw SchemaInitializationFailed("../../../src/lib/pgsql/pgsql_connection.cc" , 240, oss__.str().c_str()); } while (1); |
241 | } |
242 | int const exit_code(kea_admin.getExitStatus(pid)); |
243 | if (exit_code != 0) { |
244 | 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("../../../src/lib/pgsql/pgsql_connection.cc" , 244, oss__.str().c_str()); } while (1); |
245 | } |
246 | } |
247 | |
248 | tuple<vector<string>, vector<string>> |
249 | PgSqlConnection::toKeaAdminParameters(ParameterMap const& params) { |
250 | vector<string> result{"pgsql"}; |
251 | ProcessEnvVars vars; |
252 | for (auto const& p : params) { |
253 | string const& keyword(p.first); |
254 | string const& value(p.second); |
255 | |
256 | // These Kea parameters are the same as the kea-admin parameters. |
257 | if (keyword == "user" || |
258 | keyword == "password" || |
259 | keyword == "host" || |
260 | keyword == "port" || |
261 | keyword == "name") { |
262 | result.push_back("--" + keyword); |
263 | result.push_back(value); |
264 | continue; |
265 | } |
266 | |
267 | // These Kea parameters do not have a direct kea-admin equivalent. |
268 | // But they do have a psql client flag equivalent. |
269 | // We pass them to kea-admin using the --extra flag. |
270 | static unordered_map<string, string> conversions{ |
271 | {"cert-file", "sslcert"}, |
272 | {"key-file", "sslkey"}, |
273 | {"trust-anchor", "sslrootcert"}, |
274 | {"ssl-mode", "sslmode"}, |
275 | }; |
276 | if (conversions.count(keyword)) { |
277 | result.push_back("--extra"); |
278 | result.push_back(conversions.at(keyword) + "=" + value); |
279 | } |
280 | |
281 | // These Kea parameters do not have a direct kea-admin equivalent. |
282 | // But they do have a psql client environment variable equivalent. |
283 | // We pass them to kea-admin. |
284 | static unordered_map<string, string> env_conversions{ |
285 | {"connect-timeout", "PGCONNECT_TIMEOUT"}, |
286 | // {"tcp-user-timeout", "N/A"}, |
287 | }; |
288 | if (env_conversions.count(keyword)) { |
289 | vars.push_back(env_conversions.at(keyword) + "=" + value); |
290 | } |
291 | } |
292 | return make_tuple(result, vars); |
293 | } |
294 | |
295 | void |
296 | PgSqlConnection::prepareStatement(const PgSqlTaggedStatement& statement) { |
297 | // Prepare all statements queries with all known fields datatype |
298 | PgSqlResult r(PQprepare(conn_, statement.name, statement.text, |
299 | statement.nbparams, statement.types)); |
300 | if (PQresultStatus(r) != PGRES_COMMAND_OK) { |
301 | isc_throw(DbOperationError, "unable to prepare PostgreSQL statement: "do { std::ostringstream oss__; oss__ << "unable to prepare PostgreSQL statement: " << " name: " << statement.name << ", reason: " << PQerrorMessage(conn_) << ", text: " << statement .text; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 304, oss__.str().c_str()); } while (1) |
302 | << " name: " << statement.namedo { std::ostringstream oss__; oss__ << "unable to prepare PostgreSQL statement: " << " name: " << statement.name << ", reason: " << PQerrorMessage(conn_) << ", text: " << statement .text; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 304, oss__.str().c_str()); } while (1) |
303 | << ", reason: " << PQerrorMessage(conn_)do { std::ostringstream oss__; oss__ << "unable to prepare PostgreSQL statement: " << " name: " << statement.name << ", reason: " << PQerrorMessage(conn_) << ", text: " << statement .text; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 304, oss__.str().c_str()); } while (1) |
304 | << ", text: " << statement.text)do { std::ostringstream oss__; oss__ << "unable to prepare PostgreSQL statement: " << " name: " << statement.name << ", reason: " << PQerrorMessage(conn_) << ", text: " << statement .text; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 304, oss__.str().c_str()); } while (1); |
305 | } |
306 | } |
307 | |
308 | void |
309 | PgSqlConnection::prepareStatements(const PgSqlTaggedStatement* start_statement, |
310 | const PgSqlTaggedStatement* end_statement) { |
311 | // Created the PostgreSQL prepared statements. |
312 | for (const PgSqlTaggedStatement* tagged_statement = start_statement; |
313 | tagged_statement != end_statement; ++tagged_statement) { |
314 | prepareStatement(*tagged_statement); |
315 | } |
316 | } |
317 | |
318 | std::string |
319 | PgSqlConnection::getConnParameters() { |
320 | return (getConnParametersInternal(false)); |
321 | } |
322 | |
323 | std::string |
324 | PgSqlConnection::getConnParametersInternal(bool logging) { |
325 | string dbconnparameters; |
326 | string shost = "localhost"; |
327 | try { |
328 | shost = getParameter("host"); |
329 | } catch(...) { |
330 | // No host. Fine, we'll use "localhost" |
331 | } |
332 | |
333 | dbconnparameters += "host = '" + shost + "'" ; |
334 | |
335 | unsigned int port = 0; |
336 | try { |
337 | setIntParameterValue("port", 0, numeric_limits<uint16_t>::max(), port); |
338 | |
339 | } catch (const std::exception& ex) { |
340 | isc_throw(DbInvalidPort, ex.what())do { std::ostringstream oss__; oss__ << ex.what(); throw DbInvalidPort("../../../src/lib/pgsql/pgsql_connection.cc", 340 , oss__.str().c_str()); } while (1); |
341 | } |
342 | |
343 | // Add port to connection parameters when not default. |
344 | if (port > 0) { |
345 | std::ostringstream oss; |
346 | oss << port; |
347 | dbconnparameters += " port = " + oss.str(); |
348 | } |
349 | |
350 | string suser; |
351 | try { |
352 | suser = getParameter("user"); |
353 | dbconnparameters += " user = '" + suser + "'"; |
354 | } catch(...) { |
355 | // No user. Fine, we'll use NULL |
356 | } |
357 | |
358 | string spassword; |
359 | try { |
360 | spassword = getParameter("password"); |
361 | dbconnparameters += " password = '" + spassword + "'"; |
362 | } catch(...) { |
363 | // No password. Fine, we'll use NULL |
364 | } |
365 | if (!spassword.empty()) { |
366 | // Refuse default password. |
367 | DefaultCredentials::check(spassword); |
368 | } |
369 | |
370 | string sname; |
371 | try { |
372 | sname = getParameter("name"); |
373 | dbconnparameters += " dbname = '" + sname + "'"; |
374 | } catch(...) { |
375 | // No database name. Throw a "NoDatabaseName" exception |
376 | isc_throw(NoDatabaseName, "must specify a name for the database")do { std::ostringstream oss__; oss__ << "must specify a name for the database" ; throw NoDatabaseName("../../../src/lib/pgsql/pgsql_connection.cc" , 376, oss__.str().c_str()); } while (1); |
377 | } |
378 | |
379 | unsigned int connect_timeout = PGSQL_DEFAULT_CONNECTION_TIMEOUT; |
380 | unsigned int tcp_user_timeout = 0; |
381 | try { |
382 | // The timeout is only valid if greater than zero, as depending on the |
383 | // database, a zero timeout might signify something like "wait |
384 | // indefinitely". |
385 | setIntParameterValue("connect-timeout", 1, numeric_limits<int>::max(), connect_timeout); |
386 | // This timeout value can be 0, meaning that the database client will |
387 | // follow a default behavior. Earlier Postgres versions didn't have |
388 | // this parameter, so we allow 0 to skip setting them for these |
389 | // earlier versions. |
390 | setIntParameterValue("tcp-user-timeout", 0, numeric_limits<int>::max(), tcp_user_timeout); |
391 | |
392 | } catch (const std::exception& ex) { |
393 | isc_throw(DbInvalidTimeout, ex.what())do { std::ostringstream oss__; oss__ << ex.what(); throw DbInvalidTimeout("../../../src/lib/pgsql/pgsql_connection.cc" , 393, oss__.str().c_str()); } while (1); |
394 | } |
395 | |
396 | // Append connection timeout. |
397 | std::ostringstream oss; |
398 | oss << " connect_timeout = " << connect_timeout; |
399 | |
400 | if (tcp_user_timeout > 0) { |
401 | // tcp_user_timeout parameter is a PostgreSQL 12+ feature. |
402 | #ifdef HAVE_PGSQL_TCP_USER_TIMEOUT |
403 | oss << " tcp_user_timeout = " << tcp_user_timeout * 1000; |
404 | static_cast<void>(logging); |
405 | #else |
406 | if (logging) { |
407 | DB_LOG_WARN(PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED).arg(); |
408 | } |
409 | #endif |
410 | } |
411 | dbconnparameters += oss.str(); |
412 | |
413 | bool tls = false; |
414 | |
415 | string ssslmode; |
416 | try { |
417 | ssslmode = getParameter("ssl-mode"); |
418 | tls = true; |
Value stored to 'tls' is never read | |
419 | } catch (...) { |
420 | // No strict ssl mode |
421 | } |
422 | |
423 | string sca; |
424 | try { |
425 | sca = getParameter("trust-anchor"); |
426 | tls = true; |
427 | if (ssslmode.empty()) { |
428 | ssslmode = "verify-ca"; |
429 | } |
430 | dbconnparameters += " sslrootcert = " + sca; |
431 | } catch (...) { |
432 | // No trust anchor |
433 | } |
434 | |
435 | string scert; |
436 | try { |
437 | scert = getParameter("cert-file"); |
438 | tls = true; |
439 | dbconnparameters += " sslcert = " + scert; |
440 | } catch (...) { |
441 | // No client certificate file |
442 | } |
443 | |
444 | string skey; |
445 | try { |
446 | skey = getParameter("key-file"); |
447 | tls = true; |
448 | dbconnparameters += " sslkey = " + skey; |
449 | } catch (...) { |
450 | // No private key file |
451 | } |
452 | |
453 | if (tls) { |
454 | if (ssslmode.empty()) { |
455 | ssslmode = "require"; |
456 | } |
457 | dbconnparameters += " gssencmode = disable"; |
458 | } |
459 | |
460 | if (!ssslmode.empty()) { |
461 | dbconnparameters += " sslmode = " + ssslmode; |
462 | } |
463 | |
464 | return (dbconnparameters); |
465 | } |
466 | |
467 | void |
468 | PgSqlConnection::openDatabase() { |
469 | openDatabaseInternal(true); |
470 | } |
471 | |
472 | void |
473 | PgSqlConnection::openDatabaseInternal(bool logging) { |
474 | std::string dbconnparameters = getConnParametersInternal(logging); |
475 | // Connect to PostgreSQL, saving the low level connection pointer |
476 | // in the holder object |
477 | PGconn* new_conn = PQconnectdb(dbconnparameters.c_str()); |
478 | if (!new_conn) { |
479 | isc_throw(DbOpenError, "could not allocate connection object")do { std::ostringstream oss__; oss__ << "could not allocate connection object" ; throw DbOpenError("../../../src/lib/pgsql/pgsql_connection.cc" , 479, oss__.str().c_str()); } while (1); |
480 | } |
481 | |
482 | if (PQstatus(new_conn) != CONNECTION_OK) { |
483 | // Mark this connection as no longer usable. |
484 | markUnusable(); |
485 | |
486 | // If we have a connection object, we have to call finish |
487 | // to release it, but grab the error message first. |
488 | std::string error_message = PQerrorMessage(new_conn); |
489 | PQfinish(new_conn); |
490 | |
491 | auto const& rec = reconnectCtl(); |
492 | if (rec && DatabaseConnection::retry_) { |
493 | |
494 | // Start the connection recovery. |
495 | startRecoverDbConnection(); |
496 | |
497 | std::ostringstream s; |
498 | |
499 | s << " (scheduling retry " << rec->retryIndex() + 1 << " of " << rec->maxRetries() << " in " << rec->retryInterval() << " milliseconds)"; |
500 | |
501 | error_message += s.str(); |
502 | |
503 | isc_throw(DbOpenErrorWithRetry, error_message)do { std::ostringstream oss__; oss__ << error_message; throw DbOpenErrorWithRetry("../../../src/lib/pgsql/pgsql_connection.cc" , 503, oss__.str().c_str()); } while (1); |
504 | } |
505 | |
506 | isc_throw(DbOpenError, error_message)do { std::ostringstream oss__; oss__ << error_message; throw DbOpenError("../../../src/lib/pgsql/pgsql_connection.cc", 506 , oss__.str().c_str()); } while (1); |
507 | } |
508 | |
509 | // We have a valid connection, so let's save it to our holder |
510 | conn_.setConnection(new_conn); |
511 | } |
512 | |
513 | bool |
514 | PgSqlConnection::compareError(const PgSqlResult& r, const char* error_state) { |
515 | const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE'C'); |
516 | // PostgreSQL guarantees it will always be 5 characters long |
517 | return ((sqlstate != NULL__null) && |
518 | (memcmp(sqlstate, error_state, PGSQL_STATECODE_LEN) == 0)); |
519 | } |
520 | |
521 | void |
522 | PgSqlConnection::checkStatementError(const PgSqlResult& r, |
523 | PgSqlTaggedStatement& statement) { |
524 | int s = PQresultStatus(r); |
525 | if (s != PGRES_COMMAND_OK && s != PGRES_TUPLES_OK) { |
526 | // We're testing the first two chars of SQLSTATE, as this is the |
527 | // error class. Note, there is a severity field, but it can be |
528 | // misleadingly returned as fatal. However, a loss of connectivity |
529 | // can lead to a NULL sqlstate with a status of PGRES_FATAL_ERROR. |
530 | const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE'C'); |
531 | if ((sqlstate == NULL__null) || |
532 | ((memcmp(sqlstate, "08", 2) == 0) || // Connection Exception |
533 | (memcmp(sqlstate, "53", 2) == 0) || // Insufficient resources |
534 | (memcmp(sqlstate, "54", 2) == 0) || // Program Limit exceeded |
535 | (memcmp(sqlstate, "57", 2) == 0) || // Operator intervention |
536 | (memcmp(sqlstate, "58", 2) == 0))) { // System error |
537 | DB_LOG_ERROR(PGSQL_FATAL_ERROR) |
538 | .arg(statement.name) |
539 | .arg(PQerrorMessage(conn_)) |
540 | .arg(sqlstate ? sqlstate : "<sqlstate null>"); |
541 | |
542 | // Mark this connection as no longer usable. |
543 | markUnusable(); |
544 | |
545 | // Start the connection recovery. |
546 | startRecoverDbConnection(); |
547 | |
548 | // We still need to throw so caller can error out of the current |
549 | // processing. |
550 | isc_throw(DbConnectionUnusable,do { std::ostringstream oss__; oss__ << "fatal database error or connectivity lost" ; throw DbConnectionUnusable("../../../src/lib/pgsql/pgsql_connection.cc" , 551, oss__.str().c_str()); } while (1) |
551 | "fatal database error or connectivity lost")do { std::ostringstream oss__; oss__ << "fatal database error or connectivity lost" ; throw DbConnectionUnusable("../../../src/lib/pgsql/pgsql_connection.cc" , 551, oss__.str().c_str()); } while (1); |
552 | } |
553 | |
554 | // Failure: check for the special case of duplicate entry. |
555 | if (compareError(r, PgSqlConnection::DUPLICATE_KEY)) { |
556 | isc_throw(DuplicateEntry, "statement: " << statement.namedo { std::ostringstream oss__; oss__ << "statement: " << statement.name << ", reason: " << PQerrorMessage (conn_); throw DuplicateEntry("../../../src/lib/pgsql/pgsql_connection.cc" , 557, oss__.str().c_str()); } while (1) |
557 | << ", reason: " << PQerrorMessage(conn_))do { std::ostringstream oss__; oss__ << "statement: " << statement.name << ", reason: " << PQerrorMessage (conn_); throw DuplicateEntry("../../../src/lib/pgsql/pgsql_connection.cc" , 557, oss__.str().c_str()); } while (1); |
558 | } |
559 | |
560 | // Failure: check for the special case of null key violation. |
561 | if (compareError(r, PgSqlConnection::NULL_KEY)) { |
562 | isc_throw(NullKeyError, "statement: " << statement.namedo { std::ostringstream oss__; oss__ << "statement: " << statement.name << ", reason: " << PQerrorMessage (conn_); throw NullKeyError("../../../src/lib/pgsql/pgsql_connection.cc" , 563, oss__.str().c_str()); } while (1) |
563 | << ", reason: " << PQerrorMessage(conn_))do { std::ostringstream oss__; oss__ << "statement: " << statement.name << ", reason: " << PQerrorMessage (conn_); throw NullKeyError("../../../src/lib/pgsql/pgsql_connection.cc" , 563, oss__.str().c_str()); } while (1); |
564 | } |
565 | |
566 | // Apparently it wasn't fatal, so we throw with a helpful message. |
567 | const char* error_message = PQerrorMessage(conn_); |
568 | isc_throw(DbOperationError, "Statement exec failed for: "do { std::ostringstream oss__; oss__ << "Statement exec failed for: " << statement.name << ", status: " << s << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>" ) << " ], reason: " << error_message; throw DbOperationError ("../../../src/lib/pgsql/pgsql_connection.cc", 571, oss__.str ().c_str()); } while (1) |
569 | << statement.name << ", status: " << sdo { std::ostringstream oss__; oss__ << "Statement exec failed for: " << statement.name << ", status: " << s << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>" ) << " ], reason: " << error_message; throw DbOperationError ("../../../src/lib/pgsql/pgsql_connection.cc", 571, oss__.str ().c_str()); } while (1) |
570 | << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>")do { std::ostringstream oss__; oss__ << "Statement exec failed for: " << statement.name << ", status: " << s << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>" ) << " ], reason: " << error_message; throw DbOperationError ("../../../src/lib/pgsql/pgsql_connection.cc", 571, oss__.str ().c_str()); } while (1) |
571 | << " ], reason: " << error_message)do { std::ostringstream oss__; oss__ << "Statement exec failed for: " << statement.name << ", status: " << s << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>" ) << " ], reason: " << error_message; throw DbOperationError ("../../../src/lib/pgsql/pgsql_connection.cc", 571, oss__.str ().c_str()); } while (1); |
572 | } |
573 | } |
574 | |
575 | void |
576 | PgSqlConnection::startTransaction() { |
577 | // If it is nested transaction, do nothing. |
578 | if (++transaction_ref_count_ > 1) { |
579 | return; |
580 | } |
581 | |
582 | DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_START_TRANSACTION); |
583 | checkUnusable(); |
584 | PgSqlResult r(PQexec(conn_, "START TRANSACTION")); |
585 | if (PQresultStatus(r) != PGRES_COMMAND_OK) { |
586 | const char* error_message = PQerrorMessage(conn_); |
587 | isc_throw(DbOperationError, "unable to start transaction"do { std::ostringstream oss__; oss__ << "unable to start transaction" << error_message; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 588, oss__.str().c_str()); } while (1) |
588 | << error_message)do { std::ostringstream oss__; oss__ << "unable to start transaction" << error_message; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 588, oss__.str().c_str()); } while (1); |
589 | } |
590 | } |
591 | |
592 | bool |
593 | PgSqlConnection::isTransactionStarted() const { |
594 | return (transaction_ref_count_ > 0); |
595 | } |
596 | |
597 | void |
598 | PgSqlConnection::commit() { |
599 | if (transaction_ref_count_ <= 0) { |
600 | 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("../../../src/lib/pgsql/pgsql_connection.cc" , 600, oss__.str().c_str()); } while (1); |
601 | } |
602 | |
603 | // When committing nested transaction, do nothing. |
604 | if (--transaction_ref_count_ > 0) { |
605 | return; |
606 | } |
607 | |
608 | DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_COMMIT); |
609 | checkUnusable(); |
610 | PgSqlResult r(PQexec(conn_, "COMMIT")); |
611 | if (PQresultStatus(r) != PGRES_COMMAND_OK) { |
612 | const char* error_message = PQerrorMessage(conn_); |
613 | isc_throw(DbOperationError, "commit failed: " << error_message)do { std::ostringstream oss__; oss__ << "commit failed: " << error_message; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 613, oss__.str().c_str()); } while (1); |
614 | } |
615 | } |
616 | |
617 | void |
618 | PgSqlConnection::rollback() { |
619 | if (transaction_ref_count_ <= 0) { |
620 | 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("../../../src/lib/pgsql/pgsql_connection.cc" , 620, oss__.str().c_str()); } while (1); |
621 | } |
622 | |
623 | // When rolling back nested transaction, do nothing. |
624 | if (--transaction_ref_count_ > 0) { |
625 | return; |
626 | } |
627 | |
628 | DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_ROLLBACK); |
629 | checkUnusable(); |
630 | PgSqlResult r(PQexec(conn_, "ROLLBACK")); |
631 | if (PQresultStatus(r) != PGRES_COMMAND_OK) { |
632 | const char* error_message = PQerrorMessage(conn_); |
633 | isc_throw(DbOperationError, "rollback failed: " << error_message)do { std::ostringstream oss__; oss__ << "rollback failed: " << error_message; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc" , 633, oss__.str().c_str()); } while (1); |
634 | } |
635 | } |
636 | |
637 | void |
638 | PgSqlConnection::createSavepoint(const std::string& name) { |
639 | if (transaction_ref_count_ <= 0) { |
640 | isc_throw(InvalidOperation, "no transaction, cannot create savepoint: " << name)do { std::ostringstream oss__; oss__ << "no transaction, cannot create savepoint: " << name; throw InvalidOperation("../../../src/lib/pgsql/pgsql_connection.cc" , 640, oss__.str().c_str()); } while (1); |
641 | } |
642 | |
643 | DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_CREATE_SAVEPOINT).arg(name); |
644 | std::string sql("SAVEPOINT " + name); |
645 | executeSQL(sql); |
646 | } |
647 | |
648 | void |
649 | PgSqlConnection::rollbackToSavepoint(const std::string& name) { |
650 | if (transaction_ref_count_ <= 0) { |
651 | isc_throw(InvalidOperation, "no transaction, cannot rollback to savepoint: " << name)do { std::ostringstream oss__; oss__ << "no transaction, cannot rollback to savepoint: " << name; throw InvalidOperation("../../../src/lib/pgsql/pgsql_connection.cc" , 651, oss__.str().c_str()); } while (1); |
652 | } |
653 | |
654 | std::string sql("ROLLBACK TO SAVEPOINT " + name); |
655 | executeSQL(sql); |
656 | } |
657 | |
658 | void |
659 | PgSqlConnection::executeSQL(const std::string& sql) { |
660 | // Use a TaggedStatement so we can call checkStatementError and ensure |
661 | // we detect connectivity issues properly. |
662 | PgSqlTaggedStatement statement({0, {OID_NONE}, "run-statement", sql.c_str()}); |
663 | checkUnusable(); |
664 | PgSqlResult r(PQexec(conn_, statement.text)); |
665 | checkStatementError(r, statement); |
666 | } |
667 | |
668 | PgSqlResultPtr |
669 | PgSqlConnection::executePreparedStatement(PgSqlTaggedStatement& statement, |
670 | const PsqlBindArray& in_bindings) { |
671 | checkUnusable(); |
672 | |
673 | if (static_cast<size_t>(statement.nbparams) != in_bindings.size()) { |
674 | isc_throw (InvalidOperation, "executePreparedStatement:"do { std::ostringstream oss__; oss__ << "executePreparedStatement:" << " expected: " << statement.nbparams << " parameters, given: " << in_bindings.size() << ", statement: " << statement.name << ", SQL: " << statement.text; throw InvalidOperation("../../../src/lib/pgsql/pgsql_connection.cc" , 678, oss__.str().c_str()); } while (1) |
675 | << " expected: " << statement.nbparamsdo { std::ostringstream oss__; oss__ << "executePreparedStatement:" << " expected: " << statement.nbparams << " parameters, given: " << in_bindings.size() << ", statement: " << statement.name << ", SQL: " << statement.text; throw InvalidOperation("../../../src/lib/pgsql/pgsql_connection.cc" , 678, oss__.str().c_str()); } while (1) |
676 | << " parameters, given: " << in_bindings.size()do { std::ostringstream oss__; oss__ << "executePreparedStatement:" << " expected: " << statement.nbparams << " parameters, given: " << in_bindings.size() << ", statement: " << statement.name << ", SQL: " << statement.text; throw InvalidOperation("../../../src/lib/pgsql/pgsql_connection.cc" , 678, oss__.str().c_str()); } while (1) |
677 | << ", statement: " << statement.namedo { std::ostringstream oss__; oss__ << "executePreparedStatement:" << " expected: " << statement.nbparams << " parameters, given: " << in_bindings.size() << ", statement: " << statement.name << ", SQL: " << statement.text; throw InvalidOperation("../../../src/lib/pgsql/pgsql_connection.cc" , 678, oss__.str().c_str()); } while (1) |
678 | << ", SQL: " << statement.text)do { std::ostringstream oss__; oss__ << "executePreparedStatement:" << " expected: " << statement.nbparams << " parameters, given: " << in_bindings.size() << ", statement: " << statement.name << ", SQL: " << statement.text; throw InvalidOperation("../../../src/lib/pgsql/pgsql_connection.cc" , 678, oss__.str().c_str()); } while (1); |
679 | } |
680 | |
681 | const char* const* values = 0; |
682 | const int* lengths = 0; |
683 | const int* formats = 0; |
684 | if (statement.nbparams > 0) { |
685 | values = static_cast<const char* const*>(&in_bindings.values_[0]); |
686 | lengths = static_cast<const int *>(&in_bindings.lengths_[0]); |
687 | formats = static_cast<const int *>(&in_bindings.formats_[0]); |
688 | } |
689 | |
690 | PgSqlResultPtr result_set; |
691 | result_set.reset(new PgSqlResult(PQexecPrepared(conn_, statement.name, statement.nbparams, |
692 | values, lengths, formats, 0))); |
693 | |
694 | checkStatementError(*result_set, statement); |
695 | return (result_set); |
696 | } |
697 | |
698 | void |
699 | PgSqlConnection::selectQuery(PgSqlTaggedStatement& statement, |
700 | const PsqlBindArray& in_bindings, |
701 | ConsumeResultRowFun process_result_row) { |
702 | // Execute the prepared statement. |
703 | PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings); |
704 | |
705 | // Iterate over the returned rows and invoke the row consumption |
706 | // function on each one. |
707 | int rows = result_set->getRows(); |
708 | for (int row = 0; row < rows; ++row) { |
709 | try { |
710 | process_result_row(*result_set, row); |
711 | } catch (const std::exception& ex) { |
712 | // Rethrow the exception with a bit more data. |
713 | isc_throw(BadValue, ex.what() << ". Statement is <" <<do { std::ostringstream oss__; oss__ << ex.what() << ". Statement is <" << statement.text << ">" ; throw BadValue("../../../src/lib/pgsql/pgsql_connection.cc" , 714, oss__.str().c_str()); } while (1) |
714 | statement.text << ">")do { std::ostringstream oss__; oss__ << ex.what() << ". Statement is <" << statement.text << ">" ; throw BadValue("../../../src/lib/pgsql/pgsql_connection.cc" , 714, oss__.str().c_str()); } while (1); |
715 | } |
716 | } |
717 | } |
718 | |
719 | void |
720 | PgSqlConnection::insertQuery(PgSqlTaggedStatement& statement, |
721 | const PsqlBindArray& in_bindings) { |
722 | // Execute the prepared statement. |
723 | PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings); |
724 | } |
725 | |
726 | uint64_t |
727 | PgSqlConnection::updateDeleteQuery(PgSqlTaggedStatement& statement, |
728 | const PsqlBindArray& in_bindings) { |
729 | // Execute the prepared statement. |
730 | PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings); |
731 | |
732 | return (boost::lexical_cast<int>(PQcmdTuples(*result_set))); |
733 | } |
734 | |
735 | template<typename T> |
736 | void |
737 | PgSqlConnection::setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value) { |
738 | string svalue; |
739 | try { |
740 | svalue = getParameter(name); |
741 | } catch (...) { |
742 | // Do nothing if the parameter is not present. |
743 | } |
744 | if (svalue.empty()) { |
745 | return; |
746 | } |
747 | try { |
748 | // Try to convert the value. |
749 | auto parsed_value = boost::lexical_cast<T>(svalue); |
750 | // Check if the value is within the specified range. |
751 | if ((parsed_value < min) || (parsed_value > max)) { |
752 | isc_throw(BadValue, "bad " << svalue << " value")do { std::ostringstream oss__; oss__ << "bad " << svalue << " value"; throw BadValue("../../../src/lib/pgsql/pgsql_connection.cc" , 752, oss__.str().c_str()); } while (1); |
753 | } |
754 | // Everything is fine. Return the parsed value. |
755 | value = parsed_value; |
756 | |
757 | } catch (...) { |
758 | // We may end up here when lexical_cast fails or when the |
759 | // parsed value is not within the desired range. In both |
760 | // cases let's throw the same general error. |
761 | isc_throw(BadValue, name << " parameter (" <<do { std::ostringstream oss__; oss__ << name << " parameter (" << svalue << ") must be an integer between " << min << " and " << max; throw BadValue("../../../src/lib/pgsql/pgsql_connection.cc" , 763, oss__.str().c_str()); } while (1) |
762 | svalue << ") must be an integer between "do { std::ostringstream oss__; oss__ << name << " parameter (" << svalue << ") must be an integer between " << min << " and " << max; throw BadValue("../../../src/lib/pgsql/pgsql_connection.cc" , 763, oss__.str().c_str()); } while (1) |
763 | << min << " and " << max)do { std::ostringstream oss__; oss__ << name << " parameter (" << svalue << ") must be an integer between " << min << " and " << max; throw BadValue("../../../src/lib/pgsql/pgsql_connection.cc" , 763, oss__.str().c_str()); } while (1); |
764 | } |
765 | } |
766 | |
767 | |
768 | } // end of isc::db namespace |
769 | } // end of isc namespace |