summaryrefslogtreecommitdiffstats
path: root/src/lib/pgsql/pgsql_connection.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/pgsql/pgsql_connection.h')
-rw-r--r--src/lib/pgsql/pgsql_connection.h530
1 files changed, 530 insertions, 0 deletions
diff --git a/src/lib/pgsql/pgsql_connection.h b/src/lib/pgsql/pgsql_connection.h
new file mode 100644
index 0000000..65e8e42
--- /dev/null
+++ b/src/lib/pgsql/pgsql_connection.h
@@ -0,0 +1,530 @@
+// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#ifndef PGSQL_CONNECTION_H
+#define PGSQL_CONNECTION_H
+
+#include <asiolink/io_service.h>
+#include <pgsql/pgsql_exchange.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace db {
+
+/// @brief Define the PostgreSQL backend version.
+const uint32_t PGSQL_SCHEMA_VERSION_MAJOR = 13;
+const uint32_t PGSQL_SCHEMA_VERSION_MINOR = 0;
+
+// Maximum number of parameters that can be used a statement
+// @todo This allows us to use an initializer list (since we can't
+// require C++11). It's unlikely we'd go past this many a single
+// statement.
+const size_t PGSQL_MAX_PARAMETERS_IN_QUERY = 128;
+
+/// @brief Define a PostgreSQL statement.
+///
+/// Each statement is associated with an index, which is used to reference the
+/// associated prepared statement.
+struct PgSqlTaggedStatement {
+ /// Number of parameters for a given query
+ int nbparams;
+
+ /// @brief OID types
+ ///
+ /// Specify parameter types. See /usr/include/postgresql/catalog/pg_type.h.
+ /// For some reason that header does not export those parameters.
+ /// Those OIDs must match both input and output parameters.
+ const Oid types[PGSQL_MAX_PARAMETERS_IN_QUERY];
+
+ /// Short name of the query.
+ const char* name;
+
+ /// Text representation of the actual query.
+ const char* text;
+};
+
+/// @{
+/// @brief Constants for PostgreSQL data types
+/// These are defined by PostgreSQL in <catalog/pg_type.h>, but including
+/// this file is extraordinarily convoluted, so we'll use these to fill-in.
+/// @{
+const size_t OID_NONE = 0; // PostgreSQL infers proper type
+const size_t OID_BOOL = 16;
+const size_t OID_BYTEA = 17;
+const size_t OID_INT8 = 20; // 8 byte int
+const size_t OID_INT2 = 21; // 2 byte int
+const size_t OID_INT4 = 23; // 4 byte int
+const size_t OID_TEXT = 25;
+const size_t OID_VARCHAR = 1043;
+const size_t OID_TIMESTAMP = 1114;
+/// @}
+
+/// @brief Postgresql connection handle Holder
+///
+/// Small RAII object for safer initialization, will close the database
+/// connection upon destruction. This means that if an exception is thrown
+/// during database initialization, resources allocated to the database are
+/// guaranteed to be freed.
+///
+/// It makes no sense to copy an object of this class. After the copy, both
+/// objects would contain pointers to the same PgSql context object. The
+/// destruction of one would invalid the context in the remaining object.
+/// For this reason, the class is declared noncopyable.
+class PgSqlHolder : public boost::noncopyable {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Sets the Postgresql API connector handle to NULL.
+ ///
+ PgSqlHolder() : pgconn_(NULL) {
+ }
+
+ /// @brief Destructor
+ ///
+ /// Frees up resources allocated by the connection.
+ ~PgSqlHolder() {
+ if (pgconn_ != NULL) {
+ PQfinish(pgconn_);
+ }
+ }
+
+ /// @brief Sets the connection to the value given
+ ///
+ /// @param connection - pointer to the Postgresql connection instance
+ void setConnection(PGconn* connection) {
+ if (pgconn_ != NULL) {
+ // Already set? Release the current connection first.
+ // Maybe this should be an error instead?
+ PQfinish(pgconn_);
+ }
+
+ pgconn_ = connection;
+ }
+
+ /// @brief Conversion Operator
+ ///
+ /// Allows the PgSqlHolder object to be passed as the context argument to
+ /// PQxxxx functions.
+ operator PGconn*() const {
+ return (pgconn_);
+ }
+
+ /// @brief Boolean Operator
+ ///
+ /// Allows testing the connection for emptiness: "if (holder)"
+ operator bool() const {
+ return (pgconn_);
+ }
+
+private:
+ PGconn* pgconn_; ///< Postgresql connection
+};
+
+/// @brief Forward declaration to @ref PgSqlConnection.
+class PgSqlConnection;
+
+/// @brief RAII object representing a PostgreSQL transaction.
+///
+/// An instance of this class should be created in a scope where multiple
+/// INSERT statements should be executed within a single transaction. The
+/// transaction is started when the constructor of this class is invoked.
+/// The transaction is ended when the @ref PgSqlTransaction::commit is
+/// explicitly called or when the instance of this class is destroyed.
+/// The @ref PgSqlTransaction::commit commits changes to the database.
+/// If the class instance is destroyed before @ref PgSqlTransaction::commit
+/// has been called, the transaction is rolled back. The rollback on
+/// destruction guarantees that partial data is not stored in the database
+/// when an error occurs during any of the operations within a transaction.
+///
+/// By default PostgreSQL performs a commit following each statement which
+/// alters the database (i.e. "autocommit"). Starting a transaction
+/// stops autocommit for the connection until the transaction is ended by
+/// either commit or rollback. Other connections are unaffected.
+class PgSqlTransaction : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts transaction by executing the SQL statement: "START TRANSACTION"
+ ///
+ /// @param conn PostgreSQL connection to use for the transaction. This
+ /// connection will be later used to commit or rollback changes.
+ ///
+ /// @throw DbOperationError if statement execution fails
+ PgSqlTransaction(PgSqlConnection& conn);
+
+ /// @brief Destructor.
+ ///
+ /// If the transaction has not been committed, it is rolled back
+ /// by executing the SQL statement: "ROLLBACK"
+ ///
+ /// @throw DbOperationError if statement execution fails
+ ~PgSqlTransaction();
+
+ /// @brief Commits transaction.
+ ///
+ /// Commits all changes made during the transaction by executing the
+ /// SQL statement: "COMMIT"
+ ///
+ /// @throw DbOperationError if statement execution fails
+ void commit();
+
+private:
+
+ /// @brief Holds reference to the PostgreSQL database connection.
+ PgSqlConnection& conn_;
+
+ /// @brief Boolean flag indicating if the transaction has been committed.
+ ///
+ /// This flag is used in the class destructor to assess if the
+ /// transaction should be rolled back.
+ bool committed_;
+};
+
+/// @brief Common PgSql Connector Pool
+///
+/// This class provides common operations for PgSql database connection
+/// used by both PgSqlLeaseMgr and PgSqlHostDataSource. It manages connecting
+/// to the database and preparing compiled statements. Its fields are
+/// public, because they are used (both set and retrieved) in classes
+/// that use instances of PgSqlConnection.
+class PgSqlConnection : public db::DatabaseConnection {
+public:
+ /// @brief Define the PgSql error state for a duplicate key error.
+ static const char DUPLICATE_KEY[];
+ /// @brief Define the PgSql error state for a null foreign key error.
+ static const char NULL_KEY[];
+
+ /// @brief Function invoked to process fetched row.
+ typedef std::function<void(PgSqlResult&, int)> ConsumeResultRowFun;
+
+ /// @brief Emit the TLS support warning only once.
+ static bool warned_about_tls;
+
+ /// @brief Constructor
+ ///
+ /// Initialize PgSqlConnection object with parameters needed for connection.
+ ///
+ /// @param parameters Specify the connection details.
+ /// @param io_accessor The IOService accessor function.
+ /// @param callback The connection recovery callback.
+ PgSqlConnection(const ParameterMap& parameters,
+ IOServiceAccessorPtr io_accessor = IOServiceAccessorPtr(),
+ DbCallback callback = DbCallback())
+ : DatabaseConnection(parameters, callback),
+ io_service_accessor_(io_accessor), io_service_(),
+ transaction_ref_count_(0) {
+ }
+
+ /// @brief Destructor
+ virtual ~PgSqlConnection();
+
+ /// @brief Get the schema version.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @return Version number as a pair of unsigned integers. "first" is the
+ /// major version number, "second" the minor number.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ static std::pair<uint32_t, uint32_t>
+ getVersion(const ParameterMap& parameters);
+
+ /// @brief Prepare Single Statement
+ ///
+ /// Creates a prepared statement from the text given and adds it to the
+ /// statements_ vector at the given index.
+ ///
+ /// @param statement SQL statement to be prepared.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ void prepareStatement(const PgSqlTaggedStatement& statement);
+
+ /// @brief Prepare statements
+ ///
+ /// Creates the prepared statements for all of the SQL statements used
+ /// by the PostgreSQL backend.
+ ///
+ /// @param start_statement Pointer to the first statement in range of the
+ /// statements to be compiled.
+ /// @param end_statement Pointer to the statement marking end of the
+ /// range of statements to be compiled. This last statement is not compiled.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ void prepareStatements(const PgSqlTaggedStatement* start_statement,
+ const PgSqlTaggedStatement* end_statement);
+
+ /// @brief Open Database
+ ///
+ /// Opens the database using the information supplied in the parameters
+ /// passed to the constructor.
+ ///
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbOpenError Error opening the database
+ void openDatabase();
+
+ /// @brief Starts new transaction
+ ///
+ /// This function begins a new transaction by sending the START TRANSACTION
+ /// statement to the database. The transaction should be explicitly committed
+ /// by calling @c commit() or rolled back by calling @c rollback().
+ ///
+ /// PostgreSQL does not support nested transactions directly. Issuing a
+ /// START TRANSACTION while already in a transaction will cause a warning to
+ /// be emitted but otherwise does not alter the state of the current transaction.
+ /// In other words, the transaction will still end upon the next COMMIT or
+ /// ROLLBACK statement.
+ ///
+ /// Therefore, this function checks if a transaction has already started and
+ /// does not start a new transaction. However, it increments a transaction
+ /// reference counter which is later decremented when @c commit() or @c
+ /// rollback() is called. When this mechanism is used properly, it
+ /// guarantees that nested transactions are not attempted, thus avoiding
+ /// unexpected commits or rollbacks of the pending transaction.
+ void startTransaction();
+
+ /// @brief Checks if there is a transaction in progress.
+ ///
+ /// @return true if a transaction has been started, false otherwise.
+ bool isTransactionStarted() const;
+
+ /// @brief Commits current transaction
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// When this method is called for a nested transaction it decrements the
+ /// transaction reference counter incremented during the call to
+ /// @c startTransaction.
+ ///
+ /// @throw DbOperationError If the commit failed.
+ void commit();
+
+ /// @brief Rollbacks current transaction
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// When this method is called for a nested transaction it decrements the
+ /// transaction reference counter incremented during the call to
+ /// @c startTransaction.
+ ///
+ /// @throw DbOperationError If the rollback failed.
+ void rollback();
+
+ /// @brief Creates a savepoint within the current transaction
+ ///
+ /// Creates a named savepoint within the current transaction.
+ ///
+ /// @param name name of the savepoint to create.
+ ///
+ /// @throw InvalidOperation if called outside a transaction.
+ /// @throw DbOperationError If the savepoint cannot be created.
+ void createSavepoint(const std::string& name);
+
+ /// @brief Rollbacks to the given savepoint
+ ///
+ /// Rolls back all pending database operations made after the
+ /// named savepoint.
+ ///
+ /// @param name name of the savepoint to which to rollback.
+ ///
+ /// @throw InvalidOperation if called outside a transaction.
+ /// @throw DbOperationError if the rollback failed.
+ void rollbackToSavepoint(const std::string& name);
+
+ /// @brief Executes the an SQL statement.
+ ///
+ /// It executes the given SQL text after first checking the
+ /// connection for usability. After the statement is executed
+ /// @c checkStatementError() is invoked to ensure we detect
+ /// connectivity issues properly.
+ /// It is intended to be used to execute utility statements such
+ /// as commit, rollback et al, which have no parameters, return no
+ /// results, and are not pre-compiled.
+ ///
+ /// @param sql SQL statement to execute.
+ void executeSQL(const std::string& sql);
+
+ /// @brief Checks a result set's SQL state against an error state.
+ ///
+ /// @param r result set to check
+ /// @param error_state error state to compare against
+ ///
+ /// @return True if the result set's SQL state equals the error_state,
+ /// false otherwise.
+ bool compareError(const PgSqlResult& r, const char* error_state);
+
+ /// @brief Checks result of the r object
+ ///
+ /// This function is used to determine whether or not the SQL statement
+ /// execution succeeded, and in the event of failures, decide whether or
+ /// not the failures are recoverable.
+ ///
+ /// If the error is recoverable, the function will throw a DbOperationError.
+ /// If the error is deemed unrecoverable, such as a loss of connectivity
+ /// with the server, the function will call startRecoverDbConnection() which
+ /// will start the connection recovery.
+ ///
+ /// If the invocation returns true, this indicates the calling layer will
+ /// attempt recovery, and the function throws a DbOperationError to allow
+ /// the caller to error handle the failed db access attempt.
+ ///
+ /// @param r result of the last PostgreSQL operation
+ /// @param statement - tagged statement that was executed
+ ///
+ /// @throw isc::db::DbOperationError Detailed PostgreSQL failure
+ void checkStatementError(const PgSqlResult& r,
+ PgSqlTaggedStatement& statement);
+
+ /// @brief The recover connection
+ ///
+ /// This function starts the recover process of the connection.
+ ///
+ /// @note The recover function must be run on the IO Service thread.
+ void startRecoverDbConnection() {
+ if (callback_) {
+ if (!io_service_ && io_service_accessor_) {
+ io_service_ = (*io_service_accessor_)();
+ io_service_accessor_.reset();
+ }
+
+ if (io_service_) {
+ io_service_->post(std::bind(callback_, reconnectCtl()));
+ }
+ }
+ }
+
+ /// @brief Executes a prepared SQL statement.
+ ///
+ /// It executes the given prepared SQL statement, after checking
+ /// for usability and input parameter sanity. After the statement
+ /// is executed @c checkStatementError() is invoked to ensure we detect
+ /// connectivity issues properly. Upon successful execution, the
+ /// the result set is returned. It may be used for any form of
+ /// prepared SQL statement (e.g query, insert, update, delete...),
+ /// with or without input parameters.
+ ///
+ /// @param statement PgSqlTaggedStatement describing the prepared
+ /// statement to execute.
+ /// @param in_bindings array of input parameter bindings. If the SQL
+ /// statement requires no input arguments, this parameter should either
+ /// be omitted or an empty PsqlBindArray should be supplied.
+ /// @throw InvalidOperation if the number of parameters expected
+ /// by the statement does not match the size of the input bind array.
+ PgSqlResultPtr executePreparedStatement(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings
+ = PsqlBindArray());
+
+ /// @brief Executes SELECT query using prepared statement.
+ ///
+ /// The statement parameter refers to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// This method executes prepared statement using provided input bindings and
+ /// calls @c process_result_row function for each returned row. The
+ /// @c process_result_row function is implemented by the caller and should
+ /// gather and store each returned row in an external data structure prior.
+ ///
+ /// @param statement reference to the precompiled tagged statement to execute
+ /// @param in_bindings input bindings holding values to substitue placeholders
+ /// in the query.
+ /// @param process_result_row Pointer to the function to be invoked for each
+ /// retrieved row. This function consumes the retrieved data from the
+ /// result set.
+ void selectQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings,
+ ConsumeResultRowFun process_result_row);
+
+ /// @brief Executes INSERT prepared statement.
+ ///
+ /// The @c statement must refer to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// This method executes prepared statement using provided bindings to
+ /// insert data into the database.
+ ///
+ /// @param statement reference to the precompiled tagged statement to execute
+ /// @param in_bindings input bindings holding values to substitue placeholders
+ /// in the query.
+ void insertQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings);
+
+
+ /// @brief Executes UPDATE or DELETE prepared statement and returns
+ /// the number of affected rows.
+ ///
+ /// The @c statement must refer to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// @param statement reference to the precompiled tagged statement to execute
+ /// @param in_bindings Input bindings holding values to substitute placeholders
+ /// in the query.
+ ///
+ /// @return Number of affected rows.
+ uint64_t updateDeleteQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings);
+
+ /// @brief PgSql connection handle
+ ///
+ /// This field is public, because it is used heavily from PgSqlLeaseMgr
+ /// and from PgSqlHostDataSource.
+ PgSqlHolder conn_;
+
+ /// @brief Conversion Operator
+ ///
+ /// Allows the PgConnection object to be passed as the context argument to
+ /// PQxxxx functions.
+ operator PGconn*() const {
+ return (conn_);
+ }
+
+ /// @brief Boolean Operator
+ ///
+ /// Allows testing the PgConnection for initialized connection
+ operator bool() const {
+ return (conn_);
+ }
+
+ /// @brief Accessor function which returns the IOService that can be used to
+ /// recover the connection.
+ ///
+ /// This accessor is used to lazy retrieve the IOService when the connection
+ /// is lost. It is useful to retrieve it at a later time to support hook
+ /// libraries which create managers on load and set IOService later on by
+ /// using the dhcp4_srv_configured and dhcp6_srv_configured hooks.
+ IOServiceAccessorPtr io_service_accessor_;
+
+ /// @brief IOService object, used for all ASIO operations.
+ isc::asiolink::IOServicePtr io_service_;
+
+ /// @brief Reference counter for transactions.
+ ///
+ /// It precludes starting and committing nested transactions. PostgreSQL
+ /// logs but ignores START TRANSACTIONs (or BEGINs) issued from within an
+ /// ongoing transaction. We do not want to start new transactions when one
+ /// is already in progress.
+ int transaction_ref_count_;
+};
+
+/// @brief Defines a pointer to a PgSqlConnection
+typedef boost::shared_ptr<PgSqlConnection> PgSqlConnectionPtr;
+
+} // end of isc::db namespace
+} // end of isc namespace
+
+#endif // PGSQL_CONNECTION_H