Bug Summary

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

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-redhat-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name pgsql_connection.cc -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-private/tmphs1g5w79 -fcoverage-compilation-dir=/home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-private/tmphs1g5w79 -resource-dir /usr/bin/../lib/clang/20 -I src/lib/pgsql/libkea-pgsql.so.101.0.0.p -I src/lib/pgsql -I ../../../src/lib/pgsql -I . -I ../../.. -I src -I ../../../src -I src/bin -I ../../../src/bin -I src/lib -I ../../../src/lib -I /usr/include -D _GLIBCXX_ASSERTIONS=1 -D _FILE_OFFSET_BITS=64 -D BOOST_ALL_NO_LIB -D KEA_ADMIN="/usr/local/sbin/kea-admin" -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/15/../../../../include/c++/15 -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/15/../../../../include/c++/15/x86_64-redhat-linux -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/15/../../../../include/c++/15/backward -internal-isystem /usr/bin/../lib/clang/20/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/15/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -O0 -Wwrite-strings -Wno-missing-field-initializers -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -fcolor-diagnostics -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-logs/scanbuild/2025-10-17-063701-9203-1 -x c++ ../../../src/lib/pgsql/pgsql_connection.cc
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
22using namespace isc::asiolink;
23using namespace isc::data;
24using namespace std;
25
26namespace isc {
27namespace db {
28
29std::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
34const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
35
36// Length of error codes
37constexpr size_t PGSQL_STATECODE_LEN = 5;
38
39// Error codes from https://www.postgresql.org/docs/current/errcodes-appendix.html or utils/errcodes.h
40const char PgSqlConnection::DUPLICATE_KEY[] = "23505";
41const char PgSqlConnection::NULL_KEY[] = "23502";
42
43bool PgSqlConnection::warned_about_tls = false;
44
45PgSqlResult::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
60void
61PgSqlResult::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
68PgSqlResult::~PgSqlResult() {
69 if (result_) {
70 PQclear(result_);
71 }
72}
73
74void
75PgSqlResult::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
82void
83PgSqlResult::rowColCheck(int row, int col) const {
84 rowCheck(row);
85 colCheck(col);
86}
87
88std::string
89PgSqlResult::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
103PgSqlTransaction::PgSqlTransaction(PgSqlConnection& conn)
104 : conn_(conn), committed_(false) {
105 conn_.startTransaction();
106}
107
108PgSqlTransaction::~PgSqlTransaction() {
109 // If commit() wasn't explicitly called, rollback.
110 if (!committed_) {
111 conn_.rollback();
112 }
113}
114
115void
116PgSqlTransaction::commit() {
117 conn_.commit();
118 committed_ = true;
119}
120
121PgSqlConnection::~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
135std::pair<uint32_t, uint32_t>
136PgSqlConnection::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
167void
168PgSqlConnection::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
213void
214PgSqlConnection::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
248tuple<vector<string>, vector<string>>
249PgSqlConnection::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
295void
296PgSqlConnection::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
308void
309PgSqlConnection::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
318std::string
319PgSqlConnection::getConnParameters() {
320 return (getConnParametersInternal(false));
321}
322
323std::string
324PgSqlConnection::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
467void
468PgSqlConnection::openDatabase() {
469 openDatabaseInternal(true);
470}
471
472void
473PgSqlConnection::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
513bool
514PgSqlConnection::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
521void
522PgSqlConnection::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
575void
576PgSqlConnection::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
592bool
593PgSqlConnection::isTransactionStarted() const {
594 return (transaction_ref_count_ > 0);
595}
596
597void
598PgSqlConnection::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
617void
618PgSqlConnection::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
637void
638PgSqlConnection::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
648void
649PgSqlConnection::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
658void
659PgSqlConnection::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
668PgSqlResultPtr
669PgSqlConnection::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
698void
699PgSqlConnection::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
719void
720PgSqlConnection::insertQuery(PgSqlTaggedStatement& statement,
721 const PsqlBindArray& in_bindings) {
722 // Execute the prepared statement.
723 PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
724}
725
726uint64_t
727PgSqlConnection::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
735template<typename T>
736void
737PgSqlConnection::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