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