mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 13:37:55 +00:00
[4276] Initial impl of PgSqlConnection class
Initial refactoring of Postgresql connection logic out of PgSqlLeaseMgr into new PgSqlConnection.
This commit is contained in:
@@ -133,6 +133,7 @@ endif
|
|||||||
libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
|
libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
|
||||||
|
|
||||||
if HAVE_PGSQL
|
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
|
libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h
|
||||||
endif
|
endif
|
||||||
libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
|
libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
|
||||||
|
181
src/lib/dhcpsrv/pgsql_connection.cc
Normal file
181
src/lib/dhcpsrv/pgsql_connection.cc
Normal file
@@ -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
src/lib/dhcpsrv/pgsql_connection.h
Executable file
296
src/lib/dhcpsrv/pgsql_connection.h
Executable file
@@ -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
|
@@ -21,64 +21,16 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <time.h>
|
#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;
|
||||||
using namespace isc::dhcp;
|
using namespace isc::dhcp;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
namespace {
|
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
|
/// @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 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.
|
/// that the occur in the table. This does not apply to the where clause.
|
||||||
TaggedStatement tagged_statements[] = {
|
PgSqlTaggedStatement tagged_statements[] = {
|
||||||
// DELETE_LEASE4
|
// DELETE_LEASE4
|
||||||
{ 1, { OID_INT8 },
|
{ 1, { OID_INT8 },
|
||||||
"delete_lease4",
|
"delete_lease4",
|
||||||
@@ -1039,25 +991,12 @@ private:
|
|||||||
|
|
||||||
PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
|
PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
|
||||||
: LeaseMgr(), exchange4_(new PgSqlLease4Exchange()),
|
: LeaseMgr(), exchange4_(new PgSqlLease4Exchange()),
|
||||||
exchange6_(new PgSqlLease6Exchange()), dbconn_(parameters), conn_(NULL) {
|
exchange6_(new PgSqlLease6Exchange()), conn_(parameters) {
|
||||||
openDatabase();
|
conn_.openDatabase();
|
||||||
prepareStatements();
|
prepareStatements();
|
||||||
}
|
}
|
||||||
|
|
||||||
PgSqlLeaseMgr::~PgSqlLeaseMgr() {
|
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
|
std::string
|
||||||
@@ -1072,73 +1011,7 @@ PgSqlLeaseMgr::getDBVersion() {
|
|||||||
void
|
void
|
||||||
PgSqlLeaseMgr::prepareStatements() {
|
PgSqlLeaseMgr::prepareStatements() {
|
||||||
for(int i = 0; tagged_statements[i].text != NULL; ++ i) {
|
for(int i = 0; tagged_statements[i].text != NULL; ++ i) {
|
||||||
// Prepare all statements queries with all known fields datatype
|
conn_.prepareStatement(tagged_statements[i]);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1157,12 +1030,12 @@ PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
|
|||||||
// Failure: check for the special case of duplicate entry. If this is
|
// 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.
|
// the case, we return false to indicate that the row was not added.
|
||||||
// Otherwise we throw an exception.
|
// Otherwise we throw an exception.
|
||||||
if (compareError(r, DUPLICATE_KEY)) {
|
if (conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
|
||||||
PQclear(r);
|
PQclear(r);
|
||||||
return (false);
|
return (false);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkStatementError(r, stindex);
|
conn_.checkStatementError(r, tagged_statements[stindex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
PQclear(r);
|
PQclear(r);
|
||||||
@@ -1170,13 +1043,6 @@ PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
|
|||||||
return (true);
|
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
|
bool
|
||||||
PgSqlLeaseMgr::addLease(const Lease4Ptr& lease) {
|
PgSqlLeaseMgr::addLease(const Lease4Ptr& lease) {
|
||||||
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
|
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
|
||||||
@@ -1212,7 +1078,7 @@ void PgSqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
|
|||||||
&bind_array.lengths_[0],
|
&bind_array.lengths_[0],
|
||||||
&bind_array.formats_[0], 0);
|
&bind_array.formats_[0], 0);
|
||||||
|
|
||||||
checkStatementError(r, stindex);
|
conn_.checkStatementError(r, tagged_statements[stindex]);
|
||||||
|
|
||||||
int rows = PQntuples(r);
|
int rows = PQntuples(r);
|
||||||
if (single && rows > 1) {
|
if (single && rows > 1) {
|
||||||
@@ -1530,7 +1396,7 @@ PgSqlLeaseMgr::updateLeaseCommon(StatementIndex stindex,
|
|||||||
&bind_array.lengths_[0],
|
&bind_array.lengths_[0],
|
||||||
&bind_array.formats_[0], 0);
|
&bind_array.formats_[0], 0);
|
||||||
|
|
||||||
checkStatementError(r, stindex);
|
conn_.checkStatementError(r, tagged_statements[stindex]);
|
||||||
|
|
||||||
int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
|
int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
|
||||||
PQclear(r);
|
PQclear(r);
|
||||||
@@ -1601,7 +1467,7 @@ PgSqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex,
|
|||||||
&bind_array.lengths_[0],
|
&bind_array.lengths_[0],
|
||||||
&bind_array.formats_[0], 0);
|
&bind_array.formats_[0], 0);
|
||||||
|
|
||||||
checkStatementError(r, stindex);
|
conn_.checkStatementError(r, tagged_statements[stindex]);
|
||||||
int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
|
int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
|
||||||
PQclear(r);
|
PQclear(r);
|
||||||
|
|
||||||
@@ -1666,43 +1532,13 @@ string
|
|||||||
PgSqlLeaseMgr::getName() const {
|
PgSqlLeaseMgr::getName() const {
|
||||||
string name = "";
|
string name = "";
|
||||||
try {
|
try {
|
||||||
name = dbconn_.getParameter("name");
|
name = conn_.getParameter("name");
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// Return an empty name
|
// Return an empty name
|
||||||
}
|
}
|
||||||
return (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
|
string
|
||||||
PgSqlLeaseMgr::getDescription() const {
|
PgSqlLeaseMgr::getDescription() const {
|
||||||
return (string("PostgreSQL Database"));
|
return (string("PostgreSQL Database"));
|
||||||
@@ -1714,7 +1550,7 @@ PgSqlLeaseMgr::getVersion() const {
|
|||||||
DHCPSRV_PGSQL_GET_VERSION);
|
DHCPSRV_PGSQL_GET_VERSION);
|
||||||
|
|
||||||
PGresult* r = PQexecPrepared(conn_, "get_version", 0, NULL, NULL, NULL, 0);
|
PGresult* r = PQexecPrepared(conn_, "get_version", 0, NULL, NULL, NULL, 0);
|
||||||
checkStatementError(r, GET_VERSION);
|
conn_.checkStatementError(r, tagged_statements[GET_VERSION]);
|
||||||
|
|
||||||
istringstream tmp;
|
istringstream tmp;
|
||||||
uint32_t version;
|
uint32_t version;
|
||||||
@@ -1734,28 +1570,12 @@ PgSqlLeaseMgr::getVersion() const {
|
|||||||
|
|
||||||
void
|
void
|
||||||
PgSqlLeaseMgr::commit() {
|
PgSqlLeaseMgr::commit() {
|
||||||
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_COMMIT);
|
conn_.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PgSqlLeaseMgr::rollback() {
|
PgSqlLeaseMgr::rollback() {
|
||||||
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ROLLBACK);
|
conn_.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}; // end of isc::dhcp namespace
|
}; // end of isc::dhcp namespace
|
||||||
|
@@ -9,10 +9,10 @@
|
|||||||
|
|
||||||
#include <dhcp/hwaddr.h>
|
#include <dhcp/hwaddr.h>
|
||||||
#include <dhcpsrv/lease_mgr.h>
|
#include <dhcpsrv/lease_mgr.h>
|
||||||
#include <dhcpsrv/database_connection.h>
|
#include <dhcpsrv/pgsql_connection.h>
|
||||||
|
|
||||||
#include <boost/scoped_ptr.hpp>
|
#include <boost/scoped_ptr.hpp>
|
||||||
#include <boost/utility.hpp>
|
#include <boost/utility.hpp>
|
||||||
#include <libpq-fe.h>
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -440,15 +440,6 @@ public:
|
|||||||
/// @throw DbOperationError If the rollback failed.
|
/// @throw DbOperationError If the rollback failed.
|
||||||
virtual void rollback();
|
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
|
/// @brief Statement Tags
|
||||||
///
|
///
|
||||||
/// The contents of the enum are indexes into the list of compiled SQL
|
/// 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<PgSqlLease4Exchange> exchange4_; ///< Exchange object
|
||||||
boost::scoped_ptr<PgSqlLease6Exchange> exchange6_; ///< 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
|
/// PostgreSQL connection handle
|
||||||
PGconn* conn_;
|
PgSqlConnection conn_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}; // end of isc::dhcp namespace
|
}; // end of isc::dhcp namespace
|
||||||
|
Reference in New Issue
Block a user