Parcourir la source

[4276] Initial impl of PgSqlConnection class

    Initial refactoring of Postgresql connection logic out of
    PgSqlLeaseMgr into new PgSqlConnection.
Thomas Markwalder il y a 9 ans
Parent
commit
b1c67df1e8

+ 1 - 0
src/lib/dhcpsrv/Makefile.am

@@ -133,6 +133,7 @@ endif
 libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
 
 if HAVE_PGSQL
+libkea_dhcpsrv_la_SOURCES += pgsql_connection.cc pgsql_connection.h
 libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h
 endif
 libkea_dhcpsrv_la_SOURCES += pool.cc pool.h

+ 181 - 0
src/lib/dhcpsrv/pgsql_connection.cc

@@ -0,0 +1,181 @@
+// Copyright (C) 2016 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/.
+
+#include <config.h>
+
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/pgsql_connection.h>
+
+#include <boost/static_assert.hpp>
+
+#include <iostream>
+#include <iomanip>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <time.h>
+
+// PostgreSQL errors should be tested based on the SQL state code.  Each state
+// code is 5 decimal, ASCII, digits, the first two define the category of
+// error, the last three are the specific error.  PostgreSQL makes the state
+// code as a char[5].  Macros for each code are defined in PostgreSQL's
+// server/utils/errcodes.h, although they require a second macro, 
+// MAKE_SQLSTATE for completion.  For example, duplicate key error as:
+//
+// #define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3','5','0','5')
+// 
+// PostgreSQL deliberately omits the MAKE_SQLSTATE macro so callers can/must 
+// supply their own.  We'll define it as an initlizer_list:
+#define MAKE_SQLSTATE(ch1,ch2,ch3,ch4,ch5) {ch1,ch2,ch3,ch4,ch5}
+// So we can use it like this: const char some_error[] = ERRCODE_xxxx; 
+#define PGSQL_STATECODE_LEN 5
+#include <utils/errcodes.h>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
+
+PgSqlConnection::~PgSqlConnection() {
+    if (conn_) {
+        // Deallocate the prepared queries.
+        PgSqlResult r(PQexec(conn_, "DEALLOCATE all"));
+        if(PQresultStatus(r) != PGRES_COMMAND_OK) {
+            // Highly unlikely but we'll log it and go on.
+            LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_DEALLOC_ERROR)
+                      .arg(PQerrorMessage(conn_));
+        }
+    }
+}
+
+void
+PgSqlConnection::prepareStatement(const PgSqlTaggedStatement& statement) {
+    // Prepare all statements queries with all known fields datatype
+    PgSqlResult r(PQprepare(conn_, statement.name, statement.text,
+                            statement.nbparams, statement.types));
+    if(PQresultStatus(r) != PGRES_COMMAND_OK) {
+        isc_throw(DbOperationError, "unable to prepare PostgreSQL statement: "
+                  << statement.text << ", reason: " << PQerrorMessage(conn_));
+    }
+}
+
+void
+PgSqlConnection::openDatabase() {
+    string dbconnparameters;
+    string shost = "localhost";
+    try {
+        shost = getParameter("host");
+    } catch(...) {
+        // No host. Fine, we'll use "localhost"
+    }
+
+    dbconnparameters += "host = '" + shost + "'" ;
+
+    string suser;
+    try {
+        suser = getParameter("user");
+        dbconnparameters += " user = '" + suser + "'";
+    } catch(...) {
+        // No user. Fine, we'll use NULL
+    }
+
+    string spassword;
+    try {
+        spassword = getParameter("password");
+        dbconnparameters += " password = '" + spassword + "'";
+    } catch(...) {
+        // No password. Fine, we'll use NULL
+    }
+
+    string sname;
+    try {
+        sname = getParameter("name");
+        dbconnparameters += " dbname = '" + sname + "'";
+    } catch(...) {
+        // No database name.  Throw a "NoDatabaseName" exception
+        isc_throw(NoDatabaseName, "must specify a name for the database");
+    }
+
+    // Connect to Postgres, saving the low level connection pointer 
+    // in the holder object
+    PGconn* new_conn = PQconnectdb(dbconnparameters.c_str());
+    if (!new_conn) {
+        isc_throw(DbOpenError, "could not allocate connection object");
+    }
+
+    if (PQstatus(new_conn) != CONNECTION_OK) {
+        // If we have a connection object, we have to call finish
+        // to release it, but grab the error message first.
+        std::string error_message = PQerrorMessage(new_conn);
+        PQfinish(new_conn);
+        isc_throw(DbOpenError, error_message);
+    }
+
+    // We have a valid connection, so let's save it to our holder
+    conn_.setConnection(new_conn);
+}
+
+bool 
+PgSqlConnection::compareError(PGresult*& r, const char* error_state) {
+    const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
+    // PostgreSQL garuantees it will always be 5 characters long
+    return ((sqlstate != NULL) &&
+            (memcmp(sqlstate, error_state, PGSQL_STATECODE_LEN) == 0));
+}
+
+void
+PgSqlConnection::checkStatementError(PGresult*& r, 
+                                     PgSqlTaggedStatement& statement) const {
+    int s = PQresultStatus(r);
+    if (s != PGRES_COMMAND_OK && s != PGRES_TUPLES_OK) {
+        // We're testing the first two chars of SQLSTATE, as this is the
+        // error class. Note, there is a severity field, but it can be
+        // misleadingly returned as fatal.
+        const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
+        if ((sqlstate != NULL) &&
+            ((memcmp(sqlstate, "08", 2) == 0) ||  // Connection Exception
+             (memcmp(sqlstate, "53", 2) == 0) ||  // Insufficient resources
+             (memcmp(sqlstate, "54", 2) == 0) ||  // Program Limit exceeded
+             (memcmp(sqlstate, "57", 2) == 0) ||  // Operator intervention
+             (memcmp(sqlstate, "58", 2) == 0))) { // System error
+            LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_FATAL_ERROR)
+                         .arg(statement.name)
+                         .arg(PQerrorMessage(conn_))
+                         .arg(sqlstate);
+            exit (-1);
+        }
+
+        const char* error_message = PQerrorMessage(conn_);
+        isc_throw(DbOperationError, "Statement exec failed:" << " for: "
+                  << statement.name << ", reason: "
+                  << error_message);
+    }
+}
+
+void
+PgSqlConnection::commit() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_COMMIT);
+    PgSqlResult r(PQexec(conn_, "COMMIT"));
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+        const char* error_message = PQerrorMessage(conn_);
+        isc_throw(DbOperationError, "commit failed: " << error_message);
+    }
+}
+
+void
+PgSqlConnection::rollback() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ROLLBACK);
+    PgSqlResult r(PQexec(conn_, "ROLLBACK"));
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+        const char* error_message = PQerrorMessage(conn_);
+        isc_throw(DbOperationError, "rollback failed: " << error_message);
+    }
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 296 - 0
src/lib/dhcpsrv/pgsql_connection.h

