Bug Summary

File:home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-private/tmpz0p224zs/../../../src/lib/pgsql/pgsql_connection.cc
Warning:line 430, 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/tmpz0p224zs -fcoverage-compilation-dir=/home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-private/tmpz0p224zs -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-06-18-163926-5114-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
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 // Stop here. Initializing the schema won't work if we cannot create a connection to the
181 // database.
182 throw;
183 } catch (DbOpenErrorWithRetry const& exception) {
184 // Stop here. Initializing the schema won't work if we cannot create a connection to the
185 // database even as we are retrying.
186 throw;
187 } catch (DefaultCredential const& exception) {
188 // Stop here. Initializing the schema won't work if we cannot create a connection to the
189 // database due to default credentials being used.
190 throw;
191 } catch (exception const& exception) {
192 DB_LOG_WARN(PGSQL_INITIAL_CONNECTION_FAIL).arg(exception.what());
193
194 // Disable the recovery mechanism in test mode.
195 if (DatabaseConnection::test_mode_) {
196 throw;
197 }
198 // This failure may occur for a variety of reasons. We are looking at
199 // initializing schema as the only potential mitigation. We could narrow
200 // down on the error that would suggest an uninitialized schema
201 // which would sound something along the lines of
202 // "table schema_version does not exist", but we do not necessarily have
203 // to. If the error had another cause, it will fail again during
204 // initialization or during the subsequent version retrieval and that is
205 // fine, and the error should still be relevant.
206 initializeSchema(parameters);
207
208 // Retrieve again because the initial retrieval failed.
209 schema_version = getVersion(parameters, ac, cb, retry ? timer_name : string());
210 }
211
212 // Check that the versions match.
213 pair<uint32_t, uint32_t> const expected_version(PGSQL_SCHEMA_VERSION_MAJOR,
214 PGSQL_SCHEMA_VERSION_MINOR);
215 if (schema_version != expected_version) {
216 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", 219, oss__.str
().c_str()); } while (1)
217 << 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", 219, oss__.str
().c_str()); } while (1)
218 << ", 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", 219, oss__.str
().c_str()); } while (1)
219 << 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", 219, oss__.str
().c_str()); } while (1)
;
220 }
221}
222
223void
224PgSqlConnection::initializeSchema(const ParameterMap& parameters) {
225 if (parameters.count("readonly") && parameters.at("readonly") == "true") {
226 // The readonly flag is historically used for host backends. Still, if
227 // enabled, it is a strong indication that we should not meDDLe with it.
228 DB_LOG_WARN(PGSQL_NO_INIT_READONLY).arg();
229 return;
230 }
231
232 if (!isc::util::file::isFile(KEA_ADMIN_)) {
233 // It can happen for kea-admin to not exist, especially with
234 // packages that install it in a separate package.
235 DB_LOG_WARN(PGSQL_NO_INIT_NO_ADMIN).arg();
236 return;
237 }
238
239 // Convert parameters.
240 auto const tupl(toKeaAdminParameters(parameters));
241 vector<string> kea_admin_parameters(get<0>(tupl));
242 ProcessEnvVars const vars(get<1>(tupl));
243 kea_admin_parameters.insert(kea_admin_parameters.begin(), "db-init");
244
245 // Run.
246 ProcessSpawn kea_admin(ProcessSpawn::SYNC, KEA_ADMIN_, kea_admin_parameters, vars,
247 /* inherit_env = */ true);
248 DB_LOG_INFO(PGSQL_INITIALIZE_SCHEMA)
249 .arg(kea_admin.getCommandLine(std::unordered_set<std::string>{"--password"}));
250 pid_t const pid(kea_admin.spawn());
251 if (kea_admin.isRunning(pid)) {
252 isc_throw(SchemaInitializationFailed, "kea-admin still running")do { std::ostringstream oss__; oss__ << "kea-admin still running"
; throw SchemaInitializationFailed("../../../src/lib/pgsql/pgsql_connection.cc"
, 252, oss__.str().c_str()); } while (1)
;
253 }
254 int const exit_code(kea_admin.getExitStatus(pid));
255 if (exit_code != 0) {
256 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"
, 256, oss__.str().c_str()); } while (1)
;
257 }
258}
259
260tuple<vector<string>, vector<string>>
261PgSqlConnection::toKeaAdminParameters(ParameterMap const& params) {
262 vector<string> result{"pgsql"};
263 ProcessEnvVars vars;
264 for (auto const& p : params) {
265 string const& keyword(p.first);
266 string const& value(p.second);
267
268 // These Kea parameters are the same as the kea-admin parameters.
269 if (keyword == "user" ||
270 keyword == "password" ||
271 keyword == "host" ||
272 keyword == "port" ||
273 keyword == "name") {
274 result.push_back("--" + keyword);
275 result.push_back(value);
276 continue;
277 }
278
279 // These Kea parameters do not have a direct kea-admin equivalent.
280 // But they do have a psql client flag equivalent.
281 // We pass them to kea-admin using the --extra flag.
282 static unordered_map<string, string> conversions{
283 {"cert-file", "sslcert"},
284 {"key-file", "sslkey"},
285 {"trust-anchor", "sslrootcert"},
286 {"ssl-mode", "sslmode"},
287 };
288 if (conversions.count(keyword)) {
289 result.push_back("--extra");
290 result.push_back(conversions.at(keyword) + "=" + value);
291 }
292
293 // These Kea parameters do not have a direct kea-admin equivalent.
294 // But they do have a psql client environment variable equivalent.
295 // We pass them to kea-admin.
296 static unordered_map<string, string> env_conversions{
297 {"connect-timeout", "PGCONNECT_TIMEOUT"},
298 // {"tcp-user-timeout", "N/A"},
299 };
300 if (env_conversions.count(keyword)) {
301 vars.push_back(env_conversions.at(keyword) + "=" + value);
302 }
303 }
304 return make_tuple(result, vars);
305}
306
307void
308PgSqlConnection::prepareStatement(const PgSqlTaggedStatement& statement) {
309 // Prepare all statements queries with all known fields datatype
310 PgSqlResult r(PQprepare(conn_, statement.name, statement.text,
311 statement.nbparams, statement.types));
312 if (PQresultStatus(r) != PGRES_COMMAND_OK) {
313 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"
, 316, oss__.str().c_str()); } while (1)
314 << " 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"
, 316, oss__.str().c_str()); } while (1)
315 << ", 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"
, 316, oss__.str().c_str()); } while (1)
316 << ", 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"
, 316, oss__.str().c_str()); } while (1)
;
317 }
318}
319
320void
321PgSqlConnection::prepareStatements(const PgSqlTaggedStatement* start_statement,
322 const PgSqlTaggedStatement* end_statement) {
323 // Created the PostgreSQL prepared statements.
324 for (const PgSqlTaggedStatement* tagged_statement = start_statement;
325 tagged_statement != end_statement; ++tagged_statement) {
326 prepareStatement(*tagged_statement);
327 }
328}
329
330std::string
331PgSqlConnection::getConnParameters() {
332 return (getConnParametersInternal(false));
333}
334
335std::string
336PgSqlConnection::getConnParametersInternal(bool logging) {
337 string dbconnparameters;
338 string shost = "localhost";
339 try {
340 shost = getParameter("host");
341 } catch(...) {
342 // No host. Fine, we'll use "localhost"
343 }
344
345 dbconnparameters += "host = '" + shost + "'" ;
346
347 unsigned int port = 0;
348 try {
349 setIntParameterValue("port", 0, numeric_limits<uint16_t>::max(), port);
350
351 } catch (const std::exception& ex) {
352 isc_throw(DbInvalidPort, ex.what())do { std::ostringstream oss__; oss__ << ex.what(); throw
DbInvalidPort("../../../src/lib/pgsql/pgsql_connection.cc", 352
, oss__.str().c_str()); } while (1)
;
353 }
354
355 // Add port to connection parameters when not default.
356 if (port > 0) {
357 std::ostringstream oss;
358 oss << port;
359 dbconnparameters += " port = " + oss.str();
360 }
361
362 string suser;
363 try {
364 suser = getParameter("user");
365 dbconnparameters += " user = '" + suser + "'";
366 } catch(...) {
367 // No user. Fine, we'll use NULL
368 }
369
370 string spassword;
371 try {
372 spassword = getParameter("password");
373 dbconnparameters += " password = '" + spassword + "'";
374 } catch(...) {
375 // No password. Fine, we'll use NULL
376 }
377 if (!spassword.empty()) {
378 // Refuse default password.
379 DefaultCredentials::check(spassword);
380 }
381
382 string sname;
383 try {
384 sname = getParameter("name");
385 dbconnparameters += " dbname = '" + sname + "'";
386 } catch(...) {
387 // No database name. Throw a "NoDatabaseName" exception
388 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"
, 388, oss__.str().c_str()); } while (1)
;
389 }
390
391 unsigned int connect_timeout = PGSQL_DEFAULT_CONNECTION_TIMEOUT;
392 unsigned int tcp_user_timeout = 0;
393 try {
394 // The timeout is only valid if greater than zero, as depending on the
395 // database, a zero timeout might signify something like "wait
396 // indefinitely".
397 setIntParameterValue("connect-timeout", 1, numeric_limits<int>::max(), connect_timeout);
398 // This timeout value can be 0, meaning that the database client will
399 // follow a default behavior. Earlier Postgres versions didn't have
400 // this parameter, so we allow 0 to skip setting them for these
401 // earlier versions.
402 setIntParameterValue("tcp-user-timeout", 0, numeric_limits<int>::max(), tcp_user_timeout);
403
404 } catch (const std::exception& ex) {
405 isc_throw(DbInvalidTimeout, ex.what())do { std::ostringstream oss__; oss__ << ex.what(); throw
DbInvalidTimeout("../../../src/lib/pgsql/pgsql_connection.cc"
, 405, oss__.str().c_str()); } while (1)
;
406 }
407
408 // Append connection timeout.
409 std::ostringstream oss;
410 oss << " connect_timeout = " << connect_timeout;
411
412 if (tcp_user_timeout > 0) {
413// tcp_user_timeout parameter is a PostgreSQL 12+ feature.
414#ifdef HAVE_PGSQL_TCP_USER_TIMEOUT
415 oss << " tcp_user_timeout = " << tcp_user_timeout * 1000;
416 static_cast<void>(logging);
417#else
418 if (logging) {
419 DB_LOG_WARN(PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED).arg();
420 }
421#endif
422 }
423 dbconnparameters += oss.str();
424
425 bool tls = false;
426
427 string ssslmode;
428 try {
429 ssslmode = getParameter("ssl-mode");
430 tls = true;
Value stored to 'tls' is never read
431 } catch (...) {
432 // No strict ssl mode
433 }
434
435 string sca;
436 try {
437 sca = getParameter("trust-anchor");
438 tls = true;
439 if (ssslmode.empty()) {
440 ssslmode = "verify-ca";
441 }
442 dbconnparameters += " sslrootcert = " + sca;
443 } catch (...) {
444 // No trust anchor
445 }
446
447 string scert;
448 try {
449 scert = getParameter("cert-file");
450 tls = true;
451 dbconnparameters += " sslcert = " + scert;
452 } catch (...) {
453 // No client certificate file
454 }
455
456 string skey;
457 try {
458 skey = getParameter("key-file");
459 tls = true;
460 dbconnparameters += " sslkey = " + skey;
461 } catch (...) {
462 // No private key file
463 }
464
465 if (tls) {
466 if (ssslmode.empty()) {
467 ssslmode = "require";
468 }
469 dbconnparameters += " gssencmode = disable";
470 }
471
472 if (!ssslmode.empty()) {
473 dbconnparameters += " sslmode = " + ssslmode;
474 }
475
476 return (dbconnparameters);
477}
478
479void
480PgSqlConnection::openDatabase() {
481 openDatabaseInternal(true);
482}
483
484void
485PgSqlConnection::openDatabaseInternal(bool logging) {
486 std::string dbconnparameters = getConnParametersInternal(logging);
487 // Connect to PostgreSQL, saving the low level connection pointer
488 // in the holder object
489 PGconn* new_conn = PQconnectdb(dbconnparameters.c_str());
490 if (!new_conn) {
491 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"
, 491, oss__.str().c_str()); } while (1)
;
492 }
493
494 if (PQstatus(new_conn) != CONNECTION_OK) {
495 // Mark this connection as no longer usable.
496 markUnusable();
497
498 // If we have a connection object, we have to call finish
499 // to release it, but grab the error message first.
500 std::string error_message = PQerrorMessage(new_conn);
501 PQfinish(new_conn);
502
503 auto const& rec = reconnectCtl();
504 if (rec && DatabaseConnection::retry_) {
505
506 // Start the connection recovery.
507 startRecoverDbConnection();
508
509 std::ostringstream s;
510
511 s << " (scheduling retry " << rec->retryIndex() + 1 << " of " << rec->maxRetries() << " in " << rec->retryInterval() << " milliseconds)";
512
513 error_message += s.str();
514
515 isc_throw(DbOpenErrorWithRetry, error_message)do { std::ostringstream oss__; oss__ << error_message; throw
DbOpenErrorWithRetry("../../../src/lib/pgsql/pgsql_connection.cc"
, 515, oss__.str().c_str()); } while (1)
;
516 }
517
518 isc_throw(DbOpenError, error_message)do { std::ostringstream oss__; oss__ << error_message; throw
DbOpenError("../../../src/lib/pgsql/pgsql_connection.cc", 518
, oss__.str().c_str()); } while (1)
;
519 }
520
521 // We have a valid connection, so let's save it to our holder
522 conn_.setConnection(new_conn);
523}
524
525bool
526PgSqlConnection::compareError(const PgSqlResult& r, const char* error_state) {
527 const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE'C');
528 // PostgreSQL guarantees it will always be 5 characters long
529 return ((sqlstate != NULL__null) &&
530 (memcmp(sqlstate, error_state, PGSQL_STATECODE_LEN) == 0));
531}
532
533void
534PgSqlConnection::checkStatementError(const PgSqlResult& r,
535 PgSqlTaggedStatement& statement) {
536 int s = PQresultStatus(r);
537 if (s != PGRES_COMMAND_OK && s != PGRES_TUPLES_OK) {
538 // We're testing the first two chars of SQLSTATE, as this is the
539 // error class. Note, there is a severity field, but it can be
540 // misleadingly returned as fatal. However, a loss of connectivity
541 // can lead to a NULL sqlstate with a status of PGRES_FATAL_ERROR.
542 const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE'C');
543 if ((sqlstate == NULL__null) ||
544 ((memcmp(sqlstate, "08", 2) == 0) || // Connection Exception
545 (memcmp(sqlstate, "53", 2) == 0) || // Insufficient resources
546 (memcmp(sqlstate, "54", 2) == 0) || // Program Limit exceeded
547 (memcmp(sqlstate, "57", 2) == 0) || // Operator intervention
548 (memcmp(sqlstate, "58", 2) == 0))) { // System error
549 DB_LOG_ERROR(PGSQL_FATAL_ERROR)
550 .arg(statement.name)
551 .arg(PQerrorMessage(conn_))
552 .arg(sqlstate ? sqlstate : "<sqlstate null>");
553
554 // Mark this connection as no longer usable.
555 markUnusable();
556
557 // Start the connection recovery.
558 startRecoverDbConnection();
559
560 // We still need to throw so caller can error out of the current
561 // processing.
562 isc_throw(DbConnectionUnusable,do { std::ostringstream oss__; oss__ << "fatal database error or connectivity lost"
; throw DbConnectionUnusable("../../../src/lib/pgsql/pgsql_connection.cc"
, 563, oss__.str().c_str()); } while (1)
563 "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"
, 563, oss__.str().c_str()); } while (1)
;
564 }
565
566 // Failure: check for the special case of duplicate entry.
567 if (compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
568 isc_throw(DuplicateEntry, "statement: " << statement.namedo { std::ostringstream oss__; oss__ << "statement: " <<
statement.name << ", reason: " << PQerrorMessage
(conn_); throw DuplicateEntry("../../../src/lib/pgsql/pgsql_connection.cc"
, 569, oss__.str().c_str()); } while (1)
569 << ", reason: " << PQerrorMessage(conn_))do { std::ostringstream oss__; oss__ << "statement: " <<
statement.name << ", reason: " << PQerrorMessage
(conn_); throw DuplicateEntry("../../../src/lib/pgsql/pgsql_connection.cc"
, 569, oss__.str().c_str()); } while (1)
;
570 }
571
572 // Failure: check for the special case of null key violation.
573 if (compareError(r, PgSqlConnection::NULL_KEY)) {
574 isc_throw(NullKeyError, "statement: " << statement.namedo { std::ostringstream oss__; oss__ << "statement: " <<
statement.name << ", reason: " << PQerrorMessage
(conn_); throw NullKeyError("../../../src/lib/pgsql/pgsql_connection.cc"
, 575, oss__.str().c_str()); } while (1)
575 << ", reason: " << PQerrorMessage(conn_))do { std::ostringstream oss__; oss__ << "statement: " <<
statement.name << ", reason: " << PQerrorMessage
(conn_); throw NullKeyError("../../../src/lib/pgsql/pgsql_connection.cc"
, 575, oss__.str().c_str()); } while (1)
;
576 }
577
578 // Apparently it wasn't fatal, so we throw with a helpful message.
579 const char* error_message = PQerrorMessage(conn_);
580 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", 583, oss__.str
().c_str()); } while (1)
581 << 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", 583, oss__.str
().c_str()); } while (1)
582 << "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", 583, oss__.str
().c_str()); } while (1)
583 << " ], 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", 583, oss__.str
().c_str()); } while (1)
;
584 }
585}
586
587void
588PgSqlConnection::startTransaction() {
589 // If it is nested transaction, do nothing.
590 if (++transaction_ref_count_ > 1) {
591 return;
592 }
593
594 DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_START_TRANSACTION);
595 checkUnusable();
596 PgSqlResult r(PQexec(conn_, "START TRANSACTION"));
597 if (PQresultStatus(r) != PGRES_COMMAND_OK) {
598 const char* error_message = PQerrorMessage(conn_);
599 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"
, 600, oss__.str().c_str()); } while (1)
600 << error_message)do { std::ostringstream oss__; oss__ << "unable to start transaction"
<< error_message; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc"
, 600, oss__.str().c_str()); } while (1)
;
601 }
602}
603
604bool
605PgSqlConnection::isTransactionStarted() const {
606 return (transaction_ref_count_ > 0);
607}
608
609void
610PgSqlConnection::commit() {
611 if (transaction_ref_count_ <= 0) {
612 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"
, 612, oss__.str().c_str()); } while (1)
;
613 }
614
615 // When committing nested transaction, do nothing.
616 if (--transaction_ref_count_ > 0) {
617 return;
618 }
619
620 DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_COMMIT);
621 checkUnusable();
622 PgSqlResult r(PQexec(conn_, "COMMIT"));
623 if (PQresultStatus(r) != PGRES_COMMAND_OK) {
624 const char* error_message = PQerrorMessage(conn_);
625 isc_throw(DbOperationError, "commit failed: " << error_message)do { std::ostringstream oss__; oss__ << "commit failed: "
<< error_message; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc"
, 625, oss__.str().c_str()); } while (1)
;
626 }
627}
628
629void
630PgSqlConnection::rollback() {
631 if (transaction_ref_count_ <= 0) {
632 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"
, 632, oss__.str().c_str()); } while (1)
;
633 }
634
635 // When rolling back nested transaction, do nothing.
636 if (--transaction_ref_count_ > 0) {
637 return;
638 }
639
640 DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_ROLLBACK);
641 checkUnusable();
642 PgSqlResult r(PQexec(conn_, "ROLLBACK"));
643 if (PQresultStatus(r) != PGRES_COMMAND_OK) {
644 const char* error_message = PQerrorMessage(conn_);
645 isc_throw(DbOperationError, "rollback failed: " << error_message)do { std::ostringstream oss__; oss__ << "rollback failed: "
<< error_message; throw DbOperationError("../../../src/lib/pgsql/pgsql_connection.cc"
, 645, oss__.str().c_str()); } while (1)
;
646 }
647}
648
649void
650PgSqlConnection::createSavepoint(const std::string& name) {
651 if (transaction_ref_count_ <= 0) {
652 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"
, 652, oss__.str().c_str()); } while (1)
;
653 }
654
655 DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_CREATE_SAVEPOINT).arg(name);
656 std::string sql("SAVEPOINT " + name);
657 executeSQL(sql);
658}
659
660void
661PgSqlConnection::rollbackToSavepoint(const std::string& name) {
662 if (transaction_ref_count_ <= 0) {
663 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"
, 663, oss__.str().c_str()); } while (1)
;
664 }
665
666 std::string sql("ROLLBACK TO SAVEPOINT " + name);
667 executeSQL(sql);
668}
669
670void
671PgSqlConnection::executeSQL(const std::string& sql) {
672 // Use a TaggedStatement so we can call checkStatementError and ensure
673 // we detect connectivity issues properly.
674 PgSqlTaggedStatement statement({0, {OID_NONE}, "run-statement", sql.c_str()});
675 checkUnusable();
676 PgSqlResult r(PQexec(conn_, statement.text));
677 checkStatementError(r, statement);
678}
679
680PgSqlResultPtr
681PgSqlConnection::executePreparedStatement(PgSqlTaggedStatement& statement,
682 const PsqlBindArray& in_bindings) {
683 checkUnusable();
684
685 if (static_cast<size_t>(statement.nbparams) != in_bindings.size()) {
686 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"
, 690, oss__.str().c_str()); } while (1)
687 << " 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"
, 690, oss__.str().c_str()); } while (1)
688 << " 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"
, 690, oss__.str().c_str()); } while (1)
689 << ", 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"
, 690, oss__.str().c_str()); } while (1)
690 << ", 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"
, 690, oss__.str().c_str()); } while (1)
;
691 }
692
693 const char* const* values = 0;
694 const int* lengths = 0;
695 const int* formats = 0;
696 if (statement.nbparams > 0) {
697 values = static_cast<const char* const*>(&in_bindings.values_[0]);
698 lengths = static_cast<const int *>(&in_bindings.lengths_[0]);
699 formats = static_cast<const int *>(&in_bindings.formats_[0]);
700 }
701
702 PgSqlResultPtr result_set;
703 result_set.reset(new PgSqlResult(PQexecPrepared(conn_, statement.name, statement.nbparams,
704 values, lengths, formats, 0)));
705
706 checkStatementError(*result_set, statement);
707 return (result_set);
708}
709
710void
711PgSqlConnection::selectQuery(PgSqlTaggedStatement& statement,
712 const PsqlBindArray& in_bindings,
713 ConsumeResultRowFun process_result_row) {
714 // Execute the prepared statement.
715 PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
716
717 // Iterate over the returned rows and invoke the row consumption
718 // function on each one.
719 int rows = result_set->getRows();
720 for (int row = 0; row < rows; ++row) {
721 try {
722 process_result_row(*result_set, row);
723 } catch (const std::exception& ex) {
724 // Rethrow the exception with a bit more data.
725 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"
, 726, oss__.str().c_str()); } while (1)
726 statement.text << ">")do { std::ostringstream oss__; oss__ << ex.what() <<
". Statement is <" << statement.text << ">"
; throw BadValue("../../../src/lib/pgsql/pgsql_connection.cc"
, 726, oss__.str().c_str()); } while (1)
;
727 }
728 }
729}
730
731void
732PgSqlConnection::insertQuery(PgSqlTaggedStatement& statement,
733 const PsqlBindArray& in_bindings) {
734 // Execute the prepared statement.
735 PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
736}
737
738uint64_t
739PgSqlConnection::updateDeleteQuery(PgSqlTaggedStatement& statement,
740 const PsqlBindArray& in_bindings) {
741 // Execute the prepared statement.
742 PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
743
744 return (boost::lexical_cast<int>(PQcmdTuples(*result_set)));
745}
746
747template<typename T>
748void
749PgSqlConnection::setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value) {
750 string svalue;
751 try {
752 svalue = getParameter(name);
753 } catch (...) {
754 // Do nothing if the parameter is not present.
755 }
756 if (svalue.empty()) {
757 return;
758 }
759 try {
760 // Try to convert the value.
761 auto parsed_value = boost::lexical_cast<T>(svalue);
762 // Check if the value is within the specified range.
763 if ((parsed_value < min) || (parsed_value > max)) {
764 isc_throw(BadValue, "bad " << svalue << " value")do { std::ostringstream oss__; oss__ << "bad " <<
svalue << " value"; throw BadValue("../../../src/lib/pgsql/pgsql_connection.cc"
, 764, oss__.str().c_str()); } while (1)
;
765 }
766 // Everything is fine. Return the parsed value.
767 value = parsed_value;
768
769 } catch (...) {
770 // We may end up here when lexical_cast fails or when the
771 // parsed value is not within the desired range. In both
772 // cases let's throw the same general error.
773 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"
, 775, oss__.str().c_str()); } while (1)
774 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"
, 775, oss__.str().c_str()); } while (1)
775 << 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"
, 775, oss__.str().c_str()); } while (1)
;
776 }
777}
778
779
780} // end of isc::db namespace
781} // end of isc namespace