]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[master] Created common PgSqlConnection class from PgSqlLeaseMgr
authorThomas Markwalder <tmark@isc.org>
Wed, 1 Jun 2016 11:49:37 +0000 (07:49 -0400)
committerThomas Markwalder <tmark@isc.org>
Wed, 1 Jun 2016 11:49:37 +0000 (07:49 -0400)
    Merged in trac 4276.

1  2 
src/lib/dhcpsrv/pgsql_connection.cc
src/lib/dhcpsrv/pgsql_lease_mgr.cc
src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc

index 0000000000000000000000000000000000000000,016b06154dae665dbe5ac2509f69666ee5b8b67c..b7ef34db2f715e6b87af97f7a5703bb60b947270
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,172 +1,216 @@@
+ // 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>
+ // 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 {
++// Default connection timeout
++const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
++
+ 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");
+     }
++    unsigned int connect_timeout = PGSQL_DEFAULT_CONNECTION_TIMEOUT;
++    string stimeout;
++    try {
++        stimeout = getParameter("connect-timeout");
++    } catch (...) {
++        // No timeout parameter, we are going to use the default timeout.
++        stimeout = "";
++    }
++
++    if (stimeout.size() > 0) {
++        // Timeout was given, so try to convert it to an integer.
++
++        try {
++            connect_timeout = boost::lexical_cast<unsigned int>(stimeout);
++        } catch (...) {
++            // Timeout given but could not be converted to an unsigned int. Set
++            // the connection timeout to an invalid value to trigger throwing
++            // of an exception.
++            connect_timeout = 0;
++        }
++
++        // The timeout is only valid if greater than zero, as depending on the
++        // database, a zero timeout might signify someting like "wait
++        // indefinitely".
++        //
++        // The check below also rejects a value greater than the maximum
++        // integer value.  The lexical_cast operation used to obtain a numeric
++        // value from a string can get confused if trying to convert a negative
++        // integer to an unsigned int: instead of throwing an exception, it may
++        // produce a large positive value.
++        if ((connect_timeout == 0) ||
++            (connect_timeout > numeric_limits<int>::max())) {
++            isc_throw(DbInvalidTimeout, "database connection timeout (" <<
++                      stimeout << ") must be an integer greater than 0");
++        }
++    }
++
++    std::ostringstream oss;
++    oss << connect_timeout;
++    dbconnparameters += " connect_timeout = " + oss.str();
++
+     // 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(const PgSqlResult& 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(const PgSqlResult& 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
Simple merge