From: Thomas Markwalder Date: Tue, 29 Apr 2014 19:19:45 +0000 (-0400) Subject: [3382] v4 Working with new PsqlBindArray X-Git-Tag: trac3434_base~28^2~14 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=179876c5e6d677e2d97cbcf217f436e4d556e81e;p=thirdparty%2Fkea.git [3382] v4 Working with new PsqlBindArray Initial use of PsqlBindArray, working for v4. --- diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc index 140beaaa3f..37b5be3219 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.cc +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc @@ -62,6 +62,16 @@ struct TaggedStatement { 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 + TaggedStatement tagged_statements[] = { {PgSqlLeaseMgr::DELETE_LEASE4, 1, { 20 }, @@ -172,9 +182,46 @@ TaggedStatement tagged_statements[] = { 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) { +// std::cout << "adding char* : " << value << std::endl; + values_.push_back(value); + lengths_.push_back(strlen(value)); + formats_.push_back(TEXT_FMT); +} + +void PsqlBindArray::add(const std::string& value) { +// std::cout << "adding text : " << value << std::endl; + values_.push_back(value.c_str()); + lengths_.push_back(value.size()); + formats_.push_back(TEXT_FMT); +} + +void PsqlBindArray::add(const std::vector& data) { +// std::cout << "adding bin" << std::endl; + values_.push_back(reinterpret_cast(&(data[0]))); + lengths_.push_back(data.size()); + formats_.push_back(BINARY_FMT); +} + +void PsqlBindArray::add(const uint32_t& value) { +// std::cout << "adding 32int bin" << std::endl; + values_.push_back(reinterpret_cast(&value)); + lengths_.push_back(sizeof(uint32_t)); + formats_.push_back(BINARY_FMT); +} + +void PsqlBindArray::add(const bool& value) { + add(value ? TRUE_STR : FALSE_STR); +} + /// @brief Auxiliary PostgreSQL exchange class class PgSqlLeaseExchange { -protected: +public: /// @brief Converts time_t structure to a text representation in local time. /// @@ -218,7 +265,7 @@ protected: // Any other will throw. /// @param value text value to be converted /// @throw BadValue if passed any value other than "t" or "f" - bool stringToBool(char* value) { + bool stringToBool(const char* value) { if (!value || !strlen(value)) { return (false); } @@ -228,18 +275,114 @@ protected: case 'f': return (false); default: - isc_throw(BadValue, "Received " << value[0] << " as boolean. The " - " only accepted values are 't', 'f' or ''"); + isc_throw(BadValue, "invalid bool value: " << value); + } + } + + 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') { + value = false; + } else if (*data == 't') { + value = true; + } else { + isc_throw(DbOperationError, "Cannot convert data: " << data + << " for: " << getColumnLabel(col) << " row:" << row); + } + } + + void getColumnValue(PGresult* &r, const int row, const size_t col, + uint32_t &value) { + const char* data = getColumnValue(r, row, col); + try { + value = boost::lexical_cast(data); + } catch (const std::exception& ex) { + isc_throw(DbOperationError, "Cannot convert data: " << data + << " for: " << getColumnLabel(col) << " row:" << row + << " : " << ex.what()); } } + 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 :" + << getColumnLabel(col) << " row:" << row); + } + + return (value); + } + + void convertFromBytea(PGresult* &r, const int row, const size_t col, + uint8_t* dest, + const size_t max, size_t &bytes_converted) { + + unsigned char* bytes + = PQunescapeBytea((const unsigned char*) + (getColumnValue(r, row, col)), &bytes_converted); + if (!bytes) { + isc_throw (DbOperationError, "PQunescapeBytea failed for:" + << getColumnLabel(col) << " row:" << row); + } + + if (bytes_converted > max) { + PQfreemem(bytes); + isc_throw (DbOperationError, "Converted data size: " + << bytes_converted << " is too large for: " + << getColumnLabel(col) << " row:" << row); + } + + memcpy(dest, bytes, bytes_converted); + PQfreemem(bytes); + } + + std::string toHexText(const char* data, size_t len) { + std::ostringstream stream; + stream << "Data length is: " << len << std::endl; + for (int i = 0; i < len; ++i) { + if (i > 0 && ((i % 16) == 0)) { + stream << std::endl; + } + + stream << setfill('0') << setw(2) << setbase(16) + << static_cast(data[i]) << " "; + } + + return (stream.str()); + } + /// Compiled statement bind parameters BindParams params; + + std::vectorcolumnLabels_; + + std::string getColumnLabel(const size_t index) { + if (index > columnLabels_.size()) { + ostringstream os; + os << "Unknown column:" << index; + return (os.str()); + } + + return (columnLabels_[index]); + } }; + /// @brief Represents a single Lease4 exchange class PgSqlLease4Exchange : public PgSqlLeaseExchange { private: + + 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; @@ -247,125 +390,102 @@ public: /// @brief Default constructor PgSqlLease4Exchange() : addr4_(0), hwaddr_length_(0), 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) - columns_[0] = "address"; - columns_[1] = "hwaddr"; - columns_[2] = "client_id"; - columns_[3] = "valid_lifetime"; - columns_[4] = "expire"; - columns_[5] = "subnet_id"; - columns_[6] = "fqdn_fwd"; - columns_[7] = "fqdn_rev"; - columns_[8] = "hostname"; - - BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS); + /// @todo something more elegant than this + 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"); params.reserve(LEASE_COLUMNS); } - BindParams - createBindForSend(const Lease4Ptr& lease) { + void + createBindForSend(const Lease4Ptr& lease, PsqlBindArray& bind_array) { + // Store lease object to ensure it remains valid. lease_ = lease; - params.clear(); - ostringstream tmp; - tmp << static_cast(lease_->addr_); - params.push_back(PgSqlParam(tmp.str())); - tmp.str(""); + addr4_str_ = boost::lexical_cast + (static_cast(lease->addr_)); + + bind_array.add(addr4_str_); // Although HWADDR object will always be there, it may be just an // empty vector - if (!lease_->hwaddr_.empty()) { + if (!lease->hwaddr_.empty()) { if (lease->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) { - isc_throw(DbOperationError, - "Hardware address length : " + isc_throw(DbOperationError, "Hardware address length : " << lease->hwaddr_.size() << " exceeds maximum allowed of: " << HWAddr::MAX_HWADDR_LEN); } - params.push_back(PgSqlParam(lease_->hwaddr_)); + bind_array.add(lease->hwaddr_); } else { - params.push_back(PgSqlParam()); + bind_array.add(""); } - - if (lease_->client_id_) { - params.push_back(PgSqlParam(lease_->client_id_->getClientId())); + + if (lease->client_id_) { + bind_array.add(lease->client_id_->getClientId()); } else { - params.push_back(PgSqlParam()); + bind_array.add(""); } - tmp << static_cast(lease_->valid_lft_); - params.push_back(PgSqlParam(tmp.str())); - tmp.str(""); + valid_lft_str_ = boost::lexical_cast(lease->valid_lft_); + bind_array.add(valid_lft_str_); - time_t expire = lease_->valid_lft_ + lease_->cltt_; - params.push_back(PgSqlParam(convertToDatabaseTime(expire))); + expire_str_ = convertToDatabaseTime(lease->valid_lft_ + lease->cltt_); + bind_array.add(expire_str_); - tmp << static_cast(lease_->subnet_id_); - params.push_back(PgSqlParam(tmp.str())); + subnet_id_str_ = boost::lexical_cast(lease->subnet_id_); + bind_array.add(subnet_id_str_); - params.push_back(PgSqlParam(lease_->fqdn_fwd_ ? "TRUE" : "FALSE")); - params.push_back(PgSqlParam(lease_->fqdn_rev_ ? "TRUE" : "FALSE")); - params.push_back(PgSqlParam(lease_->hostname_)); + bind_array.add(lease->fqdn_fwd_); + bind_array.add(lease->fqdn_rev_); - return (params); + bind_array.add(lease->hostname_); } Lease4Ptr - convertFromDatabase(PGresult *& r, int line) { - const char* addr4_str = PQgetvalue(r, line, 0); - unsigned char* hwaddr_str = PQunescapeBytea((const unsigned char *) - PQgetvalue(r, line, 1), - & hwaddr_length_); - unsigned char* client_id_str = PQunescapeBytea((const unsigned char *) - PQgetvalue(r, line, 2), - & client_id_length_); - const char* valid_lifetime_str = PQgetvalue(r, line, 3); - const char* expire_str = PQgetvalue(r, line, 4); - const char* subnet_id_str = PQgetvalue(r, line, 5); - unsigned long valid_lifetime; - unsigned long subnet_id; + convertFromDatabase(PGresult *& r, int row) { - istringstream tmp; - tmp.str(addr4_str); - tmp >> addr4_; - tmp.clear(); + getColumnValue(r, row, ADDRESS_COL, addr4_); - memcpy(hwaddr_buffer_, hwaddr_str, hwaddr_length_); - memcpy(client_id_buffer_, client_id_str, client_id_length_); + convertFromBytea(r, row, HWADDR_COL, hwaddr_buffer_, + sizeof(hwaddr_buffer_), hwaddr_length_); - PQfreemem(client_id_str); - PQfreemem(hwaddr_str); + convertFromBytea(r, row, CLIENT_ID_COL, client_id_buffer_, + sizeof(client_id_buffer_), client_id_length_); - tmp.str(valid_lifetime_str); - tmp >> valid_lifetime; - valid_lifetime_ = static_cast(valid_lifetime); - tmp.clear(); + valid_lifetime_ = boost::lexical_cast + (PQgetvalue(r, row , VALID_LIFETIME_COL)); - expire_ = convertFromDatabaseTime(expire_str); + expire_ = convertFromDatabaseTime(getColumnValue(r, row, EXPIRE_COL)); - tmp.str(subnet_id_str); - tmp >> subnet_id; - subnet_id_ = static_cast(subnet_id); - tmp.clear(); + getColumnValue(r, row , SUBNET_ID_COL, subnet_id_); - time_t cltt = expire_ - valid_lifetime_; + cltt_ = expire_ - valid_lifetime_; - // Extract fqdn_fwd, fqdn_rev - bool fwd = stringToBool(PQgetvalue(r, line, 6)); - bool rev = stringToBool(PQgetvalue(r, line, 7)); + getColumnValue(r, row, FQDN_FWD_COL, fqdn_fwd_); + getColumnValue(r, row, FQDN_REV_COL, fqdn_rev_); - // Extract hostname field - string hostname(PQgetvalue(r, line, 8)); + hostname_ = getColumnValue(r, row, HOSTNAME_COL); return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_, client_id_buffer_, client_id_length_, - valid_lifetime_, 0, 0, cltt, subnet_id_, - fwd, rev, hostname))); + valid_lifetime_, 0, 0, cltt_, subnet_id_, + fqdn_fwd_, fqdn_rev_, hostname_))); } private: @@ -376,13 +496,23 @@ private: uint8_t client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN]; Lease4Ptr lease_; time_t expire_; + time_t cltt_; uint32_t subnet_id_; uint32_t valid_lifetime_; unsigned long hwaddr_length_; unsigned long client_id_length_; + bool fqdn_fwd_; + bool fqdn_rev_; string hostname_; + + std::string addr4_str_; + std::string valid_lft_str_; + std::string expire_str_; + std::string subnet_id_str_; + }; + class PgSqlLease6Exchange : public PgSqlLeaseExchange { private: static const size_t LEASE_COLUMNS = 12; @@ -457,18 +587,18 @@ public: } Lease6Ptr - convertFromDatabase(PGresult * r, int line) { - const char* addr6_str = PQgetvalue(r, line, 0); + convertFromDatabase(PGresult * r, int row) { + const char* addr6_str = PQgetvalue(r, row, 0); unsigned char* duid_str = PQunescapeBytea((const unsigned char *) - PQgetvalue(r, line, 1), + PQgetvalue(r, row, 1), & duid_length_); - const char* valid_lifetime_str = PQgetvalue(r, line, 2); - const char* expire_str = PQgetvalue(r, line, 3); - const char* subnet_id_str = PQgetvalue(r, line, 4); - const char* pref_lifetime_str = PQgetvalue(r, line, 5); - const char* lease_type_str = PQgetvalue(r, line, 6); - const char* iaid_str = PQgetvalue(r, line, 7); - const char* prefixlen_str = PQgetvalue(r, line, 8); + const char* valid_lifetime_str = PQgetvalue(r, row, 2); + const char* expire_str = PQgetvalue(r, row, 3); + const char* subnet_id_str = PQgetvalue(r, row, 4); + const char* pref_lifetime_str = PQgetvalue(r, row, 5); + const char* lease_type_str = PQgetvalue(r, row, 6); + const char* iaid_str = PQgetvalue(r, row, 7); + const char* prefixlen_str = PQgetvalue(r, row, 8); unsigned int lease_type, prefixlen; unsigned long valid_lifetime, subnet_id, pref_lifetime, iaid; @@ -533,11 +663,11 @@ public: } // Extract fqdn_fwd, fqdn_rev - bool fwd = stringToBool(PQgetvalue(r, line, 9)); - bool rev = stringToBool(PQgetvalue(r, line, 10)); + bool fwd = stringToBool(PQgetvalue(r, row, 9)); + bool rev = stringToBool(PQgetvalue(r, row, 10)); // Extract hostname field - string hostname(PQgetvalue(r, line, 11)); + string hostname(PQgetvalue(r, row, 11)); // Set up DUID, DuidPtr duid_ptr(new DUID(duid_buffer_, duid_length_)); @@ -669,6 +799,40 @@ PgSqlLeaseMgr::openDatabase() { } } +bool +PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex, + PsqlBindArray& bind_array) { + + PGresult * r = PQexecPrepared(conn_, statements_[stindex].stmt_name, + statements_[stindex].stmt_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)) { + return (false); + } + + isc_throw(DbOperationError, "unable to INSERT for " << + statements_[stindex].stmt_name << ", reason: " << + errorMsg); + } + + PQclear(r); + + return (true); +} + + bool PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex, BindParams& params) { @@ -709,9 +873,10 @@ bool PgSqlLeaseMgr::addLease(const Lease4Ptr& lease) { LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ADD_ADDR4).arg(lease->addr_.toText()); - BindParams params = exchange4_->createBindForSend(lease); - return (addLeaseCommon(INSERT_LEASE4, params)); + PsqlBindArray bind_array; + exchange4_->createBindForSend(lease, bind_array); + return (addLeaseCommon(INSERT_LEASE4, bind_array)); } bool @@ -743,15 +908,15 @@ void PgSqlLeaseMgr::getLeaseCollection(StatementIndex stindex, checkStatementError(r, stindex); - int lines = PQntuples(r); - if (single && lines > 1) { + 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 " << statements_[stindex].stmt_name); } - for(int i = 0; i < lines; ++ i) { + for(int i = 0; i < rows; ++ i) { result.push_back(exchange->convertFromDatabase(r, i)); } @@ -1007,6 +1172,43 @@ PgSqlLeaseMgr::getLeases6(Lease::Type lease_type, const DUID& duid, return (result); } +template +void +PgSqlLeaseMgr::updateLeaseCommon(StatementIndex stindex, + PsqlBindArray& bind_array, + const LeasePtr& lease) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, + DHCPSRV_PGSQL_ADD_ADDR4).arg(statements_[stindex].stmt_name); + + PGresult * r = PQexecPrepared(conn_, statements_[stindex].stmt_name, + statements_[stindex].stmt_nbparams, + &bind_array.values_[0], + &bind_array.lengths_[0], + &bind_array.formats_[0], 0); + + checkStatementError(r, stindex); + + int affected_rows = boost::lexical_cast(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()); +} + + template void PgSqlLeaseMgr::updateLeaseCommon(StatementIndex stindex, BindParams & params, @@ -1052,15 +1254,16 @@ PgSqlLeaseMgr::updateLease4(const Lease4Ptr& lease) { DHCPSRV_PGSQL_UPDATE_ADDR4).arg(lease->addr_.toText()); // Create the BIND array for the data being updated - ostringstream tmp; - BindParams params = exchange4_->createBindForSend(lease); + PsqlBindArray bind_array; + exchange4_->createBindForSend(lease, bind_array); // Set up the WHERE clause and append it to the SQL_BIND array - tmp << static_cast(lease->addr_); - params.push_back(PgSqlParam(tmp.str())); + std::string addr4_ = boost::lexical_cast + (static_cast(lease->addr_)); + bind_array.add(addr4_); // Drop to common update code - updateLeaseCommon(stindex, params, lease); + updateLeaseCommon(stindex, bind_array, lease); } void @@ -1139,7 +1342,7 @@ PgSqlLeaseMgr::checkStatementError(PGresult* r, StatementIndex index) const { } } -inline void +void PgSqlLeaseMgr::convertToQuery(const BindParams& params, std::vector& out_values, std::vector& out_lengths, diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.h b/src/lib/dhcpsrv/pgsql_lease_mgr.h index abd3ffd1c7..c359de4394 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.h +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.h @@ -57,6 +57,31 @@ struct PgSqlParam { } }; +struct PsqlBindArray { + std::vector values_; + std::vector lengths_; + std::vector formats_; + + static const int TEXT_FMT; + static const int BINARY_FMT; + static const char* TRUE_STR; + static const char* FALSE_STR; + + size_t size() { + return (values_.size()); + } + + bool empty() { + return (values_.empty()); + } + + void add(const char* value); + void add(const std::string& value); + void add(const std::vector& data); + void add(const uint32_t& value); + void add(const bool& value); +}; + /// @brief Defines all parameters for binding a compiled statement typedef std::vector BindParams; @@ -434,6 +459,8 @@ private: /// failed. bool addLeaseCommon(StatementIndex stindex, BindParams& params); + bool addLeaseCommon(StatementIndex stindex, PsqlBindArray& bind_array); + /// @brief Get Lease Collection Common Code /// /// This method performs the common actions for obtaining multiple leases @@ -569,6 +596,10 @@ private: void updateLeaseCommon(StatementIndex stindex, BindParams& params, const LeasePtr& lease); + template + void updateLeaseCommon(StatementIndex stindex, PsqlBindArray& bind_array, + const LeasePtr& lease); + /// @brief Delete lease common code /// /// Holds the common code for deleting a lease. It binds the parameters