@@ -0,0 +1,296 @@
+// Copyright (C) 2016 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 <dhcpsrv/database_connection.h>
+
+#include <libpq-fe.h>
+#include <boost/scoped_ptr.hpp>
+
+#include <vector>
+
+
+namespace isc {
+namespace dhcp {
+
+// Maximum number of parameters that can be used a statement
+// @todo This allows us to use an initializer list (since we don't
+// require C++11).  It's unlikely we'd go past in a single statement.
+const size_t PGSQL_MAX_PARAMETERS_IN_QUERY = 32;
+
+/// @brief  Defines a Postgresql SQL 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
+/// This are defined by PostreSQL in <catalog/pg_type.h>, but including
+/// this file is extrordinarily 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_TIMESTAMP = 1114;
+const size_t OID_VARCHAR = 1043;
+
+//@}
+
+/// @brief RAII wrapper for Posgtresql Result sets
+///
+/// When a Postgresql statement is executed, the results are returned
+/// in pointer allocated structure, PGresult*. Data and status information
+/// are accessed via calls to functions such as PQgetvalue() which require
+/// the results pointer.  In order to ensure this structure is freed, any
+/// invocation of Psql function which returns a PGresult* (e.g. PQexec and
+
+/// class. Examples:
+/// {{{
+///       PgSqlResult r(PQexec(conn_, "ROLLBACK"));
+/// }}}
+///
+/// This eliminates the need for an explicit release via, PQclear() and
+/// guarantees that the resources are released even if the an exception is
+/// thrown.
+
+class PgSqlResult {
+public:
+    /// @brief Constructor
+    ///
+    /// Store the pointer to the result set to being fetched.
+    ///
+    PgSqlResult(PGresult *result) : result_(result)
+    {}
+
+    /// @brief Destructor
+    ///
+    /// Frees the result set
+    ~PgSqlResult() {
+        if (result_)  {
+            PQclear(result_);
+        }
+    }
+
+    /// @brief Conversion Operator
+    ///
+    /// Allows the PgSqlResult object to be passed as the context argument to
+    /// PQxxxx functions.
+    operator PGresult*() const {
+        return (result_);
+    }
+
+    /// @brief Boolean Operator
+    ///
+    /// Allows testing the PgSqlResult object for emptiness: "if (result)"
+    operator bool() const {
+        return (result_);
+    }
+
+
+private:
+    PGresult*     result_;     ///< Result set to be freed
+};
+
+
+/// @brief PgSql 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
+    ///
+    /// Initialize PgSql
+    ///
+    PgSqlHolder() : pgconn_(NULL) {
+    }
+
+    /// @brief Destructor
+    ///
+    /// Frees up resources allocated by the connection.
+    ~PgSqlHolder() {
+        if (pgconn_ != NULL) {
+            PQfinish(pgconn_);
+        }
+    }
+
+    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 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 DatabaseConnection {
+public:
+    /// @brief Defines the PgSql error state for a duplicate key error
+    static const char DUPLICATE_KEY[];
+
+    /// @brief Constructor
+    ///
+    /// Initialize PgSqlConnection object with parameters needed for connection.
+    PgSqlConnection(const ParameterMap& parameters)
+        : DatabaseConnection(parameters) {
+    }
+
+    /// @brief Destructor
+    virtual ~PgSqlConnection();
+
+    /// @brief Prepare Single Statement
+    ///
+    /// Creates a prepared statement from the text given and adds it to the
+    /// statements_ vector at the given index.
+    ///
+    /// @param index Index into the statements_ vector into which the text
+    ///        should be placed.  The vector must be big enough for the index
+    ///        to be valid, else an exception will be thrown.
+    /// @param text Text of the SQL statement to be prepared.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    void prepareStatement(const PgSqlTaggedStatement& 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 Commit Transactions
+    ///
+    /// Commits all pending database operations. On databases that don't
+    /// support transactions, this is a no-op.
+    ///
+    /// @throw DbOperationError If the commit failed.
+    void commit();
+
+    /// @brief Rollback Transactions
+    ///
+    /// Rolls back all pending database operations. On databases that don't
+    /// support transactions, this is a no-op.
+    ///
+    /// @throw DbOperationError If the rollback failed.
+    void rollback();
+
+    /// @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(PGresult*& 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 method will throw a DbOperationError.
+    /// In the error is deemed unrecoverable, such as a loss of connectivity
+    /// with the server, this method will log the error and call exit(-1);
+    ///
+    /// @todo Calling exit() is viewed as a short term solution for Kea 1.0.
+    /// Two tickets are likely to alter this behavior, first is #3639, which
+    /// calls for the ability to attempt to reconnect to the database. The
+    /// second ticket, #4087 which calls for the implementation of a generic,
+    /// FatalException class which will propagate outward.
+    ///
+    /// @param r result of the last PostgreSQL operation
+    /// @param statement - tagged statement that was executed
+    ///
+    /// @throw isc::dhcp::DbOperationError Detailed PostgreSQL failure
+    void checkStatementError(PGresult*& r, PgSqlTaggedStatement& statement) const;
+
+    /// @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_);
+    }
+
+};
+
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // PGSQL_CONNECTION_H

+ 13 - 193
src/lib/dhcpsrv/pgsql_lease_mgr.cc

@@ -21,64 +21,16 @@
 #include <string>
 #include <time.h>
 
-// PostgreSQL errors should be tested based on the SQL state code.  Each state
-// code is 5 decimal, ASCII, digits, the first two define the category of
-// error, the last three are the specific error.  PostgreSQL makes the state
-// code as a char[5].  Macros for each code are defined in PostgreSQL's
-// errorcodes.h, although they require a second macro, MAKE_SQLSTATE for
-// completion.  PostgreSQL deliberately omits this macro from errocodes.h
-// so callers can supply their own.
-#define MAKE_SQLSTATE(ch1,ch2,ch3,ch4,ch5) {ch1,ch2,ch3,ch4,ch5}
-#include <utils/errcodes.h>
-const size_t STATECODE_LEN = 5;
-
-// Currently the only one we care to look for is duplicate key.
-const char DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
-
 using namespace isc;
 using namespace isc::dhcp;
 using namespace std;
 
 namespace {
 
-// Maximum number of parameters used in any single query
-const size_t MAX_PARAMETERS_IN_QUERY = 14;
-
-/// @brief  Defines a single query
-struct TaggedStatement {
-
-    /// 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[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
-/// This are defined by PostreSQL in <catalog/pg_type.h>, but including
-/// this file is extrordinarily 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_TIMESTAMP = 1114;
-const size_t OID_VARCHAR = 1043;
-
 /// @brief Catalog of all the SQL statements currently supported.  Note
 /// that the order columns appear in statement body must match the order they
 /// that the occur in the table.  This does not apply to the where clause.
-TaggedStatement tagged_statements[] = {
+PgSqlTaggedStatement tagged_statements[] = {
     // DELETE_LEASE4
     { 1, { OID_INT8 },
       "delete_lease4",
@@ -1039,25 +991,12 @@ private:
 
 PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
     : LeaseMgr(), exchange4_(new PgSqlLease4Exchange()),
-    exchange6_(new PgSqlLease6Exchange()), dbconn_(parameters), conn_(NULL) {
-    openDatabase();
+    exchange6_(new PgSqlLease6Exchange()), conn_(parameters) {
+    conn_.openDatabase();
     prepareStatements();
 }
 
 PgSqlLeaseMgr::~PgSqlLeaseMgr() {
-    if (conn_) {
-        // Deallocate the prepared queries.
-        PGresult* r = PQexec(conn_, "DEALLOCATE all");
-        if(PQresultStatus(r) != PGRES_COMMAND_OK) {
-            // Highly unlikely but we'll log it and go on.
-            LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_DEALLOC_ERROR)
-                      .arg(PQerrorMessage(conn_));
-        }
-
-        PQclear(r);
-        PQfinish(conn_);
-        conn_ = NULL;
-    }
 }
 
 std::string
@@ -1072,73 +1011,7 @@ PgSqlLeaseMgr::getDBVersion() {
 void
 PgSqlLeaseMgr::prepareStatements() {
     for(int i = 0; tagged_statements[i].text != NULL; ++ i) {
-        // Prepare all statements queries with all known fields datatype
-        PGresult* r = PQprepare(conn_, tagged_statements[i].name,
-                                tagged_statements[i].text,
-                                tagged_statements[i].nbparams,
-                                tagged_statements[i].types);
-
-        if(PQresultStatus(r) != PGRES_COMMAND_OK) {
-            PQclear(r);
-            isc_throw(DbOperationError,
-                      "unable to prepare PostgreSQL statement: "
-                      << tagged_statements[i].text << ", reason: "
-                      << PQerrorMessage(conn_));
-        }
-
-        PQclear(r);
-    }
-}
-
-void
-PgSqlLeaseMgr::openDatabase() {
-    string dbconnparameters;
-    string shost = "localhost";
-    try {
-        shost = dbconn_.getParameter("host");
-    } catch(...) {
-        // No host. Fine, we'll use "localhost"
-    }
-
-    dbconnparameters += "host = '" + shost + "'" ;
-
-    string suser;
-    try {
-        suser = dbconn_.getParameter("user");
-        dbconnparameters += " user = '" + suser + "'";
-    } catch(...) {
-        // No user. Fine, we'll use NULL
-    }
-
-    string spassword;
-    try {
-        spassword = dbconn_.getParameter("password");
-        dbconnparameters += " password = '" + spassword + "'";
-    } catch(...) {
-        // No password. Fine, we'll use NULL
-    }
-
-    string sname;
-    try {
-        sname= dbconn_.getParameter("name");
-        dbconnparameters += " dbname = '" + sname + "'";
-    } catch(...) {
-        // No database name.  Throw a "NoDatabaseName" exception
-        isc_throw(NoDatabaseName, "must specify a name for the database");
-    }
-
-    conn_ = PQconnectdb(dbconnparameters.c_str());
-    if (conn_ == NULL) {
-        isc_throw(DbOpenError, "could not allocate connection object");
-    }
-
-    if (PQstatus(conn_) != CONNECTION_OK) {
-        // If we have a connection object, we have to call finish
-        // to release it, but grab the error message first.
-        std::string error_message = PQerrorMessage(conn_);
-        PQfinish(conn_);
-        conn_ = NULL;
-        isc_throw(DbOpenError, error_message);
+        conn_.prepareStatement(tagged_statements[i]);
     }
 }
 
@@ -1157,12 +1030,12 @@ PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
         // Failure: check for the special case of duplicate entry.  If this is
         // the case, we return false to indicate that the row was not added.
         // Otherwise we throw an exception.
-        if (compareError(r, DUPLICATE_KEY)) {
+        if (conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
             PQclear(r);
             return (false);
         }
 
-        checkStatementError(r, stindex);
+        conn_.checkStatementError(r, tagged_statements[stindex]);
     }
 
     PQclear(r);
@@ -1170,13 +1043,6 @@ PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
     return (true);
 }
 
-bool PgSqlLeaseMgr::compareError(PGresult*& r, const char* error_state) {
-    const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
-    // PostgreSQL garuantees it will always be 5 characters long
-    return ((sqlstate != NULL) &&
-            (memcmp(sqlstate, error_state, STATECODE_LEN) == 0));
-}
-
 bool
 PgSqlLeaseMgr::addLease(const Lease4Ptr& lease) {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
@@ -1212,7 +1078,7 @@ void PgSqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
                        &bind_array.lengths_[0],
                        &bind_array.formats_[0], 0);
 
-    checkStatementError(r, stindex);
+    conn_.checkStatementError(r, tagged_statements[stindex]);
 
     int rows = PQntuples(r);
     if (single && rows > 1) {
@@ -1530,7 +1396,7 @@ PgSqlLeaseMgr::updateLeaseCommon(StatementIndex stindex,
                                   &bind_array.lengths_[0],
                                   &bind_array.formats_[0], 0);
 
-    checkStatementError(r, stindex);
+    conn_.checkStatementError(r, tagged_statements[stindex]);
 
     int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
     PQclear(r);
@@ -1601,7 +1467,7 @@ PgSqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex,
                                   &bind_array.lengths_[0],
                                   &bind_array.formats_[0], 0);
 
-    checkStatementError(r, stindex);
+    conn_.checkStatementError(r, tagged_statements[stindex]);
     int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
     PQclear(r);
 
@@ -1666,43 +1532,13 @@ string
 PgSqlLeaseMgr::getName() const {
     string name = "";
     try {
-        name = dbconn_.getParameter("name");
+        name = conn_.getParameter("name");
     } catch (...) {
         // Return an empty name
     }
     return (name);
 }
 
-void
-PgSqlLeaseMgr::checkStatementError(PGresult*& r, StatementIndex index) const {
-    int s = PQresultStatus(r);
-    if (s != PGRES_COMMAND_OK && s != PGRES_TUPLES_OK) {
-        // We're testing the first two chars of SQLSTATE, as this is the
-        // error class. Note, there is a severity field, but it can be
-        // misleadingly returned as fatal.
-        const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
-        if ((sqlstate != NULL) &&
-            ((memcmp(sqlstate, "08", 2) == 0) ||  // Connection Exception
-             (memcmp(sqlstate, "53", 2) == 0) ||  // Insufficient resources
-             (memcmp(sqlstate, "54", 2) == 0) ||  // Program Limit exceeded
-             (memcmp(sqlstate, "57", 2) == 0) ||  // Operator intervention
-             (memcmp(sqlstate, "58", 2) == 0))) { // System error
-            LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_FATAL_ERROR)
-                         .arg(tagged_statements[index].name)
-                         .arg(PQerrorMessage(conn_))
-                         .arg(sqlstate);
-            PQclear(r);
-            exit (-1);
-        }
-
-        const char* error_message = PQerrorMessage(conn_);
-        PQclear(r);
-        isc_throw(DbOperationError, "Statement exec faild:" << " for: "
-                  << tagged_statements[index].name << ", reason: "
-                  << error_message);
-    }
-}
-
 string
 PgSqlLeaseMgr::getDescription() const {
     return (string("PostgreSQL Database"));
@@ -1714,7 +1550,7 @@ PgSqlLeaseMgr::getVersion() const {
               DHCPSRV_PGSQL_GET_VERSION);
 
     PGresult* r = PQexecPrepared(conn_, "get_version", 0, NULL, NULL, NULL, 0);
-    checkStatementError(r, GET_VERSION);
+    conn_.checkStatementError(r, tagged_statements[GET_VERSION]);
 
     istringstream tmp;
     uint32_t version;
@@ -1734,28 +1570,12 @@ PgSqlLeaseMgr::getVersion() const {
 
 void
 PgSqlLeaseMgr::commit() {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_COMMIT);
-    PGresult* r = PQexec(conn_, "COMMIT");
-    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
-        const char* error_message = PQerrorMessage(conn_);
-        PQclear(r);
-        isc_throw(DbOperationError, "commit failed: " << error_message);
-    }
-
-    PQclear(r);
+    conn_.commit();
 }
 
 void
 PgSqlLeaseMgr::rollback() {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ROLLBACK);
-    PGresult* r = PQexec(conn_, "ROLLBACK");
-    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
-        const char* error_message = PQerrorMessage(conn_);
-        PQclear(r);
-        isc_throw(DbOperationError, "rollback failed: " << error_message);
-    }
-
-    PQclear(r);
+    conn_.rollback();
 }
 
 }; // end of isc::dhcp namespace

+ 3 - 18
src/lib/dhcpsrv/pgsql_lease_mgr.h

@@ -9,10 +9,10 @@
 
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/lease_mgr.h>
-#include <dhcpsrv/database_connection.h>
+#include <dhcpsrv/pgsql_connection.h>
+
 #include <boost/scoped_ptr.hpp>
 #include <boost/utility.hpp>
-#include <libpq-fe.h>
 
 #include <vector>
 
@@ -440,15 +440,6 @@ public:
     /// @throw DbOperationError If the rollback failed.
     virtual void rollback();
 
-    /// @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(PGresult*& r, const char* error_state);
-
     /// @brief Statement Tags
     ///
     /// The contents of the enum are indexes into the list of compiled SQL
@@ -700,14 +691,8 @@ private:
     boost::scoped_ptr<PgSqlLease4Exchange> exchange4_; ///< Exchange object
     boost::scoped_ptr<PgSqlLease6Exchange> exchange6_; ///< Exchange object
 
-    /// Database connection object
-    ///
-    /// @todo: Implement PgSQLConnection object and collapse
-    /// dbconn_ and conn_ into a single object.
-    DatabaseConnection dbconn_;
-
     /// PostgreSQL connection handle
-    PGconn* conn_;
+    PgSqlConnection conn_;
 };
 
 }; // end of isc::dhcp namespace