#include <string>
#include <time.h>
+// PostgreSQL errors should be tested based on the SQL state code. These are
+// each state code is 5 decimal digits, the first two define the category of
+// error, the last three are the specific error. PostgreSQL makes the state
+// code as a char* of 5 ASCII digits. Macros for each code are defined in
+// PostgreSQL's errorcodes.h, although they require a second macro,
+// MAKE_SQLSTATE for completion. They deliberately do not define this
+// this second macro in errorlogs.h, so callers can supply their own.
+#define MAKE_SQLSTATE(ch1,ch2,ch3,ch4,ch5) {ch1,ch2,ch3,ch4,ch5}
+#include <server/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;
const char* text;
};
-#if 0
-const OID_BOOL = 16;
-const OID_BTYEA = 17;
-const OID_INT8 = 20; // 8 byte int
-const OID_INT4 = 23; // 8 byte int
-const OID_INT2 = 21; // 2 byte int
-const OID_TIMESTAMP = 1114;
-const OID_VARCHAR = 1043; // ? what's diff between this and OID/25 = text
-#endif
+/// @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_INT4 = 23; // 4 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[] = {
{PgSqlLeaseMgr::DELETE_LEASE4, 1,
- { 20 },
+ { OID_INT8 },
"delete_lease4",
- "DELETE FROM lease4 WHERE address = $1"},
+ "DELETE FROM lease4 WHERE address = $1"},
{PgSqlLeaseMgr::DELETE_LEASE6, 1,
- { 1043 },
+ { OID_VARCHAR },
"delete_lease6",
- "DELETE FROM lease6 WHERE address = $1"},
+ "DELETE FROM lease6 WHERE address = $1"},
{PgSqlLeaseMgr::GET_LEASE4_ADDR, 1,
- { 20 },
+ { OID_INT8 },
"get_lease4_addr",
- "SELECT address, hwaddr, client_id, "
- "valid_lifetime, extract(epoch from expire), subnet_id, fqdn_fwd, fqdn_rev, hostname "
- "FROM lease4 "
- "WHERE address = $1"},
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname "
+ "FROM lease4 "
+ "WHERE address = $1"},
{PgSqlLeaseMgr::GET_LEASE4_CLIENTID, 1,
- { 17 },
+ { OID_BYTEA },
"get_lease4_clientid",
- "SELECT address, hwaddr, client_id, "
- "valid_lifetime, extract(epoch from expire), subnet_id, fqdn_fwd, fqdn_rev, hostname "
- "FROM lease4 "
- "WHERE client_id = $1"},
+ "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"},
{PgSqlLeaseMgr::GET_LEASE4_CLIENTID_SUBID, 2,
- { 17, 20 },
+ { 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"},
+ "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"},
{PgSqlLeaseMgr::GET_LEASE4_HWADDR, 1,
- { 17 },
- "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"},
+ { 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"},
{PgSqlLeaseMgr::GET_LEASE4_HWADDR_SUBID, 2,
- { 17, 20 },
- "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"},
+ { 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"},
{PgSqlLeaseMgr::GET_LEASE6_ADDR, 2,
- { 1043, 21 },
+ { 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"},
+ "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"},
{PgSqlLeaseMgr::GET_LEASE6_DUID_IAID, 3,
- { 17, 20, 21 },
+ { 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"},
+ "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"},
{PgSqlLeaseMgr::GET_LEASE6_DUID_IAID_SUBID, 4,
- { 21, 17, 20, 20 },
+ { 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"},
+ "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"},
{PgSqlLeaseMgr::GET_VERSION, 0,
- { 0 },
- "get_version",
- "SELECT version, minor FROM schema_version"},
+ { OID_NONE },
+ "get_version",
+ "SELECT version, minor FROM schema_version"},
{PgSqlLeaseMgr::INSERT_LEASE4, 9,
- { 20, 17, 17, 20, 1114, 20, 16, 16, 1043 },
- "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)"},
+ { 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)"},
{PgSqlLeaseMgr::INSERT_LEASE6, 12,
- { 1043, 17, 20, 1114, 20, 20, 21, 20, 21, 16, 16, 1043 },
+ { 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)"},
+ "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)"},
{PgSqlLeaseMgr::UPDATE_LEASE4, 10,
- { 20, 17, 17, 20, 1114, 20, 16, 16, 1043, 20 },
+ { 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 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"},
{PgSqlLeaseMgr::UPDATE_LEASE6, 13,
- { 1043, 17, 20, 1114, 20, 20, 21, 20, 21, 16, 16, 1043, 1043 },
+ { 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"},
-
+ "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
{PgSqlLeaseMgr::NUM_STATEMENTS, 0, { 0 }, NULL, NULL}
};
///
/// @return a const char* pointer to the column's raw data
/// @throw DbOperationError if the value cannot be fetched.
- const char* getColumnValue(PGresult* &r, const int row, const size_t col) {
+ const char* getColumnValue(PGresult*& r, const int row, const size_t col) {
const char* value = PQgetvalue(r, row, col);
if (!value) {
isc_throw(DbOperationError, "getColumnValue no data for :"
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
- void getColumnValue(PGresult* &r, const int row, const size_t col,
+ void getColumnValue(PGresult*& r, const int row, const size_t col,
bool &value) {
const char* data = getColumnValue(r, row, col);
if (!strlen(data) || *data == 'f') {
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
- void getColumnValue(PGresult* &r, const int row, const size_t col,
+ void getColumnValue(PGresult*& r, const int row, const size_t col,
uint32_t &value) {
const char* data = getColumnValue(r, row, col);
try {
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
- void getColumnValue(PGresult* &r, const int row, const size_t col,
+ void getColumnValue(PGresult*& r, const int row, const size_t col,
uint8_t &value) {
const char* data = getColumnValue(r, row, col);
try {
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
- void getColumnValue(PGresult* &r, const int row, const size_t col,
+ 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);
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
- void convertFromBytea(PGresult* &r, const int row, const size_t col,
+ void convertFromBytea(PGresult*& r, const int row, const size_t col,
uint8_t* buffer,
const size_t buffer_size, size_t &bytes_converted) {
///
/// @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;
///
/// @return Lease4Ptr to the newly created Lease4 object
/// @throw DbOperationError if the lease cannot be created.
- Lease4Ptr convertFromDatabase(PGresult *& r, int row) {
+ Lease4Ptr convertFromDatabase(PGresult*& r, int row) {
try {
getColumnValue(r, row, ADDRESS_COL, addr4_);
///
/// @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 {
///
/// @return Lease6Ptr to the newly created Lease4 object
/// @throw DbOperationError if the lease cannot be created.
- Lease6Ptr convertFromDatabase(PGresult* &r, int row) {
+ Lease6Ptr convertFromDatabase(PGresult*& r, int row) {
try {
isc::asiolink::IOAddress addr(getIPv6Value(r, row, ADDRESS_COL));
/// @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,
+ isc::asiolink::IOAddress getIPv6Value(PGresult*& r, const int row,
const size_t col) {
const char* data = getColumnValue(r, row, col);
try {
bool
PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
PsqlBindArray& bind_array) {
- PGresult * r = PQexecPrepared(conn_, 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);
int s = PQresultStatus(r);
- if (s != PGRES_COMMAND_OK) {
- const char * errorMsg = PQerrorMessage(conn_);
- PQclear(r);
- /// @todo - ok, do we have to rely on error message text??
- /// and why is failing on duplicate key NOT an error?
- /// should be looking at global sqlca struct
- if(!strncmp(errorMsg, "ERROR: duplicate key",
- sizeof("ERROR: duplicate key") - 1)) {
+ 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);
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,
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,
+ PGresult* r = PQexecPrepared(conn_, tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array.values_[0],
&bind_array.lengths_[0],
bool
PgSqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex,
PsqlBindArray& bind_array) {
- PGresult * r = PQexecPrepared(conn_, tagged_statements[stindex].name,
+ PGresult* r = PQexecPrepared(conn_, tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array.values_[0],
&bind_array.lengths_[0],
}
void
-PgSqlLeaseMgr::checkStatementError(PGresult* r, StatementIndex index) const {
+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: " <<
- PQerrorMessage(conn_));
+ isc_throw(DbOperationError, "Statement exec faild:" << " for: "
+ << tagged_statements[index].name << ", reason: "
+ << error_message);
}
}
void
PgSqlLeaseMgr::commit() {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_COMMIT);
- PGresult * r = PQexec(conn_, "COMMIT");
+ PGresult* r = PQexec(conn_, "COMMIT");
if (PQresultStatus(r) != PGRES_COMMAND_OK) {
- isc_throw(DbOperationError, "commit failed: " << PQerrorMessage(conn_));
+ 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");
+ PGresult* r = PQexec(conn_, "ROLLBACK");
if (PQresultStatus(r) != PGRES_COMMAND_OK) {
- isc_throw(DbOperationError, "rollback failed: "
- << PQerrorMessage(conn_));
+ const char* error_message = PQerrorMessage(conn_);
+ PQclear(r);
+ isc_throw(DbOperationError, "rollback failed: " << error_message);
}
PQclear(r);