mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 05:27: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:
parent
8745f651e6
commit
b1c67df1e8
@ -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
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 <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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user