Commit f2b088d0 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[github35] Many comments updated after review.

parent 161dc9c3
......@@ -22,8 +22,6 @@
#include <dhcpsrv/db_exceptions.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <memory> // for std::unique_ptr
namespace isc {
namespace dhcp {
......@@ -39,14 +37,15 @@ CqlConnection::~CqlConnection() {
CassError rc = CASS_OK;
std::string error;
// Let's free the prepared statements.
for (StatementMapEntry s : statements_) {
// typeid(s.second.first) is CassPrepared*
CqlTaggedStatement statement = s.second;
if (statement.prepared_statement_) {
cass_prepared_free(statement.prepared_statement_);
}
}
// If there's a session, tear it down and free the resources.
if (session_) {
cass_schema_meta_free(schema_meta_);
CassFuture* close_future = cass_session_close(session_);
......@@ -60,6 +59,7 @@ CqlConnection::~CqlConnection() {
session_ = NULL;
}
// Free the cluster if there's one.
if (cluster_) {
cass_cluster_free(cluster_);
cluster_ = NULL;
......
......@@ -34,12 +34,15 @@ namespace isc {
namespace dhcp {
/// @brief Pair containing major and minor versions
/// @todo: This is already defined in lease_mgr.h. Need to have one
/// definition. May need to move it if necessary.
typedef std::pair<uint32_t, uint32_t> VersionPair;
/// @brief Statement index representing the statement name
typedef char const* const StatementTag;
/// @brief Define CQL backend version: 2.3
/// @brief Define CQL backend version. The CASS_VERSION_* constants
/// are defined in a header provided by cpp-driver.
/// @{
constexpr uint32_t CQL_DRIVER_VERSION_MAJOR = CASS_VERSION_MAJOR;
constexpr uint32_t CQL_DRIVER_VERSION_MINOR = CASS_VERSION_MINOR;
......@@ -52,31 +55,33 @@ constexpr uint32_t CQL_SCHEMA_VERSION_MINOR = 0u;
/// @}
/// @brief Defines a single statement or query
///
/// @param name_ short description of the query
/// @param text_ text representation of the actual query
/// @param prepared_statement_ internal Cassandra object representing the
/// prepared statement
/// @param is_raw_statement_ shows if statement should be executed rawly or with
/// binds
struct CqlTaggedStatement {
/// Short description of the query
StatementTag name_;
/// Text representation of the actual query
char const* const text_;
/// Internal Cassandra object representing the prepared statement
const CassPrepared* prepared_statement_;
bool is_raw_statement_;
/// Should the statement be executed raw or with binds?
bool is_raw_;
/// @brief Constructor
/// @param name brief name of the query
/// @param text text (CQL) representation of the query
CqlTaggedStatement(StatementTag name, char const* const text)
: name_(name), text_(text), prepared_statement_(NULL),
is_raw_statement_(false) {
: name_(name), text_(text), prepared_statement_(NULL), is_raw_(false) {
}
/// @brief Constructor
CqlTaggedStatement(StatementTag name,
char const* const text,
bool const& is_raw_statement)
: name_(name), text_(text), prepared_statement_(NULL),
is_raw_statement_(is_raw_statement) {
/// @param name brief name of the query
/// @param text text (CQL) representation of the query
/// @param is_raw should the statement be executed raw?
CqlTaggedStatement(StatementTag name, char const* const text, bool const& is_raw)
: name_(name), text_(text), prepared_statement_(NULL), is_raw_(is_raw) {
}
};
......@@ -96,30 +101,26 @@ struct StatementTagEqual {
}
};
/// @brief Contains all statements.
typedef std::unordered_map<StatementTag,
CqlTaggedStatement,
StatementTagHash,
StatementTagEqual>
StatementMap;
/// @brief A container for all statements.
typedef std::unordered_map<StatementTag, CqlTaggedStatement,
StatementTagHash, StatementTagEqual> StatementMap;
/// @brief A type for a single entry on the statements map
typedef std::pair<StatementTag, CqlTaggedStatement> StatementMapEntry;
/// @brief Common CQL connector pool
///
/// Provides common operations for the Cassandra database connection used by
/// CqlLeaseMgr, CqlHostDataSource and CqlSrvConfigMgr. Manages the
/// connection
/// to the Cassandra database and preparing of compiled statements. Its
/// fields
/// are public because they are used (both set and retrieved) in classes
/// that
/// CqlLeaseMgr, CqlHostDataSource and CqlSrvConfigMgr. Manages the connection
/// to the Cassandra database and preparing of compiled statements. Its fields
/// are public because they are used (both set and retrieved) in classes that
/// use instances of CqlConnection.
class CqlConnection : public DatabaseConnection {
public:
/// @brief Constructor
///
/// Initialize CqlConnection object with parameters needed for connection.
/// @param parameters specify the connection details (username, ip addresses etc.)
explicit CqlConnection(const ParameterMap& parameters);
/// @brief Destructor
......@@ -129,6 +130,7 @@ public:
///
/// Creates the prepared statements for all of the CQL statements used
/// by the CQL backend.
/// @param statements statements to be prepared
///
/// @throw isc::dhcp::DbOperationError if an operation on the open database
/// has failed
......@@ -140,7 +142,16 @@ public:
///
/// Opens the database using the information supplied in the parameters
/// passed to the constructor. If no parameters are supplied, the default
/// values will be used (e.g. keyspace 'keatest', port 9042).
/// values will be used. The defaults are:
/// - contact points: 127.0.0.1
/// - port 9042
/// - no user, no password
/// - keyspace keatest
/// - reconnect-wait-time 2000
/// - connect-timeout 5000
/// - request-timeout 12000
/// - tcp-keepalive no
/// - tcp-nodelay no
///
/// @throw DbOpenError error opening the database
void openDatabase();
......@@ -159,7 +170,14 @@ public:
/// @brief Check for errors
///
/// Check for errors on the current database operation.
/// Check for errors on the current database operation and returns text
/// description of what happened. In case of success, also returns
/// some logging friendly text.
///
/// @param what text description of the operation
/// @param future the structure that holds the status of operation
/// @param statement_tag statement that was used (optional)
/// @return text description of the error
static const std::string
checkFutureError(const std::string& what,
CassFuture* future,
......
......@@ -47,6 +47,8 @@ namespace dhcp {
} \
}
/// @brief a helper structure with a function call operator that returns
/// key value in a format expected by std::hash.
struct ExchangeDataTypeHash {
public:
size_t operator()(const ExchangeDataType& key) const {
......@@ -54,13 +56,14 @@ public:
}
};
/// @brief Defines a type for storing aux. Cassandra functions
typedef std::unordered_map<ExchangeDataType, CqlFunction, ExchangeDataTypeHash>
CqlFunctionMap;
extern CqlFunctionMap CQL_FUNCTIONS;
/// @brief hash function for CassTypeMap
///
/// Required by c++ versions 5 and below.
/// Required by g++ versions 5 and below.
///
/// @param key being hashed
///
......@@ -72,9 +75,12 @@ hash_value(const CassValueType& key) {
/// @brief Map types used to determine exchange type
/// @{
/// @brief Defines type that maps specific type to an enum
typedef std::unordered_map<std::type_index, ExchangeDataType> AnyTypeMap;
// Declare uint8_t as key here for compatibility with c++-5. Ideally, it would
// be CassValueType
// Declare uint8_t as key here for compatibility with g++ version 5. Ideally,
// it would be CassValueType
typedef std::unordered_map<uint8_t, ExchangeDataType> CassTypeMap;
/// @}
......@@ -89,8 +95,8 @@ static AnyTypeMap ANY_TYPE_MAP = {
{typeid(std::string*), EXCHANGE_DATA_TYPE_STRING},
{typeid(CassBlob*), EXCHANGE_DATA_TYPE_BYTES},
{typeid(CassUuid*), EXCHANGE_DATA_TYPE_UUID},
{typeid(Udt*), EXCHANGE_DATA_TYPE_UDT},
{typeid(Collection*), EXCHANGE_DATA_TYPE_COLLECTION}};
{typeid(Udt*), EXCHANGE_DATA_TYPE_UDT}, // user data type
{typeid(AnyCollection*), EXCHANGE_DATA_TYPE_COLLECTION}};
/// @brief Maps Cassandra type to exchange type
static CassTypeMap CASS_TYPE_MAP = {
......@@ -121,7 +127,7 @@ static CassTypeMap CASS_TYPE_MAP = {
{CASS_VALUE_TYPE_UDT, EXCHANGE_DATA_TYPE_UDT},
{CASS_VALUE_TYPE_TUPLE, EXCHANGE_DATA_TYPE_UDT}};
/// @brief Udt method implementations
/// @brief Udt (user data type) method implementations
/// @{
Udt::Udt(const CqlConnection& connection, const std::string& name)
: AnyArray(), connection_(connection), name_(name) {
......@@ -142,6 +148,8 @@ Udt::Udt(const CqlConnection& connection, const std::string& name)
}
Udt::~Udt() {
/// @todo: Need to get back to this issue. This is likely a memory leak.
//
// Bug: it seems that if there is no call to
// cass_user_type_set_*(cass_user_type_), then
// cass_user_type_free(cass_user_type_) might SIGSEGV, so we never
......@@ -250,7 +258,14 @@ CqlBindUdt(const boost::any& value,
CassStatement* statement) {
Udt* udt = boost::any_cast<Udt*>(value);
if (!udt) {
isc_throw(BadValue, "Invalid value specified, not an Udt object");
}
size_t i = 0u;
// Let's iterate over all elements in udt and check that we indeed
// can assign the set function for each specified type.
for (boost::any& element : *udt) {
try {
KEA_CASS_CHECK(
......@@ -275,11 +290,13 @@ static CassError
CqlBindCollection(const boost::any& value,
const size_t& index,
CassStatement* statement) {
Collection* elements = boost::any_cast<Collection*>(value);
AnyCollection* elements = boost::any_cast<AnyCollection*>(value);
CassCollection* collection =
cass_collection_new(CASS_COLLECTION_TYPE_SET, elements->size());
// Iterate over all elements and assign appropriate append function
// for each.
for (boost::any& element : *elements) {
ExchangeDataType type = exchangeType(element);
KEA_CASS_CHECK(CQL_FUNCTIONS[type].cqlCollectionAppendFunction_(
......@@ -555,11 +572,15 @@ CqlGetUdt(const boost::any& data, const CassValue* value) {
static CassError
CqlGetCollection(const boost::any& data, const CassValue* value) {
Collection* collection = boost::any_cast<Collection*>(data);
AnyCollection* collection = boost::any_cast<AnyCollection*>(data);
if (!collection) {
isc_throw(DbOperationError, "CqlGetCollection(): column is not a collection");
}
BOOST_ASSERT(collection->size() == 1);
// @todo: Create a copy of the underlying object rather than referencing to
// it.
/// @todo: Create a copy of the underlying object rather than referencing to
/// it.
boost::any underlying_object = *collection->begin();
collection->clear();
......@@ -580,7 +601,7 @@ CqlGetCollection(const boost::any& data, const CassValue* value) {
collection->push_back(underlying_object);
KEA_CASS_CHECK(CQL_FUNCTIONS[exchangeType(type)].cqlGetFunction_(
*collection->rbegin(), item_value));
// If cqlGetFunction_() returns != CASS_OK, don't
// If cqlGetFunction_() returns != CASS_OK, don't call
// cass_iterator_free(items_iterator) because we're returning from this
// function and throwing from the callee.
}
......@@ -738,29 +759,33 @@ void
CqlExchange::convertFromDatabaseTime(const cass_int64_t& expire,
const cass_int64_t& valid_lifetime,
time_t& cltt) {
/// @todo: Although 2037 is still far away, there are people who use infinite
/// lifetimes. Cassandra doesn't have to support it right now, but at least
/// we should be able to detect a problem.
// Convert to local time
cltt = static_cast<time_t>(expire - valid_lifetime);
}
AnyArray
CqlExchange::executeSelect(const CqlConnection& connection,
const AnyArray& data,
StatementTag statement_tag,
const bool& single /* = false */) {
CqlExchange::executeSelect(const CqlConnection& connection, const AnyArray& data,
StatementTag statement_tag, const bool& single /* = false */) {
CassError rc;
CassStatement* statement = NULL;
CassFuture* future = NULL;
AnyArray local_data = data;
StatementMap::const_iterator it =
connection.statements_.find(statement_tag);
// Find the query statement first.
StatementMap::const_iterator it = connection.statements_.find(statement_tag);
if (it == connection.statements_.end()) {
isc_throw(DbOperationError,
"CqlExchange::executeSelect(): Statement "
<< statement_tag << "has not been prepared.");
}
// Bind the data before the query is executed.
CqlTaggedStatement tagged_statement = it->second;
if (tagged_statement.is_raw_statement_) {
if (tagged_statement.is_raw_) {
// The entire query is the first element in data.
std::string* query = boost::any_cast<std::string*>(local_data.back());
local_data.pop_back();
......@@ -774,6 +799,7 @@ CqlExchange::executeSelect(const CqlConnection& connection,
}
}
// Set specific level of consistency if we're told to do so.
if (connection.force_consistency_) {
rc = cass_statement_set_consistency(statement, connection.consistency_);
if (rc != CASS_OK) {
......@@ -788,6 +814,7 @@ CqlExchange::executeSelect(const CqlConnection& connection,
CqlCommon::bindData(local_data, statement);
// Everything's ready. Call the actual statement.
future = cass_session_execute(connection.session_, statement);
if (!future) {
cass_statement_free(statement);
......@@ -795,6 +822,8 @@ CqlExchange::executeSelect(const CqlConnection& connection,
"CqlExchange::executeSelect(): no CassFuture for statement "
<< tagged_statement.name_);
}
// Wait for the statement execution to complete.
cass_future_wait(future);
const std::string error = connection.checkFutureError(
"CqlExchange::executeSelect(): cass_session_execute() != CASS_OK",
......@@ -840,21 +869,20 @@ CqlExchange::executeSelect(const CqlConnection& connection,
}
void
CqlExchange::executeMutation(
const CqlConnection& connection,
const AnyArray& data,
StatementTag statement_tag) {
CqlExchange::executeMutation(const CqlConnection& connection, const AnyArray& data,
StatementTag statement_tag) {
CassError rc;
CassStatement* statement = NULL;
CassFuture* future = NULL;
// Find the statement on a list of prepared statements.
StatementMap::const_iterator it =
connection.statements_.find(statement_tag);
if (it == connection.statements_.end()) {
isc_throw(DbOperationError,
"CqlExchange::executeSelect(): Statement "
<< statement_tag << "has not been prepared.");
isc_throw(DbOperationError, "CqlExchange::executeSelect(): Statement "
<< statement_tag << "has not been prepared.");
}
// Bind the statement.
CqlTaggedStatement tagged_statement = it->second;
statement = cass_prepared_bind(tagged_statement.prepared_statement_);
if (!statement) {
......@@ -863,15 +891,14 @@ CqlExchange::executeMutation(
<< tagged_statement.name_);
}
// Set specific level of consistency, if told to do so.
if (connection.force_consistency_) {
rc = cass_statement_set_consistency(statement, connection.consistency_);
if (rc != CASS_OK) {
cass_statement_free(statement);
isc_throw(DbOperationError,
"CqlExchange::executeMutation(): unable to set statement "
"consistency for statement "
<< tagged_statement.name_
<< ", Cassandra error code: " << cass_error_desc(rc));
isc_throw(DbOperationError, "CqlExchange::executeMutation(): unable to set"
" statement consistency for statement " << tagged_statement.name_
<< ", Cassandra error code: " << cass_error_desc(rc));
}
}
......@@ -885,9 +912,8 @@ CqlExchange::executeMutation(
<< tagged_statement.name_);
}
cass_future_wait(future);
const std::string error = connection.checkFutureError(
"CqlExchange::executeMutation(): cass_session_execute() != CASS_OK",
future, statement_tag);
const std::string error = connection.checkFutureError("CqlExchange::executeMutation():"
"cass_session_execute() != CASS_OK", future, statement_tag);
rc = cass_future_error_code(future);
if (rc != CASS_OK) {
cass_future_free(future);
......@@ -896,7 +922,7 @@ CqlExchange::executeMutation(
}
// Check if statement has been applied.
bool applied = hasStatementBeenApplied(future);
bool applied = statementApplied(future);
// Free resources.
cass_future_free(future);
......@@ -911,10 +937,14 @@ CqlExchange::executeMutation(
}
bool
CqlExchange::hasStatementBeenApplied(CassFuture* future,
CqlExchange::statementApplied(CassFuture* future,
size_t* row_count,
size_t* column_count) {
const CassResult* result_collection = cass_future_get_result(future);
if (!result_collection) {
isc_throw(DbOperationError, "CqlExchange::statementApplied(): unable to get"
" results collection");
}
if (row_count) {
*row_count = cass_result_row_count(result_collection);
}
......@@ -938,11 +968,7 @@ CqlExchange::hasStatementBeenApplied(CassFuture* future,
constexpr StatementTag CqlVersionExchange::GET_VERSION;
StatementMap CqlVersionExchange::tagged_statements_ = {
{GET_VERSION, //
{GET_VERSION, //
"SELECT "
"version, minor "
"FROM schema_version "}} //
{GET_VERSION, {GET_VERSION, "SELECT version, minor FROM schema_version "}}
};
CqlVersionExchange::CqlVersionExchange() {
......@@ -952,14 +978,10 @@ CqlVersionExchange::~CqlVersionExchange() {
}
void
CqlVersionExchange::createBindForSelect(
AnyArray& data, StatementTag /* statement_tag = NULL */) {
// Start with a fresh array.
data.clear();
// id: blob
data.add(&version_);
// host_identifier: blob
data.add(&minor_);
CqlVersionExchange::createBindForSelect(AnyArray& data, StatementTag) {
data.clear(); // Start with a fresh array.
data.add(&version_); // first column is a major version
data.add(&minor_); // second column is a minor version
}
boost::any
......
......@@ -59,7 +59,7 @@ public:
// @brief Representation of a Cassandra User Defined Type
class Udt : public AnyArray {
public:
/// @brief Paramterized constructor
/// @brief Parameterized constructor
Udt(const CqlConnection& connection, const std::string& name);
/// @brief Destructor
......@@ -85,25 +85,37 @@ public:
CassUserType* cass_user_type_;
};
typedef AnyArray Collection;
/// @brief Defines an array of arbitrary objects (used by Cassandra backend)
typedef AnyArray AnyCollection;
/// @brief Binds a C++ object to a Cassandra statement's parameter. Used in all
/// statements.
/// @param value the value to be set or retreived
/// @param index offset of the value being processed
/// @param statement pointer to the parent statement being used
typedef CassError (*CqlBindFunction)(const boost::any& value,
const size_t& index,
CassStatement* statement);
/// @brief Sets a member in a UDT. Used in INSERT & UPDATE statements.
typedef CassError (*CqlUdtSetFunction)(const boost::any& udt_member,
const size_t& position,
/// @param value the value to be set or retreived
/// @param index offset of the value being processed
/// @param cass_user_type pointer to the user type that uses this member
typedef CassError (*CqlUdtSetFunction)(const boost::any& value,
const size_t& index,
CassUserType* cass_user_type);
/// @brief Sets an item in a collection. Used in INSERT & UPDATE statements.
/// @param value pointer to a value to be inserted or updated
/// @param collection pointer to collection to be inserted or updated
typedef CassError (*CqlCollectionAppendFunction)(const boost::any& value,
CassCollection* collection);
/// @brief Converts a single Cassandra column value to a C++ object. Used in
/// SELECT statements.
///
/// @param data the result will be stored here (this pointer will be updated)
/// @param value this value will be converted
typedef CassError (*CqlGetFunction)(const boost::any& data,
const CassValue* value);
......@@ -138,9 +150,19 @@ public:
/// @name Time conversion:
/// @{
/// @brief Converts time to Cassandra format
///
/// @param cltt timestamp of last client transmission time to be converted
/// @param valid_lifetime lifetime of a lease
/// @param expire expiration time (result will be stored here)
static void convertToDatabaseTime(const time_t& cltt,
const uint32_t& valid_lifetime,
cass_int64_t& expire);
/// @brief Converts time from Cassandra format
/// @param expire expiration time in Cassandra format
/// @param valid_lifetime lifetime of a lease
/// @param cltt client last transmission time (result will be stored here)
static void convertFromDatabaseTime(const cass_int64_t& expire,
const cass_int64_t& valid_lifetime,
time_t& cltt);
......@@ -196,9 +218,8 @@ public:
/// same priumary key.
///
/// @return true if statement has been succesfully applied, false otherwise
bool hasStatementBeenApplied(CassFuture* future,
size_t* row_count = NULL,
size_t* column_count = NULL);
bool statementApplied(CassFuture* future, size_t* row_count = NULL,
size_t* column_count = NULL);
/// @brief Copy received data into the derived class' object.
///
......@@ -269,9 +290,9 @@ private:
/// @brief Common operations in Cassandra exchanges
class CqlCommon {
public:
/// @brief Give values to every column of an INSERT or an UPDATE statement.
/// @brief Assigns values to every column of an INSERT or an UPDATE statement.
///
/// Calls cqlBindFunction_() for every column with it's respective type.
/// Calls cqlBindFunction_() for every column with its respective type.
///
/// @param data array containing column values to be passed to the statement
/// being executed
......@@ -281,7 +302,7 @@ public:
/// @brief Retrieves data returned by Cassandra.
///
/// Calls cqlGetFunction_() for every column with it's respective type.
/// Calls cqlGetFunction_() for every column with its respective type.
///
/// @param row internal Cassandra object containing data returned by
/// Cassandra
......
This diff is collapsed.
......@@ -39,7 +39,7 @@ class CqlLease6Exchange;
/// @brief Cassandra Lease Manager
///
/// This class provides the \ref isc::dhcp::LeaseMgr interface to the Cassandra
/// This class provides the @ref isc::dhcp::LeaseMgr interface to the Cassandra
/// database. Use of this backend implies that a CQL database is available
/// and that the Kea schema has been created within it.
class CqlLeaseMgr : public LeaseMgr {
......@@ -47,15 +47,18 @@ public:
/// @brief Constructor
///
/// Uses the following keywords in the parameters passed to it to
/// connect to the database:
/// - name - Name of the database to which to connect (mandatory)
/// - host - Host to which to connect (optional, defaults to "localhost")
/// - user - Username under which to connect (optional)
/// - password - Password for "user" on the database (optional)
///
/// If the database is successfully opened, the version number in the
/// schema_version table will be checked against hard-coded value in
/// the implementation file.
/// connect to the Cassandra cluster (if omitted, defaults specified in
/// parentheses):
/// - name - Name of the keyspace to to connect to ("keatest")
/// - contact-points - IP addresses to connect ("127.0.0.1")
/// - user - Username under which to connect (empty)
/// - password - Password for "user" on the database (empty)
/// - port - TCP port (9042)
/// - reconnect-wait-time (2000)
/// - connect-timeout (5000)
/// - request-timeout (12000)
/// - tcp-keepalive (no)
/// - tcp-nodelay (no)
///
/// Finally, all the CQL commands are pre-compiled.
///
......@@ -138,7 +141,7 @@ public:
/// and a subnet
///
/// There can be at most one lease for a given HW address in a single
/// pool, so this method with either return a single lease or NULL.
/// subnet, so this method with either return a single lease or NULL.
///
/// @param hwaddr hardware address of the client
/// @param subnet_id identifier of the subnet that lease must belong to
......
Markdown is supported
0%