Bug Summary

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