// with the server tag.
// Let's first get the primary key of the global parameter.
- uint64_t id = mysql_insert_id(conn_.mysql_);
+ uint64_t id = mysql_insert_id(conn_.handle());
// Create bindings for inserting the association into
// dhcp4_global_parameter_server table.
// Run INSERT.
conn_.insertQuery(INSERT_POOL4, in_bindings);
- uint64_t pool_id = mysql_insert_id(conn_.mysql_);
+ uint64_t pool_id = mysql_insert_id(conn_.handle());
auto option_spaces = pool->getCfgOption()->getOptionSpaceNames();
for (auto option_space : option_spaces) {
OptionContainerPtr options = pool->getCfgOption()->getAll(option_space);
in_bindings);
// Fetch unique identifier of the inserted option.
- uint64_t id = mysql_insert_id(conn_.mysql_);
+ uint64_t id = mysql_insert_id(conn_.handle());
// Create bindings needed to insert association of that option with
// a server into the dhcp4_options_server table.
// Fetch unique identifier of the inserted option definition and use it
// as input to the next query.
- uint64_t id = mysql_insert_id(conn_.mysql_);
+ uint64_t id = mysql_insert_id(conn_.handle());
MySqlBindingCollection in_server_bindings = {
MySqlBinding::createInteger<uint64_t>(id), // option_def_id
// Free up the prepared statements, ignoring errors. (What would we do
// about them? We're destroying this object and are not really concerned
// with errors on a database connection that is about to go away.)
- for (int i = 0; i < conn_.statements_.size(); ++i) {
- if (conn_.statements_[i] != NULL) {
- (void) mysql_stmt_close(conn_.statements_[i]);
- conn_.statements_[i] = NULL;
+ for (int i = 0; i < conn_.handle().statements_.size(); ++i) {
+ if (conn_.handle().statements_[i] != NULL) {
+ (void) mysql_stmt_close(conn_.handle().statements_[i]);
+ conn_.handle().statements_[i] = NULL;
}
}
}
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
-
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
-
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
// Free up the prepared statements, ignoring errors. (What would we do
// about them? We're destroying this object and are not really concerned
// with errors on a database connection that is about to go away.)
- for (int i = 0; i < conn_.statements_.size(); ++i) {
- if (conn_.statements_[i] != NULL) {
- (void) mysql_stmt_close(conn_.statements_[i]);
- conn_.statements_[i] = NULL;
+ for (int i = 0; i < conn_.handle().statements_.size(); ++i) {
+ if (conn_.handle().statements_[i] != NULL) {
+ (void) mysql_stmt_close(conn_.handle().statements_[i]);
+ conn_.handle().statements_[i] = NULL;
}
}
DHCPSRV_MYSQL_HOST_DB_GET_VERSION);
// Allocate a new statement.
- MYSQL_STMT *stmt = mysql_stmt_init(conn_.mysql_);
+ MYSQL_STMT *stmt = mysql_stmt_init(conn_.handle());
if (stmt == NULL) {
isc_throw(DbOperationError, "unable to allocate MySQL prepared "
- "statement structure, reason: " << mysql_error(conn_.mysql_));
+ "statement structure, reason: " << mysql_error(conn_.handle()));
}
// Prepare the statement from SQL text.
const char* version_sql = "SELECT version, minor FROM schema_version";
int status = mysql_stmt_prepare(stmt, version_sql, strlen(version_sql));
if (status != 0) {
+ mysql_stmt_close(stmt);
isc_throw(DbOperationError, "unable to prepare MySQL statement <"
- << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ << version_sql << ">, reason: " << mysql_errno(conn_.handle()));
}
// Execute the prepared statement.
if (mysql_stmt_execute(stmt) != 0) {
+ mysql_stmt_close(stmt);
isc_throw(DbOperationError, "cannot execute schema version query <"
- << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ << version_sql << ">, reason: " << mysql_errno(conn_.handle()));
}
// Bind the output of the statement to the appropriate variables.
if (mysql_stmt_bind_result(stmt, bind)) {
isc_throw(DbOperationError, "unable to bind result set for <"
- << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ << version_sql << ">, reason: " << mysql_errno(conn_.handle()));
}
// Fetch the data.
if (mysql_stmt_fetch(stmt)) {
mysql_stmt_close(stmt);
isc_throw(DbOperationError, "unable to bind result set for <"
- << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ << version_sql << ">, reason: " << mysql_errno(conn_.handle()));
}
// Discard the statement and its resources
std::vector<MYSQL_BIND>& bind) {
// Bind the parameters to the statement
- int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
+ int status = mysql_stmt_bind_param(conn_.handle().statements_[stindex], &bind[0]);
checkError(status, stindex, "unable to bind parameters");
// Execute the statement
- status = mysql_stmt_execute(conn_.statements_[stindex]);
+ status = mysql_stmt_execute(conn_.handle().statements_[stindex]);
if (status != 0) {
// Failure: check for the special case of duplicate entry.
- if (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) {
+ if (mysql_errno(conn_.handle()) == ER_DUP_ENTRY) {
isc_throw(DuplicateEntry, "Database duplicate entry error");
}
checkError(status, stindex, "unable to execute");
MySqlHostDataSourceImpl::delStatement(StatementIndex stindex,
MYSQL_BIND* bind) {
// Bind the parameters to the statement
- int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
+ int status = mysql_stmt_bind_param(conn_.handle().statements_[stindex], &bind[0]);
checkError(status, stindex, "unable to bind parameters");
// Execute the statement
- status = mysql_stmt_execute(conn_.statements_[stindex]);
+ status = mysql_stmt_execute(conn_.handle().statements_[stindex]);
if (status != 0) {
checkError(status, stindex, "unable to execute");
}
// Let's check how many hosts were deleted.
- my_ulonglong numrows = mysql_stmt_affected_rows(conn_.statements_[stindex]);
+ my_ulonglong numrows = mysql_stmt_affected_rows(conn_.handle().statements_[stindex]);
return (numrows != 0);
}
ConstHostCollection& result, bool single) const {
// Bind the selection parameters to the statement
- int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
+ int status = mysql_stmt_bind_param(conn_.handle().statements_[stindex], bind);
checkError(status, stindex, "unable to bind WHERE clause parameter");
// Set up the MYSQL_BIND array for the data being returned and bind it to
// the statement.
std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
- status = mysql_stmt_bind_result(conn_.statements_[stindex], &outbind[0]);
+ status = mysql_stmt_bind_result(conn_.handle().statements_[stindex], &outbind[0]);
checkError(status, stindex, "unable to bind SELECT clause parameters");
// Execute the statement
- status = mysql_stmt_execute(conn_.statements_[stindex]);
+ status = mysql_stmt_execute(conn_.handle().statements_[stindex]);
checkError(status, stindex, "unable to execute");
// Ensure that all the lease information is retrieved in one go to avoid
// overhead of going back and forth between client and server.
- status = mysql_stmt_store_result(conn_.statements_[stindex]);
+ status = mysql_stmt_store_result(conn_.handle().statements_[stindex]);
checkError(status, stindex, "unable to set up for storing all results");
// Set up the fetch "release" object to release resources associated
// with the call to mysql_stmt_fetch when this method exits, then
// retrieve the data. mysql_stmt_fetch return value equal to 0 represents
// successful data fetch.
- MySqlFreeResult fetch_release(conn_.statements_[stindex]);
- while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) ==
+ MySqlFreeResult fetch_release(conn_.handle().statements_[stindex]);
+ while ((status = mysql_stmt_fetch(conn_.handle().statements_[stindex])) ==
MLM_MYSQL_FETCH_SUCCESS) {
try {
exchange->processFetchedData(result);
impl_->addStatement(MySqlHostDataSourceImpl::INSERT_HOST, bind);
// Gets the last inserted hosts id
- uint64_t host_id = mysql_insert_id(impl_->conn_.mysql_);
+ uint64_t host_id = mysql_insert_id(impl_->conn_.handle());
// Insert DHCPv4 options.
ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
}
// v6
- ConstHostPtr host = get6(subnet_id, addr);
+ ConstHostPtr host(get6(subnet_id, addr));
if (!host) {
return (false);
}
// Miscellaneous database methods.
-std::string MySqlHostDataSource::getName() const {
+std::string
+MySqlHostDataSource::getName() const {
std::string name = "";
try {
name = impl_->conn_.getParameter("name");
return (name);
}
-std::string MySqlHostDataSource::getDescription() const {
+std::string
+MySqlHostDataSource::getDescription() const {
return (std::string("Host data source that stores host information"
"in MySQL database"));
}
bind_[2].buffer_length = client_id_length_;
bind_[2].length = &client_id_length_;
// bind_[2].is_null = &MLM_FALSE; // commented out for performance
- // reasons, see memset() above
+ // reasons, see memset() above
} else {
bind_[2].buffer_type = MYSQL_TYPE_NULL;
// According to http://dev.mysql.com/doc/refman/5.5/en/
// bind_[8].is_null = &MLM_FALSE; // commented out for performance
// reasons, see memset() above
- // state: uint32_t.
+ // state: uint32_t
bind_[9].buffer_type = MYSQL_TYPE_LONG;
bind_[9].buffer = reinterpret_cast<char*>(&lease_->state_);
bind_[9].is_unsigned = MLM_TRUE;
// bind_[8].is_null = &MLM_FALSE; // commented out for performance
// reasons, see memset() above
- // state: uint32_t
+ // state: uint32_t
bind_[9].buffer_type = MYSQL_TYPE_LONG;
bind_[9].buffer = reinterpret_cast<char*>(&state_);
bind_[9].is_unsigned = MLM_TRUE;
bind_[14].is_null = &hwaddr_null_;
}
- // state: uint32_t
+ // state: uint32_t
bind_[15].buffer_type = MYSQL_TYPE_LONG;
bind_[15].buffer = reinterpret_cast<char*>(&lease_->state_);
bind_[15].is_unsigned = MLM_TRUE;
bind_[14].buffer = reinterpret_cast<char*>(&hwaddr_source_);
bind_[14].is_unsigned = MLM_TRUE;
- // state: uint32_t
+ // state: uint32_t
bind_[15].buffer_type = MYSQL_TYPE_LONG;
bind_[15].buffer = reinterpret_cast<char*>(&state_);
bind_[15].is_unsigned = MLM_TRUE;
// Set the number of columns in the bind array based on fetch_type
// This is the number of columns expected in the result set
bind_(fetch_type_ ? 4 : 3),
- subnet_id_(0), lease_type_(0), lease_state_(0), state_count_(0) {
+ subnet_id_(0), lease_type_(0), state_(0), state_count_(0) {
validateStatement();
}
// Set the number of columns in the bind array based on fetch_type
// This is the number of columns expected in the result set
bind_(fetch_type_ ? 4 : 3),
- subnet_id_(0), lease_type_(0), lease_state_(0), state_count_(0) {
+ subnet_id_(0), lease_type_(0), state_(0), state_count_(0) {
validateStatement();
}
// Set the number of columns in the bind array based on fetch_type
// This is the number of columns expected in the result set
bind_(fetch_type_ ? 4 : 3),
- subnet_id_(0), lease_type_(0), lease_state_(0), state_count_(0) {
+ subnet_id_(0), lease_type_(0), state_(0), state_count_(0) {
validateStatement();
}
// state: uint32_t
bind_[col].buffer_type = MYSQL_TYPE_LONG;
- bind_[col].buffer = reinterpret_cast<char*>(&lease_state_);
+ bind_[col].buffer = reinterpret_cast<char*>(&state_);
bind_[col].is_unsigned = MLM_TRUE;
++col;
if (status == MLM_MYSQL_FETCH_SUCCESS) {
row.subnet_id_ = static_cast<SubnetID>(subnet_id_);
row.lease_type_ = static_cast<Lease::Type>(lease_type_);
- row.lease_state_ = lease_state_;
+ row.lease_state_ = state_;
row.state_count_ = state_count_;
have_row = true;
} else if (status != MYSQL_NO_DATA) {
" - invalid statement index" << statement_index_);
}
- statement_ = conn_.statements_[statement_index_];
+ statement_ = conn_.handle().statements_[statement_index_];
}
/// @brief Database connection to use to execute the query
/// @brief Receives the lease type when fetching a row
uint32_t lease_type_;
/// @brief Receives the lease state when fetching a row
- uint32_t lease_state_;
+ uint32_t state_;
/// @brief Receives the state count when fetching a row
int64_t state_count_;
};
std::vector<MYSQL_BIND>& bind) {
// Bind the parameters to the statement
- int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
+ int status = mysql_stmt_bind_param(conn_.handle().statements_[stindex], &bind[0]);
checkError(status, stindex, "unable to bind parameters");
// Execute the statement
- status = mysql_stmt_execute(conn_.statements_[stindex]);
+ status = mysql_stmt_execute(conn_.handle().statements_[stindex]);
if (status != 0) {
// 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 (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) {
+ if (mysql_errno(conn_.handle()) == ER_DUP_ENTRY) {
return (false);
}
checkError(status, stindex, "unable to execute");
if (bind) {
// Bind the selection parameters to the statement
- status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
+ status = mysql_stmt_bind_param(conn_.handle().statements_[stindex], bind);
checkError(status, stindex, "unable to bind WHERE clause parameter");
}
// Set up the MYSQL_BIND array for the data being returned and bind it to
// the statement.
std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
- status = mysql_stmt_bind_result(conn_.statements_[stindex], &outbind[0]);
+ status = mysql_stmt_bind_result(conn_.handle().statements_[stindex], &outbind[0]);
checkError(status, stindex, "unable to bind SELECT clause parameters");
// Execute the statement
- status = mysql_stmt_execute(conn_.statements_[stindex]);
+ status = mysql_stmt_execute(conn_.handle().statements_[stindex]);
checkError(status, stindex, "unable to execute");
// Ensure that all the lease information is retrieved in one go to avoid
// overhead of going back and forth between client and server.
- status = mysql_stmt_store_result(conn_.statements_[stindex]);
+ status = mysql_stmt_store_result(conn_.handle().statements_[stindex]);
checkError(status, stindex, "unable to set up for storing all results");
// Set up the fetch "release" object to release resources associated
// with the call to mysql_stmt_fetch when this method exits, then
// retrieve the data.
- MySqlFreeResult fetch_release(conn_.statements_[stindex]);
+ MySqlFreeResult fetch_release(conn_.handle().statements_[stindex]);
int count = 0;
- while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) == 0) {
+ while ((status = mysql_stmt_fetch(conn_.handle().statements_[stindex])) == 0) {
try {
result.push_back(exchange->getLeaseData());
MySqlLeaseMgr::getLeases6(const DUID& duid) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_DUID)
.arg(duid.toText());
-
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
const_cast<uint8_t*>(&duid_vector[0]));
inbind[0].buffer_length = duid_length;
inbind[0].length = &duid_length;
-
+
Lease6Collection result;
-
+
getLeaseCollection(GET_LEASE6_DUID, inbind, result);
return result;
const LeasePtr& lease) {
// Bind the parameters to the statement
- int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
+ int status = mysql_stmt_bind_param(conn_.handle().statements_[stindex], bind);
checkError(status, stindex, "unable to bind parameters");
// Execute
- status = mysql_stmt_execute(conn_.statements_[stindex]);
+ status = mysql_stmt_execute(conn_.handle().statements_[stindex]);
checkError(status, stindex, "unable to execute");
// See how many rows were affected. The statement should only update a
// single row.
- int affected_rows = mysql_stmt_affected_rows(conn_.statements_[stindex]);
+ int affected_rows = mysql_stmt_affected_rows(conn_.handle().statements_[stindex]);
if (affected_rows == 0) {
isc_throw(NoSuchLease, "unable to update lease for address " <<
lease->addr_ << " as it does not exist");
MySqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind) {
// Bind the input parameters to the statement
- int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
+ int status = mysql_stmt_bind_param(conn_.handle().statements_[stindex], bind);
checkError(status, stindex, "unable to bind WHERE clause parameter");
// Execute
- status = mysql_stmt_execute(conn_.statements_[stindex]);
+ status = mysql_stmt_execute(conn_.handle().statements_[stindex]);
checkError(status, stindex, "unable to execute");
// See how many rows were affected. Note that the statement may delete
// multiple rows.
- return (static_cast<uint64_t>(mysql_stmt_affected_rows(conn_.statements_[stindex])));
+ return (static_cast<uint64_t>(mysql_stmt_affected_rows(conn_.handle().statements_[stindex])));
}
bool
DHCPSRV_MYSQL_GET_VERSION);
// Allocate a new statement.
- MYSQL_STMT *stmt = mysql_stmt_init(conn_.mysql_);
+ MYSQL_STMT *stmt = mysql_stmt_init(conn_.handle());
if (stmt == NULL) {
isc_throw(DbOperationError, "unable to allocate MySQL prepared "
- "statement structure, reason: " << mysql_error(conn_.mysql_));
+ "statement structure, reason: " << mysql_error(conn_.handle()));
}
// Prepare the statement from SQL text.
int status = mysql_stmt_prepare(stmt, version_sql, strlen(version_sql));
if (status != 0) {
isc_throw(DbOperationError, "unable to prepare MySQL statement <"
- << version_sql << ">, reason: " << mysql_error(conn_.mysql_));
+ << version_sql << ">, reason: " << mysql_error(conn_.handle()));
}
// Execute the prepared statement.
if (mysql_stmt_execute(stmt) != 0) {
isc_throw(DbOperationError, "cannot execute schema version query <"
- << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ << version_sql << ">, reason: " << mysql_errno(conn_.handle()));
}
// Bind the output of the statement to the appropriate variables.
if (mysql_stmt_bind_result(stmt, bind)) {
isc_throw(DbOperationError, "unable to bind result set for <"
- << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ << version_sql << ">, reason: " << mysql_errno(conn_.handle()));
}
// Fetch the data.
if (mysql_stmt_fetch(stmt)) {
mysql_stmt_close(stmt);
isc_throw(DbOperationError, "unable to bind result set for <"
- << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ << version_sql << ">, reason: " << mysql_errno(conn_.handle()));
}
// Discard the statement and its resources
void
MySqlLeaseMgr::commit() {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
- if (mysql_commit(conn_.mysql_) != 0) {
- isc_throw(DbOperationError, "commit failed: " << mysql_error(conn_.mysql_));
+ if (mysql_commit(conn_.handle()) != 0) {
+ isc_throw(DbOperationError, "commit failed: " << mysql_error(conn_.handle()));
}
}
void
MySqlLeaseMgr::rollback() {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
- if (mysql_rollback(conn_.mysql_) != 0) {
- isc_throw(DbOperationError, "rollback failed: " << mysql_error(conn_.mysql_));
+ if (mysql_rollback(conn_.handle()) != 0) {
+ isc_throw(DbOperationError, "rollback failed: " << mysql_error(conn_.handle()));
}
}
/// @return Lease collection (may be empty if no IPv6 lease found)
/// for the DUID.
virtual Lease6Collection getLeases6(const DUID& duid) const;
-
+
/// @brief Returns range of IPv6 leases using paging.
///
/// This method implements paged browsing of the lease database. The first
// most recently added host is different than the host id of the
// currently processed host.
if (hosts.empty() || row_host_id != hosts.back()->getHostId()) {
- HostPtr host = retrieveHost(r, row, row_host_id);
+ HostPtr host(retrieveHost(r, row, row_host_id));
hosts.push_back(host);
}
}
OptionPtr option_;
};
-} // end of anonymous namespace
+} // namespace
namespace isc {
namespace dhcp {
// PgSqlHostDataSourceImpl::INSERT_HOST
// Inserts a host into the 'hosts' table. Returns the inserted host id.
{13,
- { OID_BYTEA, OID_INT2,
- OID_INT8, OID_INT8, OID_INT8, OID_VARCHAR,
- OID_VARCHAR, OID_VARCHAR, OID_TEXT },
+ { OID_BYTEA, OID_INT2, OID_INT8, OID_INT8, OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR,
+ OID_TEXT, OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR},
"insert_host",
"INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
" dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
}
};
-}; // end anonymous namespace
+} // namespace
PgSqlHostDataSourceImpl::
PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters)
isc_throw(DbOpenError,
"PostgreSQL schema version mismatch: need version: "
<< code_version.first << "." << code_version.second
- << " found version: " << db_version.first << "."
+ << " found version: " << db_version.first << "."
<< db_version.second);
}
PsqlBindArrayPtr& bind_array,
const bool return_last_id) {
uint64_t last_id = 0;
- PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
+ PgSqlResult r(PQexecPrepared(conn_.handle(), tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array->values_[0],
&bind_array->lengths_[0],
bool
PgSqlHostDataSourceImpl::delStatement(StatementIndex stindex,
PsqlBindArrayPtr& bind_array) {
- PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
+ PgSqlResult r(PQexecPrepared(conn_.handle(), tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array->values_[0],
&bind_array->lengths_[0],
ConstHostCollection& result, bool single) const {
exchange->clear();
- PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
+ PgSqlResult r(PQexecPrepared(conn_.handle(), tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array->values_[0],
&bind_array->lengths_[0],
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_HOST_DB_GET_VERSION);
const char* version_sql = "SELECT version, minor FROM schema_version;";
- PgSqlResult r(PQexec(conn_, version_sql));
+ PgSqlResult r(PQexec(conn_.handle(), version_sql));
if(PQresultStatus(r) != PGRES_TUPLES_OK) {
isc_throw(DbOperationError, "unable to execute PostgreSQL statement <"
- << version_sql << ">, reason: " << PQerrorMessage(conn_));
+ << version_sql << ">, reason: " << PQerrorMessage(conn_.handle()));
}
uint32_t version;
// Miscellaneous database methods.
-std::string PgSqlHostDataSource::getName() const {
+std::string
+PgSqlHostDataSource::getName() const {
std::string name = "";
try {
name = impl_->conn_.getParameter("name");
return (name);
}
-std::string PgSqlHostDataSource::getDescription() const {
+std::string
+PgSqlHostDataSource::getDescription() const {
return (std::string("Host data source that stores host information"
"in PostgreSQL database"));
}
impl_->conn_.rollback();
}
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+} // namespace dhcp
+} // namespace isc
"SELECT address, hwaddr, client_id, "
"valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
"fqdn_fwd, fqdn_rev, hostname, "
- "state, user_context "
+ "state, user_context "
"FROM lease4 "
"WHERE subnet_id = $1"},
"prefix_len = $9, fqdn_fwd = $10, fqdn_rev = $11, hostname = $12, "
"hwaddr = $13, hwtype = $14, hwaddr_source = $15, "
"state = $16, user_context = $17 "
- "WHERE address = $18"},
+ "WHERE address = $18" },
// ALL_LEASE4_STATS
{ 0, { OID_NONE },
"all_lease4_stats",
"SELECT subnet_id, state, leases as state_count"
- " FROM lease4_stat ORDER BY subnet_id, state"},
+ " FROM lease4_stat ORDER BY subnet_id, state" },
// SUBNET_LEASE4_STATS
{ 1, { OID_INT8 },
"SELECT subnet_id, state, leases as state_count"
" FROM lease4_stat "
" WHERE subnet_id = $1 "
- " ORDER BY state"},
+ " ORDER BY state" },
// SUBNET_RANGE_LEASE4_STATS
{ 2, { OID_INT8, OID_INT8 },
"SELECT subnet_id, state, leases as state_count"
" FROM lease4_stat "
" WHERE subnet_id >= $1 and subnet_id <= $2 "
- " ORDER BY subnet_id, state"},
+ " ORDER BY subnet_id, state" },
// ALL_LEASE6_STATS,
{ 0, { OID_NONE },
if (getSelectMode() == ALL_SUBNETS) {
// Run the query with no where clause parameters.
- result_set_.reset(new PgSqlResult(PQexecPrepared(conn_, statement_.name,
+ result_set_.reset(new PgSqlResult(PQexecPrepared(conn_.handle(), statement_.name,
0, 0, 0, 0, 0)));
} else {
// Set up the WHERE clause values
}
// Run the query with where clause parameters.
- result_set_.reset(new PgSqlResult(PQexecPrepared(conn_, statement_.name,
+ result_set_.reset(new PgSqlResult(PQexecPrepared(conn_.handle(), statement_.name,
parms.size(), &parms.values_[0],
&parms.lengths_[0], &parms.formats_[0], 0)));
}
// Now prepare the SQL statements.
int i = 0;
- for( ; tagged_statements[i].text != NULL ; ++i) {
+ for(; tagged_statements[i].text != NULL; ++i) {
conn_.prepareStatement(tagged_statements[i]);
}
bool
PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
PsqlBindArray& bind_array) {
- PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
+ PgSqlResult r(PQexecPrepared(conn_.handle(), tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array.values_[0],
&bind_array.lengths_[0],
LeaseCollection& result,
bool single) const {
const int n = tagged_statements[stindex].nbparams;
- PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name, n,
+ PgSqlResult r(PQexecPrepared(conn_.handle(), tagged_statements[stindex].name, n,
n > 0 ? &bind_array.values_[0] : NULL,
n > 0 ? &bind_array.lengths_[0] : NULL,
n > 0 ? &bind_array.formats_[0] : NULL, 0));
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_ADD_ADDR4).arg(tagged_statements[stindex].name);
- PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
+ PgSqlResult r(PQexecPrepared(conn_.handle(), tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array.values_[0],
&bind_array.lengths_[0],
uint64_t
PgSqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex,
PsqlBindArray& bind_array) {
- PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
+ PgSqlResult r(PQexecPrepared(conn_.handle(), tagged_statements[stindex].name,
tagged_statements[stindex].nbparams,
&bind_array.values_[0],
&bind_array.lengths_[0],
DHCPSRV_PGSQL_GET_VERSION);
const char* version_sql = "SELECT version, minor FROM schema_version;";
- PgSqlResult r(PQexec(conn_, version_sql));
+ PgSqlResult r(PQexec(conn_.handle(), version_sql));
if(PQresultStatus(r) != PGRES_TUPLES_OK) {
isc_throw(DbOperationError, "unable to execute PostgreSQL statement <"
- << version_sql << ", reason: " << PQerrorMessage(conn_));
+ << version_sql << ", reason: " << PQerrorMessage(conn_.handle()));
}
- istringstream tmp;
- uint32_t version;
- tmp.str(PQgetvalue(r, 0, 0));
- tmp >> version;
- tmp.str("");
- tmp.clear();
+ uint32_t major;
+ PgSqlExchange::getColumnValue(r, 0, 0, major);
uint32_t minor;
- tmp.str(PQgetvalue(r, 0, 1));
- tmp >> minor;
+ PgSqlExchange::getColumnValue(r, 0, 1, minor);
- return (make_pair(version, minor));
+ return (make_pair(major, minor));
}
void
conn_.rollback();
}
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+} // namespace dhcp
+} // namespace isc
/// @return Lease collection (may be empty if no IPv6 lease found)
/// for the DUID
virtual Lease6Collection getLeases6(const DUID& duid) const;
-
+
/// @brief Returns range of IPv6 leases using paging.
///
/// This method implements paged browsing of the lease database. The first
MySqlConnection conn(params);
conn.openDatabase();
- int status = mysql_query(conn.mysql_, query.c_str());
+ int status = mysql_query(conn.handle(), query.c_str());
if (status !=0) {
- isc_throw(DbOperationError, "Query failed: " << mysql_error(conn.mysql_));
+ isc_throw(DbOperationError, "Query failed: " << mysql_error(conn.handle()));
}
- MYSQL_RES * res = mysql_store_result(conn.mysql_);
+ MYSQL_RES * res = mysql_store_result(conn.handle());
int numrows = static_cast<int>(mysql_num_rows(res));
mysql_free_result(res);
MySqlConnection conn(params);
ASSERT_NO_THROW(conn.openDatabase());
- int status = mysql_query(conn.mysql_,
+ int status = mysql_query(conn.handle(),
"DROP TABLE IF EXISTS ipv6_reservations");
- ASSERT_EQ(0, status) << mysql_error(conn.mysql_);
+ ASSERT_EQ(0, status) << mysql_error(conn.handle());
// Create a host with a reservation.
HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::1", Host::IDENT_HWADDR, false);
PgSqlConnection conn(params);
conn.openDatabase();
- PgSqlResult r(PQexec(conn, query.c_str()));
+ PgSqlResult r(PQexec(conn.handle(), query.c_str()));
if (PQresultStatus(r) != PGRES_TUPLES_OK) {
- isc_throw(DbOperationError, "Query failed:" << PQerrorMessage(conn));
+ isc_throw(DbOperationError, "Query failed:" << PQerrorMessage(conn.handle()));
}
int numrows = PQntuples(r);
PgSqlConnection conn(params);
ASSERT_NO_THROW(conn.openDatabase());
- PgSqlResult r(PQexec(conn, "DROP TABLE IF EXISTS ipv6_reservations"));
+ PgSqlResult r(PQexec(conn.handle(), "DROP TABLE IF EXISTS ipv6_reservations"));
ASSERT_TRUE (PQresultStatus(r) == PGRES_COMMAND_OK)
- << " drop command failed :" << PQerrorMessage(conn);
+ << " drop command failed :" << PQerrorMessage(conn.handle());
// Create a host with a reservation.
HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::1",
+// Copyright (C) 2017-2019 Deutsche Telekom AG.
+//
+// Authors: Andrei Pavel <andrei.pavel@qualitance.com>
+// Cristian Secareanu <cristian.secareanu@qualitance.com>
+// Razvan Becheriu <razvan.becheriu@qualitance.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
#include <cassert>
#include <config.h>
#include <dhcpsrv/dhcpsrv_log.h>
+// Copyright (C) 2017-2019 Deutsche Telekom AG.
+//
+// Authors: Andrei Pavel <andrei.pavel@qualitance.com>
+// Cristian Secareanu <cristian.secareanu@qualitance.com>
+// Razvan Becheriu <razvan.becheriu@qualitance.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
--- /dev/null
+// Copyright (C) 2019 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 THREAD_RESOURCE_MGR_H
+#define THREAD_RESOURCE_MGR_H
+
+#include <util/threads/lock_guard.h>
+
+#include <boost/shared_ptr.hpp>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+
+namespace isc {
+namespace dhcp {
+
+template <typename Resource>
+class ThreadResourceMgr {
+ typedef boost::shared_ptr<Resource> ResourcePtr;
+public:
+ ResourcePtr resource() {
+ isc::util::thread::LockGuard<std::mutex> lock(&mutex_);
+ auto id = std::this_thread::get_id();
+ if (map_.find(id) != map_.end()) {
+ return map_[id];
+ }
+ ResourcePtr result(new Resource());
+ map_[id] = result;
+ return result;
+ }
+private:
+ std::mutex mutex_;
+ std::unordered_map<std::thread::id, ResourcePtr> map_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // THREAD_RESOURCE_MGR_H
namespace isc {
namespace db {
-bool MySqlHolder::atexit_ = false;
+bool MySqlHolder::atexit_ = []{atexit([]{mysql_library_end();});return true;};
+
+void
+MySqlHolder::openDatabase(MySqlConnection& connection) {
+ if (connected_) {
+ return;
+ }
+ connected_ = true;
+ prepared_ = true;
+ connection.openDatabase();
+ prepared_ = false;
+}
+
+void
+MySqlHolder::prepareStatements(MySqlConnection& connection) {
+ if (prepared_) {
+ return;
+ }
+ clearPrepared();
+ statements_.resize(connection.text_statements_.size(), NULL);
+ uint32_t index = 0;
+ for (auto it = connection.text_statements_.begin();
+ it != connection.text_statements_.end(); ++it) {
+ statements_[index] = mysql_stmt_init(mysql_);
+ if (statements_[index] == NULL) {
+ isc_throw(DbOperationError, "unable to allocate MySQL prepared "
+ "statement structure, reason: " << mysql_error(mysql_));
+ }
+
+ int status = mysql_stmt_prepare(statements_[index], it->c_str(), it->size());
+ if (status != 0) {
+ isc_throw(DbOperationError, "unable to prepare MySQL statement <" <<
+ *it << ">, reason: " << mysql_error(mysql_));
+ }
+ ++index;
+ }
+ prepared_ = true;
+}
/// @todo: Migrate this default value to src/bin/dhcpX/simple_parserX.cc
const int MYSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
// Rollback if the MySqlTransaction::commit wasn't explicitly
// called.
if (!committed_) {
- conn_.rollback();
+ try {
+ conn_.rollback();
+ } catch (...) {
+ }
}
}
// No timeout parameter, we are going to use the default timeout.
stimeout = "";
}
-
if (stimeout.size() > 0) {
// Timeout was given, so try to convert it to an integer.
-
try {
connect_timeout = boost::lexical_cast<unsigned int>(stimeout);
} catch (...) {
// connection after a reconnect as among other things, it drops all our
// pre-compiled statements.
my_bool auto_reconnect = MLM_FALSE;
- int result = mysql_options(mysql_, MYSQL_OPT_RECONNECT, &auto_reconnect);
+ int result = mysql_options(handle(), MYSQL_OPT_RECONNECT, &auto_reconnect);
if (result != 0) {
isc_throw(DbOpenError, "unable to set auto-reconnect option: " <<
- mysql_error(mysql_));
+ mysql_error(handle()));
}
// Make sure we have a large idle time window ... say 30 days...
const char *wait_time = "SET SESSION wait_timeout = 30 * 86400";
- result = mysql_options(mysql_, MYSQL_INIT_COMMAND, wait_time);
+ result = mysql_options(handle(), MYSQL_INIT_COMMAND, wait_time);
if (result != 0) {
isc_throw(DbOpenError, "unable to set wait_timeout " <<
- mysql_error(mysql_));
+ mysql_error(handle()));
}
// Set SQL mode options for the connection: SQL mode governs how what
// invalid data. We want to ensure we get the strictest behavior and
// to reject invalid data with an error.
const char *sql_mode = "SET SESSION sql_mode ='STRICT_ALL_TABLES'";
- result = mysql_options(mysql_, MYSQL_INIT_COMMAND, sql_mode);
+ result = mysql_options(handle(), MYSQL_INIT_COMMAND, sql_mode);
if (result != 0) {
isc_throw(DbOpenError, "unable to set SQL mode options: " <<
- mysql_error(mysql_));
+ mysql_error(handle()));
}
// Connection timeout, the amount of time taken for the client to drop
// the connection if the server is not responding.
- result = mysql_options(mysql_, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
+ result = mysql_options(handle(), MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
if (result != 0) {
isc_throw(DbOpenError, "unable to set database connection timeout: " <<
- mysql_error(mysql_));
+ mysql_error(handle()));
}
// Open the database.
// This makes it hard to distinguish whether the UPDATE changed no rows
// because no row matching the WHERE clause was found, or because a
// row was found but no data was altered.
- MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
+ MYSQL* status = mysql_real_connect(handle(), host, user, password, name,
port, NULL, CLIENT_FOUND_ROWS);
- if (status != mysql_) {
- isc_throw(DbOpenError, mysql_error(mysql_));
+ if (status != handle()) {
+ isc_throw(DbOpenError, mysql_error(handle()));
}
// Enable autocommit. In case transaction is explicitly used, this
// caused issues for some unit tests which were unable to cleanup
// the database after the test because of pending transactions.
// Use of autocommit will eliminate this problem.
- my_bool auto_commit = mysql_autocommit(mysql_, 1);
+ my_bool auto_commit = mysql_autocommit(handle(), 1);
if (auto_commit != MLM_FALSE) {
- isc_throw(DbOperationError, mysql_error(mysql_));
+ isc_throw(DbOperationError, mysql_error(handle()));
}
+ handle().connected_ = true;
+ connected_ = true;
}
MySqlConnection::prepareStatement(uint32_t index, const char* text) {
// Validate that there is space for the statement in the statements array
// and that nothing has been placed there before.
- if ((index >= statements_.size()) || (statements_[index] != NULL)) {
+ if (index >= text_statements_.size()) {
isc_throw(InvalidParameter, "invalid prepared statement index (" <<
- static_cast<int>(index) << ") or indexed prepared " <<
- "statement is not null");
+ static_cast<int>(index) << ")");
}
// All OK, so prepare the statement
text_statements_[index] = std::string(text);
- statements_[index] = mysql_stmt_init(mysql_);
- if (statements_[index] == NULL) {
- isc_throw(DbOperationError, "unable to allocate MySQL prepared "
- "statement structure, reason: " << mysql_error(mysql_));
- }
-
- int status = mysql_stmt_prepare(statements_[index], text, strlen(text));
- if (status != 0) {
- isc_throw(DbOperationError, "unable to prepare MySQL statement <" <<
- text << ">, reason: " << mysql_error(mysql_));
- }
}
void
// Created the MySQL prepared statements for each DML statement.
for (const TaggedStatement* tagged_statement = start_statement;
tagged_statement != end_statement; ++tagged_statement) {
- if (tagged_statement->index >= statements_.size()) {
- statements_.resize(tagged_statement->index + 1, NULL);
+ if (tagged_statement->index >= text_statements_.size()) {
text_statements_.resize(tagged_statement->index + 1,
std::string(""));
}
prepareStatement(tagged_statement->index,
tagged_statement->text);
}
+ prepared_ = true;
}
/// @brief Destructor
MySqlConnection::~MySqlConnection() {
- // Free up the prepared statements, ignoring errors. (What would we do
- // about them? We're destroying this object and are not really concerned
- // with errors on a database connection that is about to go away.)
- for (int i = 0; i < statements_.size(); ++i) {
- if (statements_[i] != NULL) {
- (void) mysql_stmt_close(statements_[i]);
- statements_[i] = NULL;
- }
- }
- statements_.clear();
text_statements_.clear();
}
void
MySqlConnection::convertToDatabaseTime(const time_t cltt,
- const uint32_t valid_lifetime,
- MYSQL_TIME& expire) {
+ const uint32_t valid_lifetime,
+ MYSQL_TIME& expire) {
MySqlBinding::convertToDatabaseTime(cltt, valid_lifetime, expire);
}
DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_START_TRANSACTION);
// We create prepared statements for all other queries, but MySQL
// don't support prepared statements for START TRANSACTION.
- int status = mysql_query(mysql_, "START TRANSACTION");
+ int status = mysql_query(handle(), "START TRANSACTION");
if (status != 0) {
isc_throw(DbOperationError, "unable to start transaction, "
- "reason: " << mysql_error(mysql_));
+ "reason: " << mysql_error(handle()));
}
}
void
MySqlConnection::commit() {
DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_COMMIT);
- if (mysql_commit(mysql_) != 0) {
+ if (mysql_commit(handle()) != 0) {
isc_throw(DbOperationError, "commit failed: "
- << mysql_error(mysql_));
+ << mysql_error(handle()));
}
}
void
MySqlConnection::rollback() {
DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_ROLLBACK);
- if (mysql_rollback(mysql_) != 0) {
+ if (mysql_rollback(handle()) != 0) {
isc_throw(DbOperationError, "rollback failed: "
- << mysql_error(mysql_));
+ << mysql_error(handle()));
}
}
-
-} // namespace isc::db
-} // namespace isc
+} // namespace db
+} // namespace isc
#include <database/database_connection.h>
#include <database/db_exceptions.h>
#include <database/db_log.h>
+#include <dhcpsrv/thread_resource_mgr.h>
#include <exceptions/exceptions.h>
#include <mysql/mysql_binding.h>
#include <mysql/mysql_constants.h>
namespace isc {
namespace db {
-
/// @brief Fetch and Release MySQL Results
///
/// When a MySQL statement is expected, to fetch the results the function
class MySqlFreeResult {
public:
-
/// @brief Constructor
///
/// Store the pointer to the statement for which data is being fetched.
/// way, any error from mysql_stmt_free_result is ignored. (Generating
/// an exception is not much help, as it will only confuse things if the
/// method calling mysql_stmt_fetch is exiting via an exception.)
- MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement)
- {}
+ MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement) {
+ }
/// @brief Destructor
///
/// Frees up fetch context if a fetch has been successfully executed.
~MySqlFreeResult() {
- (void) mysql_stmt_free_result(statement_);
+ (void)mysql_stmt_free_result(statement_);
}
private:
- MYSQL_STMT* statement_; ///< Statement for which results are freed
+ MYSQL_STMT* statement_; ///< Statement for which results are freed
};
/// @brief MySQL Selection Statements
const char* text;
};
+
+/// @brief Forward declaration to @ref MySqlConnection.
+class MySqlConnection;
+
/// @brief MySQL Handle Holder
///
/// Small RAII object for safer initialization, will close the database
/// For this reason, the class is declared noncopyable.
class MySqlHolder : public boost::noncopyable {
public:
-
/// @brief Constructor
///
/// Push a call to mysql_library_end() at exit time.
/// Initialize MySql and store the associated context object.
///
/// @throw DbOpenError Unable to initialize MySql handle.
- MySqlHolder() : mysql_(mysql_init(NULL)) {
- if (!atexit_) {
- atexit([]{ mysql_library_end(); });
- atexit_ = true;
- }
+ MySqlHolder() : connected_(false), prepared_(false),
+ mysql_(mysql_init(NULL)) {
if (mysql_ == NULL) {
isc_throw(db::DbOpenError, "unable to initialize MySQL");
}
///
/// Frees up resources allocated by the initialization of MySql.
~MySqlHolder() {
+ clearPrepared();
if (mysql_ != NULL) {
mysql_close(mysql_);
}
return (mysql_);
}
+ void clearPrepared() {
+ // Free up the prepared statements, ignoring errors. (What would we do
+ // about them? We're destroying this object and are not really concerned
+ // with errors on a database connection that is about to go away.)
+ for (int i = 0; i < statements_.size(); ++i) {
+ if (statements_[i] != NULL) {
+ (void) mysql_stmt_close(statements_[i]);
+ statements_[i] = NULL;
+ }
+ }
+ statements_.clear();
+ }
+
+ void openDatabase(MySqlConnection& connection);
+
+ void prepareStatements(MySqlConnection& connection);
+
+ /// @brief Prepared statements
+ ///
+ /// This field is public, because it is used heavily from MySqlConnection
+ /// and will be from MySqlHostDataSource.
+ std::vector<MYSQL_STMT*> statements_;
+
+ bool connected_; ///< Flag to indicate openDatabase has been called
+
private:
+ bool prepared_; ///< Flag to indicate prepareStatements has been called
+
static bool atexit_; ///< Flag to call atexit once.
MYSQL* mysql_; ///< Initialization context
};
-/// @brief Forward declaration to @ref MySqlConnection.
-class MySqlConnection;
-
/// @brief RAII object representing MySQL transaction.
///
/// An instance of this class should be created in a scope where multiple
/// database which don't use transactions will still be auto committed.
class MySqlTransaction : public boost::noncopyable {
public:
-
/// @brief Constructor.
///
/// Starts transaction by making a "START TRANSACTION" query.
void commit();
private:
-
/// @brief Holds reference to the MySQL database connection.
MySqlConnection& conn_;
bool committed_;
};
-
/// @brief Common MySQL Connector Pool
///
/// This class provides common operations for MySQL database connection
/// that use instances of MySqlConnection.
class MySqlConnection : public db::DatabaseConnection {
public:
-
/// @brief Function invoked to process fetched row.
typedef std::function<void(MySqlBindingCollection&)> ConsumeResultFun;
///
/// Initialize MySqlConnection object with parameters needed for connection.
MySqlConnection(const ParameterMap& parameters)
- : DatabaseConnection(parameters) {
+ : DatabaseConnection(parameters), connected_(false), prepared_(false) {
}
/// @brief Destructor
int status = 0;
if (!in_bind_vec.empty()) {
// Bind parameters to the prepared statement.
- status = mysql_stmt_bind_param(statements_[index], &in_bind_vec[0]);
+ status = mysql_stmt_bind_param(handle().statements_[index], &in_bind_vec[0]);
checkError(status, index, "unable to bind parameters for select");
}
out_bind_vec.push_back(out_binding->getMySqlBinding());
}
if (!out_bind_vec.empty()) {
- status = mysql_stmt_bind_result(statements_[index], &out_bind_vec[0]);
+ status = mysql_stmt_bind_result(handle().statements_[index], &out_bind_vec[0]);
checkError(status, index, "unable to bind result parameters for select");
}
// Execute query.
- status = mysql_stmt_execute(statements_[index]);
+ status = mysql_stmt_execute(handle().statements_[index]);
checkError(status, index, "unable to execute");
- status = mysql_stmt_store_result(statements_[index]);
+ status = mysql_stmt_store_result(handle().statements_[index]);
checkError(status, index, "unable to set up for storing all results");
// Fetch results.
- MySqlFreeResult fetch_release(statements_[index]);
- while ((status = mysql_stmt_fetch(statements_[index])) ==
+ MySqlFreeResult fetch_release(handle().statements_[index]);
+ while ((status = mysql_stmt_fetch(handle().statements_[index])) ==
MLM_MYSQL_FETCH_SUCCESS) {
try {
// For each returned row call user function which should
}
// Bind the parameters to the statement
- int status = mysql_stmt_bind_param(statements_[index], &in_bind_vec[0]);
+ int status = mysql_stmt_bind_param(handle().statements_[index], &in_bind_vec[0]);
checkError(status, index, "unable to bind parameters");
// Execute the statement
- status = mysql_stmt_execute(statements_[index]);
+ status = mysql_stmt_execute(handle().statements_[index]);
if (status != 0) {
// Failure: check for the special case of duplicate entry.
- if (mysql_errno(mysql_) == ER_DUP_ENTRY) {
+ if (mysql_errno(handle()) == ER_DUP_ENTRY) {
isc_throw(DuplicateEntry, "Database duplicate entry error");
}
checkError(status, index, "unable to execute");
}
// Bind the parameters to the statement
- int status = mysql_stmt_bind_param(statements_[index], &in_bind_vec[0]);
+ int status = mysql_stmt_bind_param(handle().statements_[index], &in_bind_vec[0]);
checkError(status, index, "unable to bind parameters");
// Execute the statement
- status = mysql_stmt_execute(statements_[index]);
+ status = mysql_stmt_execute(handle().statements_[index]);
if (status != 0) {
// Failure: check for the special case of duplicate entry.
- if ((mysql_errno(mysql_) == ER_DUP_ENTRY)
+ if ((mysql_errno(handle()) == ER_DUP_ENTRY)
#ifdef ER_FOREIGN_DUPLICATE_KEY
- || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY)
+ || (mysql_errno(handle()) == ER_FOREIGN_DUPLICATE_KEY)
#endif
#ifdef ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO
- || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO)
+ || (mysql_errno(handle()) == ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO)
#endif
#ifdef ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO
- || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO)
+ || (mysql_errno(handle()) == ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO)
#endif
) {
isc_throw(DuplicateEntry, "Database duplicate entry error");
}
// Let's return how many rows were affected.
- return (static_cast<uint64_t>(mysql_stmt_affected_rows(statements_[index])));
+ return (static_cast<uint64_t>(mysql_stmt_affected_rows(handle().statements_[index])));
}
///
/// @throw isc::db::DbOperationError An operation on the open database has
/// failed.
- template<typename StatementIndex>
+ template <typename StatementIndex>
void checkError(const int status, const StatementIndex& index,
const char* what) const {
if (status != 0) {
- switch(mysql_errno(mysql_)) {
+ switch(mysql_errno(handle())) {
// These are the ones we consider fatal. Remember this method is
// used to check errors of API calls made subsequent to successfully
// connecting. Errors occurring while attempting to connect are
DB_LOG_ERROR(db::MYSQL_FATAL_ERROR)
.arg(what)
.arg(text_statements_[static_cast<int>(index)])
- .arg(mysql_error(mysql_))
- .arg(mysql_errno(mysql_));
+ .arg(mysql_error(handle()))
+ .arg(mysql_errno(handle()));
// If there's no lost db callback or it returns false,
// then we're not attempting to recover so we're done
if (!invokeDbLostCallback()) {
- exit (-1);
+ exit(-1);
}
// We still need to throw so caller can error out of the current
isc_throw(db::DbOperationError, what << " for <"
<< text_statements_[static_cast<int>(index)]
<< ">, reason: "
- << mysql_error(mysql_) << " (error code "
- << mysql_errno(mysql_) << ")");
+ << mysql_error(handle()) << " (error code "
+ << mysql_errno(handle()) << ")");
}
}
}
- /// @brief Prepared statements
- ///
- /// This field is public, because it is used heavily from MySqlConnection
- /// and will be from MySqlHostDataSource.
- std::vector<MYSQL_STMT*> statements_;
-
/// @brief Raw text of statements
///
/// This field is public, because it is used heavily from MySqlConnection
- /// and will be from MySqlHostDataSource.
+ /// and from MySqlHostDataSource.
std::vector<std::string> text_statements_;
/// @brief MySQL connection handle
///
/// This field is public, because it is used heavily from MySqlConnection
- /// and will be from MySqlHostDataSource.
- MySqlHolder mysql_;
+ /// and from MySqlHostDataSource.
+ MySqlHolder& handle() const {
+ auto result = handles_.resource();
+ if (connected_) {
+ result->openDatabase(*(const_cast<MySqlConnection*>(this)));
+ }
+ if (prepared_) {
+ result->prepareStatements(*(const_cast<MySqlConnection*>(this)));
+ }
+ return *result;
+ }
+
+private:
+ bool connected_; ///< Flag to indicate openDatabase has been called
+
+ bool prepared_; ///< Flag to indicate prepareStatements has been called
+
+ mutable isc::dhcp::ThreadResourceMgr<MySqlHolder> handles_;
};
-}; // end of isc::db namespace
-}; // end of isc namespace
+} // namespace db
+} // namespace isc
-#endif // MYSQL_CONNECTION_H
+#endif // MYSQL_CONNECTION_H
///
/// @param sql Query in the textual form.
void runQuery(const std::string& sql) {
- MYSQL_STMT *stmt = mysql_stmt_init(conn_.mysql_);
+ MYSQL_STMT *stmt = mysql_stmt_init(conn_.handle());
if (stmt == NULL) {
isc_throw(DbOperationError, "unable to allocate MySQL prepared "
- "statement structure, reason: " << mysql_error(conn_.mysql_));
+ "statement structure, reason: " << mysql_error(conn_.handle()));
}
int status = mysql_stmt_prepare(stmt, sql.c_str(), sql.length());
if (status != 0) {
isc_throw(DbOperationError, "unable to prepare MySQL statement <"
- << sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ << sql << ">, reason: " << mysql_errno(conn_.handle()));
}
// Execute the prepared statement.
if (mysql_stmt_execute(stmt) != 0) {
isc_throw(DbOperationError, "cannot execute MySQL query <"
- << sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ << sql << ">, reason: " << mysql_errno(conn_.handle()));
}
// Discard the statement and its resources
const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
+PgSqlHolder::~PgSqlHolder() {
+ if (pgconn_ != NULL) {
+ // Deallocate the prepared queries.
+ if (PQstatus(pgconn_) == CONNECTION_OK) {
+ PgSqlResult r(PQexec(pgconn_, "DEALLOCATE all"));
+ if(PQresultStatus(r) != PGRES_COMMAND_OK) {
+ // Highly unlikely but we'll log it and go on.
+ DB_LOG_ERROR(PGSQL_DEALLOC_ERROR)
+ .arg(PQerrorMessage(pgconn_));
+ }
+ }
+ PQfinish(pgconn_);
+ }
+}
+
+void
+PgSqlHolder::openDatabase(PgSqlConnection& connection) {
+ if (connected_) {
+ return;
+ }
+ connected_ = true;
+ prepared_ = true;
+ connection.openDatabase();
+ prepared_ = false;
+}
+
+void
+PgSqlHolder::prepareStatements(PgSqlConnection& connection) {
+ if (prepared_) {
+ return;
+ }
+ // Prepare all statements queries with all known fields datatype
+ for (auto it = connection.statements_.begin();
+ it != connection.statements_.end(); ++it) {
+ PgSqlResult r(PQprepare(pgconn_, (*it)->name, (*it)->text,
+ (*it)->nbparams, (*it)->types));
+ if(PQresultStatus(r) != PGRES_COMMAND_OK) {
+ isc_throw(DbOperationError, "unable to prepare PostgreSQL statement: "
+ << (*it)->text << ", reason: " << PQerrorMessage(pgconn_));
+ }
+ }
+ prepared_ = true;
+}
+
PgSqlResult::PgSqlResult(PGresult *result)
: result_(result), rows_(0), cols_(0) {
if (!result) {
PgSqlTransaction::~PgSqlTransaction() {
// If commit() wasn't explicitly called, rollback.
if (!committed_) {
- conn_.rollback();
+ try {
+ conn_.rollback();
+ } catch (...) {
+ }
}
}
}
PgSqlConnection::~PgSqlConnection() {
- if (conn_) {
- // Deallocate the prepared queries.
- if (PQstatus(conn_) == CONNECTION_OK) {
- PgSqlResult r(PQexec(conn_, "DEALLOCATE all"));
- if(PQresultStatus(r) != PGRES_COMMAND_OK) {
- // Highly unlikely but we'll log it and go on.
- DB_LOG_ERROR(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_));
- }
+ statements_.push_back(&statement);
}
void
tagged_statement != end_statement; ++tagged_statement) {
prepareStatement(*tagged_statement);
}
+ prepared_ = true;
}
void
}
// We have a valid connection, so let's save it to our holder
- conn_.setConnection(new_conn);
+ handle().setConnection(new_conn);
+ handle().connected_ = true;
+ connected_ = true;
}
bool
(memcmp(sqlstate, "58", 2) == 0))) { // System error
DB_LOG_ERROR(PGSQL_FATAL_ERROR)
.arg(statement.name)
- .arg(PQerrorMessage(conn_))
+ .arg(PQerrorMessage(handle()))
.arg(sqlstate ? sqlstate : "<sqlstate null>");
// If there's no lost db callback or it returns false,
}
// Apparently it wasn't fatal, so we throw with a helpful message.
- const char* error_message = PQerrorMessage(conn_);
+ const char* error_message = PQerrorMessage(handle());
isc_throw(DbOperationError, "Statement exec failed:" << " for: "
<< statement.name << ", status: " << s
<< "sqlstate:[ " << (sqlstate ? sqlstate : "<null>")
void
PgSqlConnection::startTransaction() {
DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_START_TRANSACTION);
- PgSqlResult r(PQexec(conn_, "START TRANSACTION"));
+ PgSqlResult r(PQexec(handle(), "START TRANSACTION"));
if (PQresultStatus(r) != PGRES_COMMAND_OK) {
- const char* error_message = PQerrorMessage(conn_);
+ const char* error_message = PQerrorMessage(handle());
isc_throw(DbOperationError, "unable to start transaction"
<< error_message);
}
void
PgSqlConnection::commit() {
DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_COMMIT);
- PgSqlResult r(PQexec(conn_, "COMMIT"));
+ PgSqlResult r(PQexec(handle(), "COMMIT"));
if (PQresultStatus(r) != PGRES_COMMAND_OK) {
- const char* error_message = PQerrorMessage(conn_);
+ const char* error_message = PQerrorMessage(handle());
isc_throw(DbOperationError, "commit failed: " << error_message);
}
}
void
PgSqlConnection::rollback() {
DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_ROLLBACK);
- PgSqlResult r(PQexec(conn_, "ROLLBACK"));
+ PgSqlResult r(PQexec(handle(), "ROLLBACK"));
if (PQresultStatus(r) != PGRES_COMMAND_OK) {
- const char* error_message = PQerrorMessage(conn_);
+ const char* error_message = PQerrorMessage(handle());
isc_throw(DbOperationError, "rollback failed: " << error_message);
}
}
#define PGSQL_CONNECTION_H
#include <database/database_connection.h>
+#include <dhcpsrv/thread_resource_mgr.h>
#include <libpq-fe.h>
#include <boost/scoped_ptr.hpp>
}
private:
- PGresult* result_; ///< Result set to be freed
- int rows_; ///< Number of rows in the result set
- int cols_; ///< Number of columns in the result set
+ PGresult* result_; ///< Result set to be freed
+ int rows_; ///< Number of rows in the result set
+ int cols_; ///< Number of columns in the result set
};
+/// @brief Forward declaration to @ref PgSqlConnection.
+class PgSqlConnection;
/// @brief Postgresql connection handle Holder
///
/// For this reason, the class is declared noncopyable.
class PgSqlHolder : public boost::noncopyable {
public:
-
/// @brief Constructor
///
/// Sets the Postgresql API connector handle to NULL.
///
- PgSqlHolder() : pgconn_(NULL) {
+ PgSqlHolder() : connected_(false), prepared_(false), pgconn_(NULL) {
}
/// @brief Destructor
///
/// Frees up resources allocated by the connection.
- ~PgSqlHolder() {
- if (pgconn_ != NULL) {
- PQfinish(pgconn_);
- }
- }
+ ~PgSqlHolder();
/// @brief Sets the connection to the value given
///
pgconn_ = connection;
}
+ void openDatabase(PgSqlConnection& connection);
+
+ void prepareStatements(PgSqlConnection& connection);
+
/// @brief Conversion Operator
///
/// Allows the PgSqlHolder object to be passed as the context argument to
return (pgconn_);
}
- /// @brief Boolean Operator
- ///
- /// Allows testing the connection for emptiness: "if (holder)"
- operator bool() const {
- return (pgconn_);
- }
+ bool connected_; ///< Flag to indicate openDatabase has been called
private:
- PGconn* pgconn_; ///< Postgresql connection
-};
+ bool prepared_; ///< Flag to indicate prepareStatements has been called
-/// @brief Forward declaration to @ref PgSqlConnection.
-class PgSqlConnection;
+ PGconn* pgconn_; ///< Postgresql connection
+};
/// @brief RAII object representing a PostgreSQL transaction.
///
///
/// Initialize PgSqlConnection object with parameters needed for connection.
PgSqlConnection(const ParameterMap& parameters)
- : DatabaseConnection(parameters) {
+ : DatabaseConnection(parameters), connected_(false), prepared_(false) {
}
/// @brief Destructor
void checkStatementError(const PgSqlResult& r,
PgSqlTaggedStatement& statement) const;
+ /// @brief Raw statements
+ ///
+ /// This field is public, because it is used heavily from PgSqlConnection
+ /// and will be from MySqlHostDataSource.
+ std::vector<const PgSqlTaggedStatement*> statements_;
+
/// @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_);
+ PgSqlHolder& handle() const {
+ auto result = handles_.resource();
+ if (connected_) {
+ result->openDatabase(*(const_cast<PgSqlConnection*>(this)));
+ }
+ if (prepared_) {
+ result->prepareStatements(*(const_cast<PgSqlConnection*>(this)));
+ }
+ return *result;
}
- /// @brief Boolean Operator
- ///
- /// Allows testing the PgConnection for initialized connection
- operator bool() const {
- return (conn_);
- }
+private:
+ bool connected_; ///< Flag to indicate openDatabase has been called
+
+ bool prepared_; ///< Flag to indicate prepareStatements has been called
+ mutable isc::dhcp::ThreadResourceMgr<PgSqlHolder> handles_;
};
}; // end of isc::db namespace
/// @return Returns true if there are no entries in the array, false
/// otherwise.
bool empty() const {
-
return (values_.empty());
}
protected:
/// @brief Stores text labels for columns, currently only used for
/// logging and errors.
- std::vector<std::string>columns_;
+ std::vector<std::string> columns_;
};
}; // end of isc::db namespace
" varchar_col VARCHAR(255) "
"); ";
- PgSqlResult r(PQexec(*conn_, sql));
+ PgSqlResult r(PQexec(conn_->handle(), sql));
ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
- << " create basics table failed: " << PQerrorMessage(*conn_);
+ << " create basics table failed: " << PQerrorMessage(conn_->handle());
}
/// @brief Destroys the basics table
/// Asserts if the destruction fails
void destroySchema() {
if (conn_) {
- PgSqlResult r(PQexec(*conn_, "DROP TABLE IF EXISTS basics;"));
+ PgSqlResult r(PQexec(conn_->handle(), "DROP TABLE IF EXISTS basics;"));
ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
- << " drop basics table failed: " << PQerrorMessage(*conn_);
+ << " drop basics table failed: " << PQerrorMessage(conn_->handle());
}
}
/// Asserts if the result set status does not equal the expected outcome.
void runSql(PgSqlResultPtr& r, const std::string& sql, int exp_outcome,
int lineno) {
- r.reset(new PgSqlResult(PQexec(*conn_, sql.c_str())));
+ r.reset(new PgSqlResult(PQexec(conn_->handle(), sql.c_str())));
ASSERT_EQ(PQresultStatus(*r), exp_outcome)
<< " runSql at line: " << lineno << " failed, sql:[" << sql
- << "]\n reason: " << PQerrorMessage(*conn_);
+ << "]\n reason: " << PQerrorMessage(conn_->handle());
}
/// @brief Executes a SQL statement and tests for an expected outcome
PgSqlTaggedStatement& statement,
PsqlBindArrayPtr bind_array, int exp_outcome,
int lineno) {
- r.reset(new PgSqlResult(PQexecPrepared(*conn_, statement.name,
+ r.reset(new PgSqlResult(PQexecPrepared(conn_->handle(), statement.name,
statement.nbparams,
&bind_array->values_[0],
&bind_array->lengths_[0],
ASSERT_EQ(PQresultStatus(*r), exp_outcome)
<< " runPreparedStatement at line: " << lineno
<< " statement name:[" << statement.name
- << "]\n reason: " << PQerrorMessage(*conn_);
+ << "]\n reason: " << PQerrorMessage(conn_->handle());
}
/// @brief Fetches all of the rows currently in the table