2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-05 08:25:16 +00:00
Files
kea/src/lib/dhcpsrv/pgsql_lease_mgr.cc
Marcin Siodelski 3ec0536917 [3673] Fixed cppcheck errors in the MySQL and PgSQL backends.
This is about making some functions static or const.
2015-03-04 15:02:04 +01:00

1557 lines
54 KiB
C++

// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/pgsql_lease_mgr.h>
#include <boost/static_assert.hpp>
#include <iostream>
#include <iomanip>
#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
// 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 = 13;
/// @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[] = {
// DELETE_LEASE4
{ 1, { OID_INT8 },
"delete_lease4",
"DELETE FROM lease4 WHERE address = $1"},
// DELETE_LEASE6
{ 1, { OID_VARCHAR },
"delete_lease6",
"DELETE FROM lease6 WHERE address = $1"},
// GET_LEASE4_ADDR
{ 1, { OID_INT8 },
"get_lease4_addr",
"SELECT address, hwaddr, client_id, "
"valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
"fqdn_fwd, fqdn_rev, hostname "
"FROM lease4 "
"WHERE address = $1"},
// GET_LEASE4_CLIENTID
{ 1, { OID_BYTEA },
"get_lease4_clientid",
"SELECT address, hwaddr, client_id, "
"valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
"fqdn_fwd, fqdn_rev, hostname "
"FROM lease4 "
"WHERE client_id = $1"},
// GET_LEASE4_CLIENTID_SUBID
{ 2, { OID_BYTEA, OID_INT8 },
"get_lease4_clientid_subid",
"SELECT address, hwaddr, client_id, "
"valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
"fqdn_fwd, fqdn_rev, hostname "
"FROM lease4 "
"WHERE client_id = $1 AND subnet_id = $2"},
// GET_LEASE4_HWADDR
{ 1, { OID_BYTEA },
"get_lease4_hwaddr",
"SELECT address, hwaddr, client_id, "
"valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
"fqdn_fwd, fqdn_rev, hostname "
"FROM lease4 "
"WHERE hwaddr = $1"},
// GET_LEASE4_HWADDR_SUBID
{ 2, { OID_BYTEA, OID_INT8 },
"get_lease4_hwaddr_subid",
"SELECT address, hwaddr, client_id, "
"valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
"fqdn_fwd, fqdn_rev, hostname "
"FROM lease4 "
"WHERE hwaddr = $1 AND subnet_id = $2"},
// GET_LEASE6_ADDR
{ 2, { OID_VARCHAR, OID_INT2 },
"get_lease6_addr",
"SELECT address, duid, valid_lifetime, "
"extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
"lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname "
"FROM lease6 "
"WHERE address = $1 AND lease_type = $2"},
// GET_LEASE6_DUID_IAID
{ 3, { OID_BYTEA, OID_INT8, OID_INT2 },
"get_lease6_duid_iaid",
"SELECT address, duid, valid_lifetime, "
"extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
"lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname "
"FROM lease6 "
"WHERE duid = $1 AND iaid = $2 AND lease_type = $3"},
// GET_LEASE6_DUID_IAID_SUBID
{ 4, { OID_INT2, OID_BYTEA, OID_INT8, OID_INT8 },
"get_lease6_duid_iaid_subid",
"SELECT address, duid, valid_lifetime, "
"extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
"lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname "
"FROM lease6 "
"WHERE lease_type = $1 "
"AND duid = $2 AND iaid = $3 AND subnet_id = $4"},
// GET_VERSION
{ 0, { OID_NONE },
"get_version",
"SELECT version, minor FROM schema_version"},
// INSERT_LEASE4
{ 9, { OID_INT8, OID_BYTEA, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
OID_BOOL, OID_BOOL, OID_VARCHAR },
"insert_lease4",
"INSERT INTO lease4(address, hwaddr, client_id, "
"valid_lifetime, expire, subnet_id, fqdn_fwd, fqdn_rev, hostname) "
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"},
// INSERT_LEASE6
{ 12, { OID_VARCHAR, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
OID_INT8, OID_INT2, OID_INT8, OID_INT2, OID_BOOL, OID_BOOL,
OID_VARCHAR },
"insert_lease6",
"INSERT INTO lease6(address, duid, valid_lifetime, "
"expire, subnet_id, pref_lifetime, "
"lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname) "
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)"},
// UPDATE_LEASE4
{ 10, { OID_INT8, OID_BYTEA, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
OID_BOOL, OID_BOOL, OID_VARCHAR, OID_INT8 },
"update_lease4",
"UPDATE lease4 SET address = $1, hwaddr = $2, "
"client_id = $3, valid_lifetime = $4, expire = $5, "
"subnet_id = $6, fqdn_fwd = $7, fqdn_rev = $8, hostname = $9 "
"WHERE address = $10"},
// UPDATE_LEASE6
{ 13, { OID_VARCHAR, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8, OID_INT8,
OID_INT2, OID_INT8, OID_INT2, OID_BOOL, OID_BOOL, OID_VARCHAR,
OID_VARCHAR },
"update_lease6",
"UPDATE lease6 SET address = $1, duid = $2, "
"valid_lifetime = $3, expire = $4, subnet_id = $5, "
"pref_lifetime = $6, lease_type = $7, iaid = $8, "
"prefix_len = $9, fqdn_fwd = $10, fqdn_rev = $11, hostname = $12 "
"WHERE address = $13"},
// End of list sentinel
{ 0, { 0 }, NULL, NULL}
};
};
namespace isc {
namespace dhcp {
const int PsqlBindArray::TEXT_FMT = 0;
const int PsqlBindArray::BINARY_FMT = 1;
const char* PsqlBindArray::TRUE_STR = "TRUE";
const char* PsqlBindArray::FALSE_STR = "FALSE";
void PsqlBindArray::add(const char* value) {
values_.push_back(value);
lengths_.push_back(strlen(value));
formats_.push_back(TEXT_FMT);
}
void PsqlBindArray::add(const std::string& value) {
values_.push_back(value.c_str());
lengths_.push_back(value.size());
formats_.push_back(TEXT_FMT);
}
void PsqlBindArray::add(const std::vector<uint8_t>& data) {
values_.push_back(reinterpret_cast<const char*>(&(data[0])));
lengths_.push_back(data.size());
formats_.push_back(BINARY_FMT);
}
void PsqlBindArray::add(const bool& value) {
add(value ? TRUE_STR : FALSE_STR);
}
std::string PsqlBindArray::toText() {
std::ostringstream stream;
for (int i = 0; i < values_.size(); ++i) {
stream << i << " : ";
if (formats_[i] == TEXT_FMT) {
stream << "\"" << values_[i] << "\"" << std::endl;
} else {
const char *data = values_[i];
if (lengths_[i] == 0) {
stream << "empty" << std::endl;
} else {
stream << "0x";
for (int i = 0; i < lengths_[i]; ++i) {
stream << setfill('0') << setw(2) << setbase(16)
<< static_cast<unsigned int>(data[i]);
}
stream << std::endl;
}
}
}
return (stream.str());
}
/// @brief Base class for marshalling leases to and from PostgreSQL.
///
/// Provides the common functionality to set up binding information between
/// lease objects in the program and their database representation in the
/// database.
class PgSqlLeaseExchange {
public:
PgSqlLeaseExchange()
: addr_str_(""), valid_lifetime_(0), valid_lft_str_(""),
expire_(0), expire_str_(""), subnet_id_(0), subnet_id_str_(""),
cltt_(0), fqdn_fwd_(false), fqdn_rev_(false), hostname_("") {
}
virtual ~PgSqlLeaseExchange(){}
/// @brief Converts time_t structure to a text representation in local time.
///
/// The format of the output string is "%Y-%m-%d %H:%M:%S". Database
/// table columns using this value should be typed as TIMESTAMP WITH
/// TIME ZONE. For such columns PostgreSQL assumes input strings without
/// timezones should be treated as in local time and are converted to UTC
/// when stored. Likewise, these columns are automatically adjusted
/// upon retrieval unless fetched via "extract(epoch from <column>))".
///
/// @param time_val_64 timestamp to be converted. This is given as a
/// 64-bit value to avoid overflows on the 32-bit systems where time_t
/// is implemented as int32_t.
/// @return std::string containing the stringified time
std::string
convertToDatabaseTime(const int64_t& time_val_64) {
// PostgreSQL does funny things with time if you get past Y2038. It
// will accept the values (unlike MySQL which throws) but it
// stops correctly adjusting to local time when reading them back
// out. So lets disallow it here.
if (time_val_64 > LeaseMgr::MAX_DB_TIME) {
isc_throw(BadValue, "Time value is too large: " << time_val_64);
}
struct tm tinfo;
char buffer[20];
const time_t time_val = static_cast<time_t>(time_val_64);
localtime_r(&time_val, &tinfo);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
return (std::string(buffer));
}
/// @brief Converts time stamp from the database to a time_t
///
/// @param db_time_val timestamp to be converted. This value
/// is expected to be the number of seconds since the epoch
/// expressed as base-10 integer string.
/// @return Converted timestamp as time_t value.
time_t convertFromDatabaseTime(const std::string& db_time_val) {
// Convert string time value to time_t
try {
return (boost::lexical_cast<time_t>(db_time_val));
} catch (const std::exception& ex) {
isc_throw(BadValue, "Database time value is invalid: "
<< db_time_val);
}
}
/// @brief Gets a pointer to the raw column value in a result set row
///
/// Given a result set, row, and column return a const char* pointer to
/// the data value in the result set. The pointer is valid as long as
/// the result set has not been freed. It may point to text or binary
/// data depending on how query was structured. You should not attempt
/// to free this pointer.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
///
/// @return a const char* pointer to the column's raw data
/// @throw DbOperationError if the value cannot be fetched.
const char* getRawColumnValue(PGresult*& r, const int row,
const size_t col) {
const char* value = PQgetvalue(r, row, col);
if (!value) {
isc_throw(DbOperationError, "getRawColumnValue no data for :"
<< getColumnLabel(col) << " row:" << row);
}
return (value);
}
/// @brief Converts a column in a row in a result set to a boolean.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(PGresult*& r, const int row, const size_t col,
bool &value) {
const char* data = getRawColumnValue(r, row, col);
if (!strlen(data) || *data == 'f') {
value = false;
} else if (*data == 't') {
value = true;
} else {
isc_throw(DbOperationError, "Invalid boolean data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : must be 't' or 'f'");
}
}
/// @brief Converts a column in a row in a result set to a uint32_t.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(PGresult*& r, const int row, const size_t col,
uint32_t &value) {
const char* data = getRawColumnValue(r, row, col);
try {
value = boost::lexical_cast<uint32_t>(data);
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Invalid uint32_t data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : " << ex.what());
}
}
/// @brief Converts a column in a row in a result set to a uint8_t.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(PGresult*& r, const int row, const size_t col,
uint8_t &value) {
const char* data = getRawColumnValue(r, row, col);
try {
// lexically casting as uint8_t doesn't convert from char
// so we use uint16_t and implicitly convert.
value = boost::lexical_cast<uint16_t>(data);
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Invalid uint8_t data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : " << ex.what());
}
}
/// @brief Converts a column in a row in a result set to a Lease6::Type
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(PGresult*& r, const int row, const size_t col,
Lease6::Type& value) {
uint32_t raw_value = 0;
getColumnValue(r, row , col, raw_value);
switch (raw_value) {
case Lease6::TYPE_NA:
value = Lease6::TYPE_NA;
break;
case Lease6::TYPE_TA:
value = Lease6::TYPE_TA;
break;
case Lease6::TYPE_PD:
value = Lease6::TYPE_PD;
break;
default:
isc_throw(DbOperationError, "Invalid lease type: " << raw_value
<< " for: " << getColumnLabel(col) << " row:" << row);
}
}
/// @brief Converts a column in a row in a result set to a binary bytes
///
/// Method is used to convert columns stored as BYTEA into a buffer of
/// binary bytes, (uint8_t). It uses PQunescapeBytea to do the conversion.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] buffer pre-allocated buffer to which the converted bytes
/// will be stored.
/// @param buffer_size size of the output buffer
/// @param[out] bytes_converted number of bytes converted
/// value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void convertFromBytea(PGresult*& r, const int row, const size_t col,
uint8_t* buffer,
const size_t buffer_size, size_t &bytes_converted) {
// Returns converted bytes in a dynamically allocated buffer, and
// sets bytes_converted.
unsigned char* bytes = PQunescapeBytea((const unsigned char*)
(getRawColumnValue(r, row, col)),
&bytes_converted);
// Unlikely it couldn't allocate it but you never know.
if (!bytes) {
isc_throw (DbOperationError, "PQunescapeBytea failed for:"
<< getColumnLabel(col) << " row:" << row);
}
// Make sure it's not larger than expected.
if (bytes_converted > buffer_size) {
// Free the allocated buffer first!
PQfreemem(bytes);
isc_throw (DbOperationError, "Converted data size: "
<< bytes_converted << " is too large for: "
<< getColumnLabel(col) << " row:" << row);
}
// Copy from the allocated buffer to caller's buffer the free up
// the allocated buffer.
memcpy(buffer, bytes, bytes_converted);
PQfreemem(bytes);
}
/// @brief Returns column label given a column number
std::string getColumnLabel(const size_t column) const {
if (column > columnLabels_.size()) {
ostringstream os;
os << "Unknown column:" << column;
return (os.str());
}
return (columnLabels_[column]);
}
protected:
/// @brief Stores text labels for columns, currently only used for
/// logging and errors.
std::vector<std::string>columnLabels_;
/// @brief Common Instance members used for binding and conversion
//@{
std::string addr_str_;
uint32_t valid_lifetime_;
std::string valid_lft_str_;
time_t expire_;
std::string expire_str_;
uint32_t subnet_id_;
std::string subnet_id_str_;
time_t cltt_;
bool fqdn_fwd_;
bool fqdn_rev_;
std::string hostname_;
//@}
};
/// @brief Supports exchanging IPv4 leases with PostgreSQL.
class PgSqlLease4Exchange : public PgSqlLeaseExchange {
private:
/// @brief Column numbers for each column in the Lease4 table.
/// These are used for both retrieving data and for looking up
/// column labels for logging. Note that their numeric order
/// MUST match that of the column order in the Lease4 table.
static const size_t ADDRESS_COL = 0;
static const size_t HWADDR_COL = 1;
static const size_t CLIENT_ID_COL = 2;
static const size_t VALID_LIFETIME_COL = 3;
static const size_t EXPIRE_COL = 4;
static const size_t SUBNET_ID_COL = 5;
static const size_t FQDN_FWD_COL = 6;
static const size_t FQDN_REV_COL = 7;
static const size_t HOSTNAME_COL = 8;
/// @brief Number of columns in the table holding DHCPv4 leases.
static const size_t LEASE_COLUMNS = 9;
public:
/// @brief Default constructor
PgSqlLease4Exchange()
: lease_(), addr4_(0), hwaddr_length_(0), hwaddr_(hwaddr_length_),
client_id_length_(0) {
BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);
memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
// Set the column names (for error messages)
columnLabels_.push_back("address");
columnLabels_.push_back("hwaddr");
columnLabels_.push_back("client_id");
columnLabels_.push_back("valid_lifetime");
columnLabels_.push_back("expire");
columnLabels_.push_back("subnet_id");
columnLabels_.push_back("fqdn_fwd");
columnLabels_.push_back("fqdn_rev");
columnLabels_.push_back("hostname");
}
/// @brief Creates the bind array for sending Lease4 data to the database.
///
/// Converts each Lease4 member into the appropriate form and adds it
/// to the bind array. Note that the array additions must occur in the
/// order the columns are specified in the SQL statement. By convention
/// all columns in the table are explicitly listed in the SQL statement(s)
/// in the same order as they occur in the table.
///
/// @param lease Lease4 object that is to be written to the database
/// @param[out] bind_array array to populate with the lease data values
///
/// @throw DbOperationError if bind_array cannot be populated.
void createBindForSend(const Lease4Ptr& lease, PsqlBindArray& bind_array) {
if (!lease) {
isc_throw(BadValue, "createBindForSend:: Lease4 object is NULL");
}
// Store lease object to ensure it remains valid.
lease_ = lease;
try {
addr_str_ = boost::lexical_cast<std::string>
(static_cast<uint32_t>(lease->addr_));
bind_array.add(addr_str_);
if (lease->hwaddr_ && !lease->hwaddr_->hwaddr_.empty()) {
// PostgreSql does not provide MAX on variable length types
// so we have to enforce it ourselves.
if (lease->hwaddr_->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) {
isc_throw(DbOperationError, "Hardware address length : "
<< lease_->hwaddr_->hwaddr_.size()
<< " exceeds maximum allowed of: "
<< HWAddr::MAX_HWADDR_LEN);
}
bind_array.add(lease->hwaddr_->hwaddr_);
} else {
bind_array.add("");
}
if (lease->client_id_) {
bind_array.add(lease->client_id_->getClientId());
} else {
bind_array.add("");
}
valid_lft_str_ = boost::lexical_cast<std::string>
(lease->valid_lft_);
bind_array.add(valid_lft_str_);
expire_str_ = convertToDatabaseTime(static_cast<int64_t>(lease->valid_lft_) +
static_cast<int64_t>(lease->cltt_));
bind_array.add(expire_str_);
subnet_id_str_ = boost::lexical_cast<std::string>
(lease->subnet_id_);
bind_array.add(subnet_id_str_);
bind_array.add(lease->fqdn_fwd_);
bind_array.add(lease->fqdn_rev_);
bind_array.add(lease->hostname_);
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
"Could not create bind array for Lease4: "
<< lease_->addr_.toText() << ", reason: " << ex.what());
}
}
/// @brief Creates a Lease4 object from a given row in a result set.
///
/// @param r result set containing one or rows from the Lease4 table
/// @param row row number within the result set from to create the Lease4
/// object.
///
/// @return Lease4Ptr to the newly created Lease4 object
/// @throw DbOperationError if the lease cannot be created.
Lease4Ptr convertFromDatabase(PGresult*& r, int row) {
try {
getColumnValue(r, row, ADDRESS_COL, addr4_);
convertFromBytea(r, row, HWADDR_COL, hwaddr_buffer_,
sizeof(hwaddr_buffer_), hwaddr_length_);
convertFromBytea(r, row, CLIENT_ID_COL, client_id_buffer_,
sizeof(client_id_buffer_), client_id_length_);
getColumnValue(r, row, VALID_LIFETIME_COL, valid_lifetime_);
expire_ = convertFromDatabaseTime(getRawColumnValue(r, row,
EXPIRE_COL));
getColumnValue(r, row , SUBNET_ID_COL, subnet_id_);
cltt_ = expire_ - valid_lifetime_;
getColumnValue(r, row, FQDN_FWD_COL, fqdn_fwd_);
getColumnValue(r, row, FQDN_REV_COL, fqdn_rev_);
hostname_ = getRawColumnValue(r, row, HOSTNAME_COL);
HWAddrPtr hwaddr(new HWAddr(hwaddr_buffer_, hwaddr_length_,
HTYPE_ETHER));
return (Lease4Ptr(new Lease4(addr4_, hwaddr,
client_id_buffer_, client_id_length_,
valid_lifetime_, 0, 0, cltt_,
subnet_id_, fqdn_fwd_, fqdn_rev_,
hostname_)));
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
"Could not convert data to Lease4, reason: "
<< ex.what());
}
}
private:
/// @brief Lease4 object currently being sent to the database.
/// Storing this value ensures that it remains in scope while any bindings
/// that refer to its contents are in use.
Lease4Ptr lease_;
/// @Brief Lease4 specific members used for binding and conversion.
uint32_t addr4_;
size_t hwaddr_length_;
std::vector<uint8_t> hwaddr_;
uint8_t hwaddr_buffer_[HWAddr::MAX_HWADDR_LEN];
size_t client_id_length_;
uint8_t client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
};
/// @brief Supports exchanging IPv6 leases with PostgreSQL.
class PgSqlLease6Exchange : public PgSqlLeaseExchange {
private:
/// @brief Column numbers for each column in the Lease6 table.
/// These are used for both retrieving data and for looking up
/// column labels for logging. Note that their numeric order
/// MUST match that of the column order in the Lease6 table.
//@{
static const int ADDRESS_COL = 0;
static const int DUID_COL = 1;
static const int VALID_LIFETIME_COL = 2;
static const int EXPIRE_COL = 3;
static const int SUBNET_ID_COL = 4;
static const int PREF_LIFETIME_COL = 5;
static const int LEASE_TYPE_COL = 6;
static const int IAID_COL = 7;
static const int PREFIX_LEN_COL = 8;
static const int FQDN_FWD_COL = 9;
static const int FQDN_REV_COL = 10;
static const int HOSTNAME_COL = 11;
//@}
/// @brief Number of columns in the table holding DHCPv4 leases.
static const size_t LEASE_COLUMNS = 12;
public:
PgSqlLease6Exchange()
: lease_(), duid_length_(0), duid_(), iaid_(0), iaid_str_(""),
lease_type_(Lease6::TYPE_NA), lease_type_str_(""), prefix_len_(0),
prefix_len_str_(""), pref_lifetime_(0), preferred_lft_str_("") {
BOOST_STATIC_ASSERT(11 < LEASE_COLUMNS);
memset(duid_buffer_, 0, sizeof(duid_buffer_));
// Set the column names (for error messages)
columnLabels_.push_back("address");
columnLabels_.push_back("duid");
columnLabels_.push_back("valid_lifetime");
columnLabels_.push_back("expire");
columnLabels_.push_back("subnet_id");
columnLabels_.push_back("pref_lifetime");
columnLabels_.push_back("lease_type");
columnLabels_.push_back("iaid");
columnLabels_.push_back("prefix_len");
columnLabels_.push_back("fqdn_fwd");
columnLabels_.push_back("fqdn_rev");
columnLabels_.push_back("hostname");
}
/// @brief Creates the bind array for sending Lease6 data to the database.
///
/// Converts each Lease6 member into the appropriate form and adds it
/// to the bind array. Note that the array additions must occur in the
/// order the columns are specified in the SQL statement. By convention
/// all columns in the table are explicitly listed in the SQL statement(s)
/// in the same order as they occur in the table.
///
/// @param lease Lease6 object that is to be written to the database
/// @param[out] bind_array array to populate with the lease data values
///
/// @throw DbOperationError if bind_array cannot be populated.
void createBindForSend(const Lease6Ptr& lease, PsqlBindArray& bind_array) {
if (!lease) {
isc_throw(BadValue, "createBindForSend:: Lease6 object is NULL");
}
// Store lease object to ensure it remains valid.
lease_ = lease;
try {
addr_str_ = lease_->addr_.toText();
bind_array.add(addr_str_);
if (lease_->duid_) {
bind_array.add(lease_->duid_->getDuid());
} else {
isc_throw (BadValue, "IPv6 Lease cannot have a null DUID");
}
valid_lft_str_ = boost::lexical_cast<std::string>
(lease->valid_lft_);
bind_array.add(valid_lft_str_);
expire_str_ = convertToDatabaseTime(static_cast<int64_t>(lease->valid_lft_) +
static_cast<int64_t>(lease->cltt_));
bind_array.add(expire_str_);
subnet_id_str_ = boost::lexical_cast<std::string>
(lease->subnet_id_);
bind_array.add(subnet_id_str_);
preferred_lft_str_ = boost::lexical_cast<std::string>
(lease_->preferred_lft_);
bind_array.add(preferred_lft_str_);
lease_type_str_ = boost::lexical_cast<std::string>(lease_->type_);
bind_array.add(lease_type_str_);
iaid_str_ = boost::lexical_cast<std::string>(lease_->iaid_);
bind_array.add(iaid_str_);
prefix_len_str_ = boost::lexical_cast<std::string>
(static_cast<unsigned int>(lease_->prefixlen_));
bind_array.add(prefix_len_str_);
bind_array.add(lease->fqdn_fwd_);
bind_array.add(lease->fqdn_rev_);
bind_array.add(lease->hostname_);
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
"Could not create bind array from Lease6: "
<< lease_->addr_.toText() << ", reason: " << ex.what());
}
}
/// @brief Creates a Lease6 object from a given row in a result set.
///
/// @param r result set containing one or rows from the Lease6 table
/// @param row row number within the result set from to create the Lease6
/// object.
///
/// @return Lease6Ptr to the newly created Lease4 object
/// @throw DbOperationError if the lease cannot be created.
Lease6Ptr convertFromDatabase(PGresult*& r, int row) {
try {
isc::asiolink::IOAddress addr(getIPv6Value(r, row, ADDRESS_COL));
convertFromBytea(r, row, DUID_COL, duid_buffer_,
sizeof(duid_buffer_), duid_length_);
DuidPtr duid_ptr(new DUID(duid_buffer_, duid_length_));
getColumnValue(r, row, VALID_LIFETIME_COL, valid_lifetime_);
expire_ = convertFromDatabaseTime(getRawColumnValue(r, row,
EXPIRE_COL));
cltt_ = expire_ - valid_lifetime_;
getColumnValue(r, row , SUBNET_ID_COL, subnet_id_);
getColumnValue(r, row , PREF_LIFETIME_COL, pref_lifetime_);
getColumnValue(r, row, LEASE_TYPE_COL, lease_type_);
getColumnValue(r, row , IAID_COL, iaid_);
getColumnValue(r, row , PREFIX_LEN_COL, prefix_len_);
getColumnValue(r, row, FQDN_FWD_COL, fqdn_fwd_);
getColumnValue(r, row, FQDN_REV_COL, fqdn_rev_);
hostname_ = getRawColumnValue(r, row, HOSTNAME_COL);
/// @todo: implement this in #3557.
HWAddrPtr hwaddr;
Lease6Ptr result(new Lease6(lease_type_, addr, duid_ptr, iaid_,
pref_lifetime_, valid_lifetime_, 0, 0,
subnet_id_, fqdn_fwd_, fqdn_rev_,
hostname_, hwaddr, prefix_len_));
result->cltt_ = cltt_;
return (result);
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
"Could not convert data to Lease6, reason: "
<< ex.what());
}
}
/// @brief Converts a column in a row in a result set into IPv6 address.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
///
/// @return isc::asiolink::IOAddress containing the IPv6 address.
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
isc::asiolink::IOAddress getIPv6Value(PGresult*& r, const int row,
const size_t col) {
const char* data = getRawColumnValue(r, row, col);
try {
return (isc::asiolink::IOAddress(data));
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Cannot convert data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : " << ex.what());
}
}
private:
/// @brief Lease6 object currently being sent to the database.
/// Storing this value ensures that it remains in scope while any bindings
/// that refer to its contents are in use.
Lease6Ptr lease_;
/// @brief Lease6 specific members for binding and conversion.
//@{
size_t duid_length_;
vector<uint8_t> duid_;
uint8_t duid_buffer_[DUID::MAX_DUID_LEN];
uint32_t iaid_;
std::string iaid_str_;
Lease6::Type lease_type_;
std::string lease_type_str_;
uint8_t prefix_len_;
std::string prefix_len_str_;
uint32_t pref_lifetime_;
std::string preferred_lft_str_;
//@}
};
PgSqlLeaseMgr::PgSqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
: LeaseMgr(parameters), exchange4_(new PgSqlLease4Exchange()),
exchange6_(new PgSqlLease6Exchange()), conn_(NULL) {
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;
}
}
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 = 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");
}
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);
}
}
bool
PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
PsqlBindArray& bind_array) {
PGresult* r = PQexecPrepared(conn_, tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array.values_[0],
&bind_array.lengths_[0],
&bind_array.formats_[0], 0);
int s = PQresultStatus(r);
if (s != PGRES_COMMAND_OK) {
// 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)) {
PQclear(r);
return (false);
}
const char* errorMsg = PQerrorMessage(conn_);
PQclear(r);
isc_throw(DbOperationError, "unable to INSERT for " <<
tagged_statements[stindex].name << ", reason: " <<
errorMsg);
}
PQclear(r);
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,
DHCPSRV_PGSQL_ADD_ADDR4).arg(lease->addr_.toText());
PsqlBindArray bind_array;
exchange4_->createBindForSend(lease, bind_array);
return (addLeaseCommon(INSERT_LEASE4, bind_array));
}
bool
PgSqlLeaseMgr::addLease(const Lease6Ptr& lease) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_ADD_ADDR6).arg(lease->addr_.toText());
PsqlBindArray bind_array;
exchange6_->createBindForSend(lease, bind_array);
return (addLeaseCommon(INSERT_LEASE6, bind_array));
}
template <typename Exchange, typename LeaseCollection>
void PgSqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
PsqlBindArray& bind_array,
Exchange& exchange,
LeaseCollection& result,
bool single) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_GET_ADDR4).arg(tagged_statements[stindex].name);
PGresult* r = PQexecPrepared(conn_, tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array.values_[0],
&bind_array.lengths_[0],
&bind_array.formats_[0], 0);
checkStatementError(r, stindex);
int rows = PQntuples(r);
if (single && rows > 1) {
PQclear(r);
isc_throw(MultipleRecords, "multiple records were found in the "
"database where only one was expected for query "
<< tagged_statements[stindex].name);
}
for(int i = 0; i < rows; ++ i) {
result.push_back(exchange->convertFromDatabase(r, i));
}
PQclear(r);
}
void
PgSqlLeaseMgr::getLease(StatementIndex stindex, PsqlBindArray& bind_array,
Lease4Ptr& result) const {
// Create appropriate collection object and get all leases matching
// the selection criteria. The "single" parameter is true to indicate
// that the called method should throw an exception if multiple
// matching records are found: this particular method is called when only
// one or zero matches is expected.
Lease4Collection collection;
getLeaseCollection(stindex, bind_array, exchange4_, collection, true);
// Return single record if present, else clear the lease.
if (collection.empty()) {
result.reset();
} else {
result = *collection.begin();
}
}
void
PgSqlLeaseMgr::getLease(StatementIndex stindex, PsqlBindArray& bind_array,
Lease6Ptr& result) const {
// Create appropriate collection object and get all leases matching
// the selection criteria. The "single" parameter is true to indicate
// that the called method should throw an exception if multiple
// matching records are found: this particular method is called when only
// one or zero matches is expected.
Lease6Collection collection;
getLeaseCollection(stindex, bind_array, exchange6_, collection, true);
// Return single record if present, else clear the lease.
if (collection.empty()) {
result.reset();
} else {
result = *collection.begin();
}
}
Lease4Ptr
PgSqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_GET_ADDR4).arg(addr.toText());
// Set up the WHERE clause value
PsqlBindArray bind_array;
// LEASE ADDRESS
std::string addr_str = boost::lexical_cast<std::string>
(static_cast<uint32_t>(addr));
bind_array.add(addr_str);
// Get the data
Lease4Ptr result;
getLease(GET_LEASE4_ADDR, bind_array, result);
return (result);
}
Lease4Collection
PgSqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_GET_HWADDR).arg(hwaddr.toText());
// Set up the WHERE clause value
PsqlBindArray bind_array;
// HWADDR
if (!hwaddr.hwaddr_.empty()) {
bind_array.add(hwaddr.hwaddr_);
} else {
bind_array.add("");
}
// Get the data
Lease4Collection result;
getLeaseCollection(GET_LEASE4_HWADDR, bind_array, result);
return (result);
}
Lease4Ptr
PgSqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_GET_SUBID_HWADDR)
.arg(subnet_id).arg(hwaddr.toText());
// Set up the WHERE clause value
PsqlBindArray bind_array;
// HWADDR
if (!hwaddr.hwaddr_.empty()) {
bind_array.add(hwaddr.hwaddr_);
} else {
bind_array.add("");
}
// SUBNET_ID
std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id);
bind_array.add(subnet_id_str);
// Get the data
Lease4Ptr result;
getLease(GET_LEASE4_HWADDR_SUBID, bind_array, result);
return (result);
}
Lease4Collection
PgSqlLeaseMgr::getLease4(const ClientId& clientid) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_GET_CLIENTID).arg(clientid.toText());
// Set up the WHERE clause value
PsqlBindArray bind_array;
// CLIENT_ID
bind_array.add(clientid.getClientId());
// Get the data
Lease4Collection result;
getLeaseCollection(GET_LEASE4_CLIENTID, bind_array, result);
return (result);
}
Lease4Ptr
PgSqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_GET_SUBID_CLIENTID)
.arg(subnet_id).arg(clientid.toText());
// Set up the WHERE clause value
PsqlBindArray bind_array;
// CLIENT_ID
bind_array.add(clientid.getClientId());
// SUBNET_ID
std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id);
bind_array.add(subnet_id_str);
// Get the data
Lease4Ptr result;
getLease(GET_LEASE4_CLIENTID_SUBID, bind_array, result);
return (result);
}
Lease4Ptr
PgSqlLeaseMgr::getLease4(const ClientId&, const HWAddr&, SubnetID) const {
/// This function is currently not implemented because allocation engine
/// searches for the lease using HW address or client identifier.
/// It never uses both parameters in the same time. We need to
/// consider if this function is needed at all.
isc_throw(NotImplemented, "The PgSqlLeaseMgr::getLease4 function was"
" called, but it is not implemented");
}
Lease6Ptr
PgSqlLeaseMgr::getLease6(Lease::Type lease_type,
const isc::asiolink::IOAddress& addr) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_ADDR6)
.arg(addr.toText()).arg(lease_type);
// Set up the WHERE clause value
PsqlBindArray bind_array;
// LEASE ADDRESS
std::string addr_str = addr.toText();
bind_array.add(addr_str);
// LEASE_TYPE
std::string type_str_ = boost::lexical_cast<std::string>(lease_type);
bind_array.add(type_str_);
// ... and get the data
Lease6Ptr result;
getLease(GET_LEASE6_ADDR, bind_array, result);
return (result);
}
Lease6Collection
PgSqlLeaseMgr::getLeases6(Lease::Type lease_type, const DUID& duid,
uint32_t iaid) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_GET_IAID_DUID)
.arg(iaid).arg(duid.toText()).arg(lease_type);
// Set up the WHERE clause value
PsqlBindArray bind_array;
// DUID
bind_array.add(duid.getDuid());
// IAID
std::string iaid_str = boost::lexical_cast<std::string>(iaid);
bind_array.add(iaid_str);
// LEASE_TYPE
std::string lease_type_str = boost::lexical_cast<std::string>(lease_type);
bind_array.add(lease_type_str);
// ... and get the data
Lease6Collection result;
getLeaseCollection(GET_LEASE6_DUID_IAID, bind_array, result);
return (result);
}
Lease6Collection
PgSqlLeaseMgr::getLeases6(Lease::Type lease_type, const DUID& duid,
uint32_t iaid, SubnetID subnet_id) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_GET_IAID_SUBID_DUID)
.arg(iaid).arg(subnet_id).arg(duid.toText()).arg(lease_type);
// Set up the WHERE clause value
PsqlBindArray bind_array;
// LEASE_TYPE
std::string lease_type_str = boost::lexical_cast<std::string>(lease_type);
bind_array.add(lease_type_str);
// DUID
bind_array.add(duid.getDuid());
// IAID
std::string iaid_str = boost::lexical_cast<std::string>(iaid);
bind_array.add(iaid_str);
// SUBNET ID
std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id);
bind_array.add(subnet_id_str);
// ... and get the data
Lease6Collection result;
getLeaseCollection(GET_LEASE6_DUID_IAID_SUBID, bind_array, result);
return (result);
}
template <typename LeasePtr>
void
PgSqlLeaseMgr::updateLeaseCommon(StatementIndex stindex,
PsqlBindArray& bind_array,
const LeasePtr& lease) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_ADD_ADDR4).arg(tagged_statements[stindex].name);
PGresult* r = PQexecPrepared(conn_, tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array.values_[0],
&bind_array.lengths_[0],
&bind_array.formats_[0], 0);
checkStatementError(r, stindex);
int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
PQclear(r);
// Check success case first as it is the most likely outcome.
if (affected_rows == 1) {
return;
}
// If no rows affected, lease doesn't exist.
if (affected_rows == 0) {
isc_throw(NoSuchLease, "unable to update lease for address " <<
lease->addr_.toText() << " as it does not exist");
}
// Should not happen - primary key constraint should only have selected
// one row.
isc_throw(DbOperationError, "apparently updated more than one lease "
"that had the address " << lease->addr_.toText());
}
void
PgSqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE4;
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_UPDATE_ADDR4).arg(lease->addr_.toText());
// Create the BIND array for the data being updated
PsqlBindArray bind_array;
exchange4_->createBindForSend(lease, bind_array);
// Set up the WHERE clause and append it to the SQL_BIND array
std::string addr4_ = boost::lexical_cast<std::string>
(static_cast<uint32_t>(lease->addr_));
bind_array.add(addr4_);
// Drop to common update code
updateLeaseCommon(stindex, bind_array, lease);
}
void
PgSqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE6;
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_UPDATE_ADDR6).arg(lease->addr_.toText());
// Create the BIND array for the data being updated
PsqlBindArray bind_array;
exchange6_->createBindForSend(lease, bind_array);
// Set up the WHERE clause and append it to the BIND array
std::string addr_str = lease->addr_.toText();
bind_array.add(addr_str);
// Drop to common update code
updateLeaseCommon(stindex, bind_array, lease);
}
bool
PgSqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex,
PsqlBindArray& bind_array) {
PGresult* r = PQexecPrepared(conn_, tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array.values_[0],
&bind_array.lengths_[0],
&bind_array.formats_[0], 0);
checkStatementError(r, stindex);
int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
PQclear(r);
return (affected_rows > 0);
}
bool
PgSqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_DELETE_ADDR).arg(addr.toText());
// Set up the WHERE clause value
PsqlBindArray bind_array;
if (addr.isV4()) {
std::string addr4_str = boost::lexical_cast<std::string>
(static_cast<uint32_t>(addr));
bind_array.add(addr4_str);
return (deleteLeaseCommon(DELETE_LEASE4, bind_array));
}
std::string addr6_str = addr.toText();
bind_array.add(addr6_str);
return (deleteLeaseCommon(DELETE_LEASE6, bind_array));
}
string
PgSqlLeaseMgr::getName() const {
string name = "";
try {
name = 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) {
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"));
}
pair<uint32_t, uint32_t>
PgSqlLeaseMgr::getVersion() const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_GET_VERSION);
PGresult* r = PQexecPrepared(conn_, "get_version", 0, NULL, NULL, NULL, 0);
checkStatementError(r, GET_VERSION);
istringstream tmp;
uint32_t version;
tmp.str(PQgetvalue(r, 0, 0));
tmp >> version;
tmp.str("");
tmp.clear();
uint32_t minor;
tmp.str(PQgetvalue(r, 0, 1));
tmp >> minor;
PQclear(r);
return make_pair<uint32_t, uint32_t>(version, minor);
}
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);
}
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);
}
}; // end of isc::dhcp namespace
}; // end of isc namespace