Kea 3.3.1
pgsql_connection.cc
Go to the documentation of this file.
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
14#include <database/db_log.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
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;
55
56// Default connection timeout
57
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
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: " << row
89 << ", out of range: 0.." << rows_);
90 }
91}
92
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: " << col
103 << ", out of range: 0.." << cols_);
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
129 : conn_(conn), committed_(false) {
130 conn_.startTransaction();
131}
132
134 // If commit() wasn't explicitly called, rollback.
135 if (!committed_) {
136 conn_.rollback();
137 }
138}
139
140void
142 conn_.commit();
143 committed_ = true;
144}
145
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.
154 .arg(PQerrorMessage(conn_));
155 }
156 }
157 }
158}
159
160std::pair<uint32_t, uint32_t>
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 <"
180 << version_sql << ", reason: " << PQerrorMessage(conn.conn_));
181 }
182
183 uint32_t version;
185
186 uint32_t minor;
187 PgSqlExchange::getColumnValue(r, 0, 1, minor);
188
189 return (make_pair(version, minor));
190}
191
192void
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
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) {
218
219 // Disable the recovery mechanism in 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,
240 if (schema_version != expected_version) {
241 isc_throw(DbOpenError, "PostgreSQL schema version mismatch: expected version: "
242 << expected_version.first << "." << expected_version.second
243 << ", found version: " << schema_version.first << "."
244 << schema_version.second);
245 }
246}
247
248void
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.
254 return;
255 }
256
257 if (parameters.count("password-file")) {
258 // Kea-admin does not support the password-file argument.
260 return;
261 }
262
264 // It can happen for kea-admin to not exist, especially with
265 // packages that install it in a separate package.
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);
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");
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);
288 }
289}
290
291tuple<vector<string>, vector<string>>
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
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: "
345 << " name: " << statement.name
346 << ", reason: " << PQerrorMessage(conn_)
347 << ", text: " << statement.text);
348 }
349}
350
351void
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
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());
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");
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());
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;
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
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");
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.
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);
559 }
560
561 isc_throw(DbOpenError, error_message);
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);
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
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);
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
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.
602
603 // We still need to throw so caller can error out of the current
604 // processing.
606 "fatal database error or connectivity lost");
607 }
608
609 // Failure: check for the special case of duplicate entry.
611 isc_throw(DuplicateEntry, "statement: " << statement.name
612 << ", reason: " << PQerrorMessage(conn_));
613 }
614
615 // Failure: check for the special case of null key violation.
617 isc_throw(NullKeyError, "statement: " << statement.name
618 << ", reason: " << PQerrorMessage(conn_));
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: "
624 << statement.name << ", status: " << s
625 << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>")
626 << " ], reason: " << error_message);
627 }
628}
629
630void
632 // If it is nested transaction, do nothing.
633 if (++transaction_ref_count_ > 1) {
634 return;
635 }
636
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"
643 << error_message);
644 }
645}
646
647bool
651
652void
654 if (transaction_ref_count_ <= 0) {
655 isc_throw(Unexpected, "commit called for not started transaction - coding error");
656 }
657
658 // When committing nested transaction, do nothing.
659 if (--transaction_ref_count_ > 0) {
660 return;
661 }
662
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);
669 }
670}
671
672void
674 if (transaction_ref_count_ <= 0) {
675 isc_throw(Unexpected, "rollback called for not started transaction - coding error");
676 }
677
678 // When rolling back nested transaction, do nothing.
679 if (--transaction_ref_count_ > 0) {
680 return;
681 }
682
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);
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);
696 }
697
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);
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()});
719 PgSqlResult r(PQexec(conn_, statement.text));
720 checkStatementError(r, statement);
721}
722
725 const PsqlBindArray& in_bindings) {
727
728 if (static_cast<size_t>(statement.nbparams) != in_bindings.size()) {
729 isc_throw (InvalidOperation, "executePreparedStatement:"
730 << " expected: " << statement.nbparams
731 << " parameters, given: " << in_bindings.size()
732 << ", statement: " << statement.name
733 << ", SQL: " << statement.text);
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
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 <" <<
769 statement.text << ">");
770 }
771 }
772}
773
774void
776 const PsqlBindArray& in_bindings) {
777 // Execute the prepared statement.
778 PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
779}
780
781uint64_t
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");
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 (" <<
817 svalue << ") must be an integer between "
818 << min << " and " << max);
819 }
820}
821
822
823} // end of isc::db namespace
824} // end of isc namespace
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
A generic exception that is thrown if a function is called in a prohibited way.
A generic exception that is thrown when an unexpected error condition occurs.
Exception thrown on attempt to use a default credential.
std::string getParameter(const std::string &name) const
Returns value of a connection parameter.
util::ReconnectCtlPtr reconnectCtl()
The reconnect settings.
virtual void makeReconnectCtl(const std::string &timer_name, unsigned int id)
Instantiates a ReconnectCtl based on the connection's reconnect parameters.
void markUnusable()
Sets the unusable flag to true.
static bool test_mode_
Test mode flag (default false).
static bool retry_
Flag which indicates if the database connection should be retried on fail.
void checkUnusable()
Throws an exception if the connection is not usable.
bool isUnusable()
Flag which indicates if connection is unusable.
static isc::asiolink::IOServicePtr & getIOService()
Returns pointer to the IO service.
std::map< std::string, std::string > ParameterMap
Database configuration parameter map.
Exception thrown when a specific connection has been rendered unusable either through loss of connect...
Exception thrown on failure to open database but permit retries.
Exception thrown on failure to open database.
Exception thrown on failure to execute a database function.
Database duplicate entry error.
Key is NULL but was specified NOT NULL.
Common PgSql Connector Pool.
static bool warned_about_tls
Emit the TLS support warning only once.
void startTransaction()
Starts new transaction.
void rollback()
Rollbacks current transaction.
void createSavepoint(const std::string &name)
Creates a savepoint within the current transaction.
uint64_t updateDeleteQuery(PgSqlTaggedStatement &statement, const PsqlBindArray &in_bindings)
Executes UPDATE or DELETE prepared statement and returns the number of affected rows.
int transaction_ref_count_
Reference counter for transactions.
void selectQuery(PgSqlTaggedStatement &statement, const PsqlBindArray &in_bindings, ConsumeResultRowFun process_result_row)
Executes SELECT query using prepared statement.
bool compareError(const PgSqlResult &r, const char *error_state)
Checks a result set's SQL state against an error state.
std::string getConnParameters()
Creates connection string from specified parameters.
static const char NULL_KEY[]
Define the PgSql error state for a null foreign key error.
std::function< void(PgSqlResult &, int)> ConsumeResultRowFun
Function invoked to process fetched row.
void prepareStatement(const PgSqlTaggedStatement &statement)
Prepare Single Statement.
static const char DUPLICATE_KEY[]
Define the PgSql error state for a duplicate key error.
static void ensureSchemaVersion(const ParameterMap &parameters, const DbCallback &cb=DbCallback(), const std::string &timer_name=std::string())
Retrieve schema version, validate it against the hardcoded version, and attempt to initialize the sch...
PgSqlResultPtr executePreparedStatement(PgSqlTaggedStatement &statement, const PsqlBindArray &in_bindings=PsqlBindArray())
Executes a prepared SQL statement.
bool isTransactionStarted() const
Checks if there is a transaction in progress.
static std::pair< uint32_t, uint32_t > getVersion(const ParameterMap &parameters, const IOServiceAccessorPtr &ac=IOServiceAccessorPtr(), const DbCallback &cb=DbCallback(), const std::string &timer_name=std::string(), unsigned int id=0)
Get the schema version.
static std::string KEA_ADMIN_
Holds location to kea-admin.
PgSqlHolder conn_
PgSql connection handle.
void rollbackToSavepoint(const std::string &name)
Rollbacks to the given savepoint.
static std::tuple< std::vector< std::string >, std::vector< std::string > > toKeaAdminParameters(ParameterMap const &params)
Convert PostgreSQL library parameters to kea-admin parameters.
static void initializeSchema(const ParameterMap &parameters)
Initialize schema.
void startRecoverDbConnection()
The recover connection.
void insertQuery(PgSqlTaggedStatement &statement, const PsqlBindArray &in_bindings)
Executes INSERT prepared statement.
void commit()
Commits current transaction.
void executeSQL(const std::string &sql)
Executes the an SQL statement.
virtual ~PgSqlConnection()
Destructor.
void checkStatementError(const PgSqlResult &r, PgSqlTaggedStatement &statement)
Checks result of the r object.
void prepareStatements(const PgSqlTaggedStatement *start_statement, const PgSqlTaggedStatement *end_statement)
Prepare statements.
void openDatabase()
Open database with logging.
PgSqlConnection(const ParameterMap &parameters, IOServiceAccessorPtr io_accessor=IOServiceAccessorPtr(), DbCallback callback=DbCallback())
Constructor.
static void getColumnValue(const PgSqlResult &r, const int row, const size_t col, std::string &value)
Fetches text column value as a string.
RAII wrapper for PostgreSQL Result sets.
void colCheck(int col) const
Determines if a column index is valid.
void rowCheck(int row) const
Determines if a row index is valid.
void rowColCheck(int row, int col) const
Determines if both a row and column index are valid.
std::string getColumnLabel(const int col) const
Fetches the name of the column in a result set.
PgSqlResult(PGresult *result)
Constructor.
PgSqlTransaction(PgSqlConnection &conn)
Constructor.
void commit()
Commits transaction.
Thrown when an initialization of the schema failed.
int version()
returns Kea hooks version.
We want to reuse the database backend connection and exchange code for other uses,...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
int get(CalloutHandle &handle)
The gss-tsig-get command.
const int DB_DBG_TRACE_DETAIL
Database logging levels.
Definition db_log.cc:21
const int PGSQL_DEFAULT_CONNECTION_TIMEOUT
@ PGSQL_NO_INIT_NO_PASSWORD
Definition db_log.h:57
@ PGSQL_CREATE_SAVEPOINT
Definition db_log.h:64
@ PGSQL_ROLLBACK
Definition db_log.h:63
@ PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED
Definition db_log.h:66
@ PGSQL_COMMIT
Definition db_log.h:62
@ PGSQL_NO_INIT_NO_ADMIN
Definition db_log.h:56
@ PGSQL_START_TRANSACTION
Definition db_log.h:61
@ PGSQL_FATAL_ERROR
Definition db_log.h:60
@ PGSQL_NO_INIT_READONLY
Definition db_log.h:58
@ PGSQL_INITIALIZE_SCHEMA
Definition db_log.h:55
@ PGSQL_DEALLOC_ERROR
Definition db_log.h:59
@ PGSQL_INITIAL_CONNECTION_FAIL
Definition db_log.h:54
boost::shared_ptr< PgSqlResult > PgSqlResultPtr
constexpr size_t PGSQL_STATECODE_LEN
boost::shared_ptr< IOServiceAccessor > IOServiceAccessorPtr
Pointer to an instance of IOServiceAccessor.
const size_t OID_NONE
Constants for PostgreSQL data types These are defined by PostgreSQL in <catalog/pg_type....
const uint32_t PGSQL_SCHEMA_VERSION_MINOR
std::function< bool(util::ReconnectCtlPtr db_reconnect_ctl)> DbCallback
Defines a callback prototype for propagating events upward.
std::function< isc::asiolink::IOServicePtr()> IOServiceAccessor
Function which returns the IOService that can be used to recover the connection.
const uint32_t PGSQL_SCHEMA_VERSION_MAJOR
Define the PostgreSQL backend version.
string getContent(string const &file_name)
Get the content of a regular file.
Definition filesystem.cc:33
bool isFile(string const &path)
Check if there is a file at the given path.
Definition filesystem.cc:80
Defines the logger used by the top-level component of kea-lfc.
static void check(const std::string &value)
Check if the value is a default credential.
DB_LOG & arg(T first, Args... args)
Pass parameters to replace logger placeholders.
Definition db_log.h:152
Define a PostgreSQL statement.
int nbparams
Number of parameters for a given query.
const char * text
Text representation of the actual query.
const char * name
Short name of the query.
const Oid types[PGSQL_MAX_PARAMETERS_IN_QUERY]
OID types.
std::vector< const char * > values_
Vector of pointers to the data values.
std::vector< int > formats_
Vector of "format" for each value.
size_t size() const
Fetches the number of entries in the array.
std::vector< int > lengths_
Vector of data lengths for each value.