]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
Cassandra Host Data Source stress test
authorAndrei Pavel <andrei.pavel@qualitance.com>
Wed, 14 Dec 2016 12:47:57 +0000 (14:47 +0200)
committerAndrei Pavel <andrei.pavel@qualitance.com>
Wed, 11 Jan 2017 12:28:56 +0000 (14:28 +0200)
It is part of the generic_host_data_source_unittest.cc, able to be
implemented for other HDS as well. It executes INSERT statements and
a certain SELECT statement which Cassandra struggles in performance
with since it has to do two queries instead of a join. All statement
executions are timed and repeated on an increasingly number of hosts.
It is placed last and DISABLED_ as you don't want to run this during
a usual make check.

41 files changed:
AUTHORS
configure.ac
doc/guide/admin.xml
doc/guide/dhcp4-srv.xml
doc/guide/dhcp6-srv.xml
src/bin/admin/admin-utils.sh
src/bin/admin/kea-admin.in
src/bin/admin/tests/Makefile.am
src/bin/admin/tests/cql_tests.sh.in
src/bin/admin/tests/dhcpdb_create_1.0.cql
src/lib/dhcpsrv/Makefile.am
src/lib/dhcpsrv/cfg_option.cc
src/lib/dhcpsrv/cql_connection.cc
src/lib/dhcpsrv/cql_connection.h
src/lib/dhcpsrv/cql_exchange.cc [new file with mode: 0644]
src/lib/dhcpsrv/cql_exchange.h [new file with mode: 0644]
src/lib/dhcpsrv/cql_host_data_source.cc [new file with mode: 0644]
src/lib/dhcpsrv/cql_host_data_source.h [new file with mode: 0644]
src/lib/dhcpsrv/cql_lease_mgr.cc
src/lib/dhcpsrv/cql_lease_mgr.h
src/lib/dhcpsrv/dhcpsrv_messages.mes
src/lib/dhcpsrv/host.h
src/lib/dhcpsrv/host_data_source_factory.cc
src/lib/dhcpsrv/lease_mgr.h
src/lib/dhcpsrv/mysql_host_data_source.cc
src/lib/dhcpsrv/parsers/dbaccess_parser.cc
src/lib/dhcpsrv/sql_common.h [new file with mode: 0644]
src/lib/dhcpsrv/tests/Makefile.am
src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
src/lib/dhcpsrv/testutils/cql_schema.cc
src/lib/dhcpsrv/testutils/cql_schema.h
src/share/database/scripts/cql/.gitignore
src/share/database/scripts/cql/Makefile.am
src/share/database/scripts/cql/dhcpdb_create.cql
src/share/database/scripts/cql/dhcpdb_drop.cql
src/share/database/scripts/cql/soft_wipe.cql
src/share/database/scripts/cql/upgrade_1.0_to_2.0.sh.in [new file with mode: 0644]
src/share/database/scripts/mysql/Makefile.am

diff --git a/AUTHORS b/AUTHORS
index 1aaae275cee60594897b43f0948862ed25bb7843..e2d0bee6ac8a79f0e82bdfe53bf1cfe1c7ba7269 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -123,6 +123,7 @@ We have received the following contributions:
 
  - Andrei Pavel, Qualitance
    2016-10: Support for DHCPv6 options defined in RFC6603 and RFC7598
+   2016-12: Cassandra Host Data Source
 
  - Vincent Legout
    2016-11: Fixed serveral spelling mistakes
@@ -131,7 +132,8 @@ Kea uses log4cplus (http://sourceforge.net/projects/log4cplus/) for logging,
 Boost (http://www.boost.org/) library for almost everything, and can use Botan
 (http://botan.randombit.net/) or OpenSSL (https://www.openssl.org/) for
 cryptographic operations. It can also optionally use PostgreSQL
-(http://www.postgresql.org/) and/or MySQL (http://www.mysql.com/) as a database.
+(http://www.postgresql.org/) and/or MySQL (http://www.mysql.com/) and/or
+Cassandra (http://cassandra.apache.org/) as a database.
 
 Kea can use googletest for unit-tests (http://code.google.com/p/googletest/).
 
index 47d6c16359ea985e2d173d74260a2424cb86d3d3..0dbbe0e05aa19ee7a378b16c4f2963373b1442b4 100644 (file)
@@ -1666,6 +1666,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/share/database/scripts/pgsql/upgrade_1.0_to_2.0.sh
                  src/share/database/scripts/pgsql/upgrade_2.0_to_3.0.sh
                  src/share/database/scripts/cql/Makefile
+                 src/share/database/scripts/cql/upgrade_1.0_to_2.0.sh
                  tools/Makefile
                  tools/path_replacer.sh
 ])
index a8d8324851b429caf056e276188fc5f447622ee4..f9ee56e2e7dcd7ea9ae441efbafe09f897148420 100644 (file)
@@ -72,7 +72,7 @@
         <listitem>
           <simpara>
             <command>lease-init</command> &mdash;
-            Initializes a new lease database. This is useful during a new 
+            Initializes a new lease database. This is useful during a new
             Kea installation. The database is initialized to the
             latest version supported by the version of the software being
             installed.
     <para>
       <table frame="all" id="backends">
         <title>List of available backends</title>
-        <tgroup cols='2'>
+        <tgroup cols='5'>
           <colspec colname='feature'/>
           <colspec colname='memfile'/>
           <colspec colname='mysql'/>
           <colspec colname='pgsql'/>
-          <colspec colname='cql'/>
+          <colspec colname='cql' colwidth='1.5*'/>
           <thead>
             <row>
               <entry>Feature</entry>
               <entry>Memfile</entry>
               <entry>MySQL</entry>
               <entry>PostgreSQL</entry>
-              <entry>CQL(Cassandra)</entry>
+              <entry>CQL (Cassandra)</entry>
             </row>
           </thead>
           <tbody>
               <entry>no</entry>
               <entry>yes</entry>
               <entry>yes</entry>
-              <entry>no</entry>
+              <entry>yes</entry>
             </row>
 
             <row>
               <entry>no</entry>
               <entry>yes</entry>
               <entry>yes</entry>
-              <entry>no</entry>
+              <entry>yes</entry>
             </row>
 
           </tbody>
@@ -409,6 +409,8 @@ $ <userinput>kea-admin lease-upgrade mysql -u <replaceable>database-user</replac
       <title>PostgreSQL</title>
 
       <para>
+        PostgreSQL is able to store leases, host reservations and options
+        defined on a per host basis.
         A PostgreSQL database must be set up if you want Kea to store
         lease and other information in PostgreSQL. This step can be
         safely ignored if you are using other database backends.
@@ -590,10 +592,9 @@ $ <userinput>kea-admin lease-upgrade pgsql -u <replaceable>database-user</replac
       <para>
         Cassandra, or Cassandra Query Language (CQL), is the newest backend
         added to Kea. Since it was added recently and has not undergone as much
-        testing as other backends, it is considered experimental: please use
-        with caution. The CQL backend is currently able to store leases only. The
-        ability to store host reservations will likely be added some time in the
-        future.
+        testing as other backends, it is considered experimental. Please use
+        with caution. The Casandra backend is currently able to store leases,
+        host reservations and options defined on a per host basis.
       </para>
 
       <para>
index c2798e7a023faa96117332307d5314f2e3237111..c7da52d69f8c5fda9f09101a349efa921c56d091 100644 (file)
@@ -477,8 +477,7 @@ If a timeout is given though, it should be an integer greater than zero.
     purpose, be it lease or hosts information. This arrangement gives the most
     flexibility. Kea can be used to keep leases and host reservations
     separately, but can also point to the same database. Currently the
-    supported hosts database types are MySQL and PostgreSQL. The Cassandra
-    backend does not support host reservations yet.</para>
+    supported hosts database types are MySQL, PostgreSQL and CQL (Cassandra).</para>
 
     <para>Please note that usage of hosts storage is optional. A user can define
     all host reservations in the configuration file. That is the recommended way
@@ -2973,13 +2972,13 @@ It is merely echoed by the server
     with classification using expressions.</para>
     </section>
 
-    <section id="reservations4-mysql-pgsql">
-      <title>Storing Host Reservations in MySQL or PostgreSQL</title>
+    <section id="reservations4-mysql-pgsql-cql">
+      <title>Storing Host Reservations in MySQL, PostgreSQL or CQL (Cassandra)</title>
 
       <para>
-        It is possible to store host reservations in MySQL or PostgreSQL database. See
+        It is possible to store host reservations in MySQL, PostgreSQL or Cassandra. See
         <xref linkend="hosts4-storage"/> for information on how to configure Kea to use
-        reservations stored in MySQL or PostgreSQL. Kea does not provide any dedicated
+        reservations stored in MySQL, PostgreSQL or Cassandra. Kea does not provide any dedicated
         tools for managing reservations in a database. The Kea wiki <ulink
         url="http://kea.isc.org/wiki/HostReservationsHowTo" /> provides detailed
         information and examples of how reservations can be inserted into the
@@ -2990,13 +2989,6 @@ It is merely echoed by the server
       arbitrarily set to 4096 bytes.</simpara></note>
     </section>
 
-    <section id="reservations4-cql">
-      <title>Storing host reservations in CQL (Cassandra)</title>
-      <para>Kea currently does not support storing reservations in
-      Cassandra (CQL).</para>
-    </section>
-
-
     <section id="reservations4-tuning">
       <title>Fine Tuning DHCPv4 Host Reservation</title>
 
index 9c7ca469ce5a273b1123bfae8fe13fc339e08c2a..2a2490da956f8b35f5390a864ef221a9753d3c53 100644 (file)
@@ -1156,7 +1156,7 @@ temporarily override a list of interface names and listen on all interfaces.
 <row><entry>s46-br</entry><entry>90</entry><entry>ipv6-address</entry><entry>false</entry></row>
 <row><entry>s46-dmr</entry><entry>91</entry><entry>ipv6-prefix</entry><entry>false</entry></row>
 <row><entry>s46-v4v6bind</entry><entry>92</entry><entry>record (ipv4-address, ipv6-prefix)</entry><entry>false</entry></row>
-<row><entry>s46-portparams</entry><entry>93</entry><entry>record(uint8, psid)</entry><entry>false</entry></row>
+<row><entry>s46-portparams</entry><entry>93</entry><entry>record (uint8, psid)</entry><entry>false</entry></row>
 <row><entry>s46-cont-mape</entry><entry>94</entry><entry>empty</entry><entry>false</entry></row>
 <row><entry>s46-cont-mapt</entry><entry>95</entry><entry>empty</entry><entry>false</entry></row>
 <row><entry>s46-cont-lw</entry><entry>96</entry><entry>empty</entry><entry>false</entry></row>
@@ -2908,13 +2908,13 @@ should include options from the isc option space:
     with classification using expressions.</para>
     </section>
 
-    <section id="reservations6-mysql-pgsql">
-      <title>Storing Host Reservations in MySQL or PostgreSQL</title>
+    <section id="reservations6-mysql-pgsql-cql">
+      <title>Storing Host Reservations in MySQL, PostgreSQL or CQL (Cassandra)</title>
 
       <para>
-        It is possible to store host reservations in MySQL or PostgreSQL. See <xref
-        linkend="hosts6-storage" /> for information on how to configure Kea to use
-        reservations stored in MySQL or PostgreSQL. Kea does not provide any dedicated
+        It is possible to store host reservations in MySQL, PostgreSQL or Cassandra. See
+        <xref linkend="hosts6-storage" /> for information on how to configure Kea to use
+        reservations stored in MySQL, PostgreSQL or Cassandra. Kea does not provide any dedicated
         tools for managing reservations in a database. The Kea wiki <ulink
         url="http://kea.isc.org/wiki/HostReservationsHowTo" /> provides detailed
         information and examples of how reservations can be inserted into the
@@ -2925,12 +2925,6 @@ should include options from the isc option space:
       arbitrarily set to 4096 bytes.</simpara></note>
     </section>
 
-    <section id="reservations6-cql">
-      <title>Storing Host Reservations in CQL (Cassandra)</title>
-      <para>Kea currently does not support storing reservations in
-      Cassandra (CQL).</para>
-    </section>
-
     <section id="reservations6-tuning">
       <title>Fine Tuning DHCPv6 Host Reservation</title>
 
index 3df7d4fc4efde98f5f18cfdd82992253c403e9bc..aa2736a057ba834efa19462e8606aec355ea038e 100755 (executable)
@@ -18,7 +18,7 @@
 mysql_execute() {
     QUERY=$1
     shift
-    if [ $# -gt 1 ]; then
+    if [ $# -ge 1 ]; then
         mysql -N -B  $* -e "${QUERY}"
         retcode=$?
     else
@@ -47,7 +47,7 @@ mysql_version() {
 pgsql_execute() {
     QUERY=$1
     shift
-    if [ $# -gt 0 ]; then
+    if [ $# -ge 1 ]; then
         echo $QUERY | psql --set ON_ERROR_STOP=1 -A -t -h localhost -q $*
         retcode=$?
     else
@@ -71,7 +71,7 @@ pgsql_execute() {
 pgsql_execute_script() {
     file=$1
     shift
-    if [ $# -gt 0 ]; then
+    if [ $# -ge 1 ]; then
         psql --set ON_ERROR_STOP=1 -A -t -h localhost -q -f $file $*
         retcode=$?
     else
@@ -90,8 +90,8 @@ pgsql_version() {
 cql_execute() {
     query=$1
     shift
-    if [ $# -gt 1 ]; then
-        cqlsh $* -e "$query"
+    if [ $# -ge 1 ]; then
+        cqlsh "$@" -e "$query"
         retcode=$?
     else
         cqlsh -u $db_user -p $db_password -k $db_name -e "$query"
@@ -109,8 +109,8 @@ cql_execute() {
 cql_execute_script() {
     file=$1
     shift
-    if [ $# -gt 1 ]; then
-        cqlsh $* -e "$file"
+    if [ $# -ge 1 ]; then
+        cqlsh "$@" -f "$file"
         retcode=$?
     else
         cqlsh -u $db_user -p $db_password -k $db_name -f "$file"
@@ -126,8 +126,9 @@ cql_execute_script() {
 }
 
 cql_version() {
-    version=`cql_execute "SELECT version, minor FROM schema_version" "$@"`
-    version=`echo "$version" | grep -A 1 "+" | grep -v "+" | tr -d ' ' | cut -d "|" -f 1-2 --output-delimiter="."`
-    echo $version
-    return $?
+  version=$(cql_execute "SELECT version, minor FROM schema_version" "$@")
+  error=$?
+  version=$(echo "$version" | grep -A 1 "+" | grep -v "+" | tr -d ' ' | cut -d "|" -f 1-2 --output-delimiter=".")
+  echo "$version"
+  return $error
 }
index 32a27a315491484bfbe618a0e6bd5b491ef2a89e..01310fd67de63a5c9ae6a411963870dafa6e14f8 100644 (file)
@@ -203,8 +203,8 @@ pgsql_init() {
 cql_init() {
     printf "Checking if there is a database initialized already... Please ignore errors.\n"
 
-    result=`cql_execute "USE $db_name; DESCRIBE tables;"`
-    if [ "$result"="<empty>" ]; then
+    result=$(cql_execute "DESCRIBE tables;")
+    if [ $(echo "$result" | grep "<empty>" | wc -w) -gt 0 ]; then
         printf "Creating and initializing tables using script %s...\n" $scripts_dir/cql/dhcpdb_create.cql
         cql_execute_script $scripts_dir/cql/dhcpdb_create.cql
     else
@@ -212,8 +212,8 @@ cql_init() {
         exit 2
     fi
 
-    version=`cql_version`
-    printf "Lease DB version reported after initialization: $version\n"
+    version=$(cql_version)
+    printf "Lease DB version reported after initialization: %s\n" "$version"
 
     exit 0
 }
index ea0f4370e69ca0afbaa8ea40bbf15e4c2466f063..8092f27bd9e9e4d2fa429179a007b2892c140e19 100644 (file)
@@ -13,6 +13,7 @@ endif
 if HAVE_CQL
 SHTESTS += cql_tests.sh
 endif
+
 noinst_SCRIPTS = $(SHTESTS)
 
 EXTRA_DIST = dhcpdb_create_1.0.mysql
index 80673d1774c0cfaffa5f7a4cf467c662fd3f899a..6d282b0683e981626a85b06c287eb25e6f1e9c89 100644 (file)
@@ -44,8 +44,7 @@ cql_lease_init_test() {
     assert_eq 0 $? "lease4 table check failed, expected exit code: %d, actual: %d"
 
     # Check lease6 table
-    cql_execute "SELECT address, duid, valid_lifetime, expire, subnet_id, pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname,\
-     state FROM lease6;"
+    cql_execute "SELECT address, duid, valid_lifetime, expire, subnet_id, pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, state FROM lease6;"
     assert_eq 0 $? "lease6 table check failed, expected exit code: %d, actual: %d"
 
     # Check lease6_types table
@@ -83,7 +82,7 @@ cql_lease_version_test() {
 
     # Verfiy that kea-admin lease-version returns the correct version.
     version=$($keaadmin lease-version cql -u $db_user -p $db_password -n $db_name)
-    assert_str_eq "1.0" $version "Expected kea-admin to return %s, returned value was %s"
+    assert_str_eq "2.0" $version "Expected kea-admin to return %s, returned value was %s"
 
     # Wipe the database.
     cql_execute_script $db_scripts_dir/cql/dhcpdb_drop.cql
@@ -151,20 +150,20 @@ cql_lease4_dump_test() {
     # 1430694930 corresponds to 2015-04-04 01:15:30
     # 1433464245 corresponds to 2015-05-05 02:30:45
     # 1436173267 corresponds to 2015-06-06 11:01:07
-    insert_sql="\
-insert into lease4(address, hwaddr, client_id, valid_lifetime, expire, subnet_id,\
+    insert_cql="\
+INSERT INTO lease4(address, hwaddr, client_id, valid_lifetime, expire, subnet_id,\
  fqdn_fwd, fqdn_rev, hostname, state)\
values(-1073741302,textAsBlob('20'),textAsBlob('30'),40,1430694930,50,true,true,\
VALUES(-1073741302,textAsBlob('20'),textAsBlob('30'),40,1430694930,50,true,true,\
  'one.example.com', 0);\
-insert into lease4(address, hwaddr, client_id, valid_lifetime, expire, subnet_id,\
+INSERT INTO lease4(address, hwaddr, client_id, valid_lifetime, expire, subnet_id,\
  fqdn_fwd, fqdn_rev, hostname, state)\
values(-1073741301,NULL,textAsBlob('123'),40,1433464245,50,true,true,'', 1);\
-insert into lease4(address, hwaddr, client_id, valid_lifetime, expire, subnet_id,\
VALUES(-1073741301,NULL,textAsBlob('123'),40,1433464245,50,true,true,'', 1);\
+INSERT INTO lease4(address, hwaddr, client_id, valid_lifetime, expire, subnet_id,\
  fqdn_fwd, fqdn_rev, hostname, state)\
values(-1073741300,textAsBlob('22'),NULL,40,1436173267,50,true,true,\
VALUES(-1073741300,textAsBlob('22'),NULL,40,1436173267,50,true,true,\
  'three.example.com', 2);"
 
-    cql_execute "$insert_sql"
+    cql_execute "$insert_cql"
     assert_eq 0 $? "insert into lease4 failed, expected exit code %d, actual %d"
 
     # Dump lease4 to output_file.
@@ -220,24 +219,24 @@ cql_lease6_dump_test() {
     # 1430694930 corresponds to 2015-04-04 01:15:30
     # 1433464245 corresponds to 2015-05-05 02:30:45
     # 1436173267 corresponds to 2015-06-06 11:01:07
-    insert_sql="\
-insert into lease6(address, duid, valid_lifetime, expire, subnet_id,\
+    insert_cql="\
+INSERT INTO lease6(address, duid, valid_lifetime, expire, subnet_id,\
  pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname,\
  hwaddr, hwtype, hwaddr_source, state)\
values('2001:db8::10',textAsBlob('20'),30,1430694930,40,50,1,60,70,true,true,\
VALUES('2001:db8::10',textAsBlob('20'),30,1430694930,40,50,1,60,70,true,true,\
  'one.example.com',textAsBlob('80'),90,16,0);\
-insert into lease6(address, duid, valid_lifetime, expire, subnet_id,\
+INSERT INTO lease6(address, duid, valid_lifetime, expire, subnet_id,\
  pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname,\
  hwaddr, hwtype, hwaddr_source, state)\
values('2001:db8::11',NULL,30,1433464245,40,50,1,60,70,true,true,\
VALUES('2001:db8::11',NULL,30,1433464245,40,50,1,60,70,true,true,\
  '',textAsBlob('80'),90,1,1);\
-insert into lease6(address, duid, valid_lifetime, expire, subnet_id,\
+INSERT INTO lease6(address, duid, valid_lifetime, expire, subnet_id,\
  pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname,\
  hwaddr, hwtype, hwaddr_source, state)\
values('2001:db8::12',textAsBlob('21'),30,1436173267,40,50,1,60,70,true,true,\
VALUES('2001:db8::12',textAsBlob('21'),30,1436173267,40,50,1,60,70,true,true,\
  'three.example.com',textAsBlob('80'),90,4,2);"
 
-    cql_execute "$insert_sql"
+    cql_execute "$insert_cql"
     assert_eq 0 $? "insert into lease6 failed, expected exit code %d, actual %d"
 
     # Dump lease4 to output_file.
index e015a260f353485f839e1098212cb34e13679fd3..85f9089effcf846f794ee0f63530da90e3fdbad8 100644 (file)
 -- is initialized to 1.0, then upgraded to 2.0 etc. This may be somewhat
 -- sub-optimal, but it ensues consistency with upgrade scripts. (It is much
 -- easier to maintain init and upgrade scripts if they look the same).
--- Since initialization is done only once, it's perfromance is not an issue.
+-- Since initialization is done only once, it's performance is not an issue.
 
 -- This line starts database initialization to 1.0.
 
 -- Holds the IPv4 leases.
+-- -----------------------------------------------------
+-- Table `lease4`
+-- -----------------------------------------------------
 CREATE TABLE lease4 (
     address int,
     hwaddr blob,
@@ -61,11 +64,15 @@ CREATE TABLE lease4 (
 CREATE INDEX lease4index1 ON lease4 (client_id);
 CREATE INDEX lease4index2 ON lease4 (subnet_id);
 CREATE INDEX lease4index3 ON lease4 (hwaddr);
-CREATE INDEX lease4index4 ON lease4 (state);
+CREATE INDEX lease4index4 ON lease4 (expire);
+CREATE INDEX lease4index5 ON lease4 (state);
 
 -- Holds the IPv6 leases.
 -- N.B. The use of a VARCHAR for the address is temporary for development:
 -- it will eventually be replaced by BINARY(16).
+-- -----------------------------------------------------
+-- Table `lease6`
+-- -----------------------------------------------------
 CREATE TABLE lease6 (
     address varchar,
     duid blob,
@@ -91,13 +98,17 @@ CREATE INDEX lease6index1 ON lease6 (lease_type);
 CREATE INDEX lease6index2 ON lease6 (duid);
 CREATE INDEX lease6index3 ON lease6 (iaid);
 CREATE INDEX lease6index4 ON lease6 (subnet_id);
-CREATE INDEX lease6index5 ON lease6 (state);
+CREATE INDEX lease6index5 ON lease6 (expire);
+CREATE INDEX lease6index6 ON lease6 (state);
 
 -- ... and a definition of lease6 types.  This table is a convenience for
 -- users of the database - if they want to view the lease table and use the
 -- type names, they can join this table with the lease6 table.
 -- Make sure those values match Lease6::LeaseType enum (see src/bin/dhcpsrv/
 -- lease_mgr.h)
+-- -----------------------------------------------------
+-- Table `lease6_types`
+-- -----------------------------------------------------
 CREATE TABLE lease6_types (
     lease_type int,                             -- Lease type code.
     name varchar,                               -- Name of the lease type
@@ -107,13 +118,15 @@ INSERT INTO lease6_types (lease_type, name) VALUES (0, 'IA_NA');   -- Non-tempor
 INSERT INTO lease6_types (lease_type, name) VALUES (1, 'IA_TA');   -- Temporary v6 addresses
 INSERT INTO lease6_types (lease_type, name) VALUES (2, 'IA_PD');   -- Prefix delegations
 
-
 -- Kea keeps track of the hardware/MAC address source, i.e. how the address
 -- was obtained. Depending on the technique and your network topology, it may
 -- be more or less trustworthy. This table is a convenience for
 -- users of the database - if they want to view the lease table and use the
 -- type names, they can join this table with the lease6 table. For details,
 -- see constants defined in src/lib/dhcp/dhcp/pkt.h for detailed explanation.
+-- -----------------------------------------------------
+-- Table `lease_hwaddr_source`
+-- -----------------------------------------------------
 CREATE TABLE lease_hwaddr_source (
     hwaddr_source int,
     name varchar,
@@ -141,47 +154,12 @@ INSERT INTO lease_hwaddr_source (hwaddr_source, name) VALUES (32, 'HWADDR_SOURCE
 -- Hardware address extracted from docsis options
 INSERT INTO lease_hwaddr_source (hwaddr_source, name) VALUES (64, 'HWADDR_SOURCE_DOCSIS_CMTS');
 
--- -----------------------------------------------------
--- Table `dhcp4_options`
--- -----------------------------------------------------
-CREATE TABLE dhcp4_options (
-    option_id int,
-    code int,
-    value blob,
-    formatted_value varchar,
-    space varchar,
-    persistent int,
-    dhcp_client_class varchar,
-    dhcp4_subnet_id int,
-    host_id int,
-    PRIMARY KEY (option_id)
-);
-
--- Create search indexes for dhcp4_options table
-CREATE INDEX dhcp4_optionsindex1 ON dhcp4_options (host_id);
-
--- -----------------------------------------------------
--- Table `dhcp6_options`
--- -----------------------------------------------------
-CREATE TABLE dhcp6_options (
-    option_id int,
-    code int,
-    value blob,
-    formatted_value varchar,
-    space varchar,
-    persistent int,
-    dhcp_client_class varchar,
-    dhcp6_subnet_id int,
-    host_id int,
-    PRIMARY KEY (option_id)
-);
-
--- Create search indexes for dhcp6_options table
-CREATE INDEX dhcp6_optionsindex1 ON dhcp6_options (host_id);
-
 -- Create table holding mapping of the lease states to their names.
 -- This is not used in queries from the DHCP server but rather in
 -- direct queries from the lease database management tools.
+-- -----------------------------------------------------
+-- Table `lease_state`
+-- -----------------------------------------------------
 CREATE TABLE lease_state (
     state int,
     name varchar,
@@ -199,9 +177,15 @@ INSERT INTO lease_state (state, name) VALUES (2, 'expired-reclaimed');
 -- first column is called "version" and not "major".
 -- Note: This MUST be kept in step with src/share/database/scripts/cassandra/dhcpdb_create.cql,
 -- which defines the schema for the unit tests.
+-- -----------------------------------------------------
+-- Table `schema_version`
+-- -----------------------------------------------------
 CREATE TABLE schema_version (
     version int,
     minor int,
     PRIMARY KEY (version)
 );
+
 INSERT INTO schema_version (version, minor) VALUES (1, 0);
+
+-- This line concludes database initalization to version 1.0.
index 6c8f48bb0f3e2b1eba56f72a4be41f65f578b1ac..c102851892377375ef4c7987099c61bc368a8342 100644 (file)
@@ -124,6 +124,7 @@ libkea_dhcpsrv_la_SOURCES += logging.cc logging.h
 libkea_dhcpsrv_la_SOURCES += logging_info.cc logging_info.h
 libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += memfile_lease_storage.h
+libkea_dhcpsrv_la_SOURCES += sql_common.h
 
 if HAVE_MYSQL
 libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
@@ -139,10 +140,14 @@ libkea_dhcpsrv_la_SOURCES += pgsql_exchange.cc pgsql_exchange.h
 libkea_dhcpsrv_la_SOURCES += pgsql_host_data_source.cc pgsql_host_data_source.h
 libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h
 endif
+
 if HAVE_CQL
-libkea_dhcpsrv_la_SOURCES += cql_lease_mgr.cc cql_lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += cql_connection.cc cql_connection.h
+libkea_dhcpsrv_la_SOURCES += cql_exchange.cc cql_exchange.h
+libkea_dhcpsrv_la_SOURCES += cql_host_data_source.cc cql_host_data_source.h
+libkea_dhcpsrv_la_SOURCES += cql_lease_mgr.cc cql_lease_mgr.h
 endif
+
 libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
 libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h
 libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h
@@ -236,5 +241,3 @@ libkea_dhcpsrv_include_HEADERS = \
 
 install-data-local:
        $(mkinstalldirs) $(DESTDIR)$(dhcp_data_dir)
-
-
index 8947dd5335d3e063130cddab64cd62ce7a43f15a..e8250cbcbde3ec3503d7ae46026eaf33f0792702 100644 (file)
@@ -164,9 +164,9 @@ CfgOption::mergeInternal(const OptionSpaceContainer<OptionContainer,
             // If there is no such option in the destination container,
             // add one.
             if (std::distance(range.first, range.second) == 0) {
-                dest_container.addItem(OptionDescriptor(src_opt->option_,
-                                                        src_opt->persistent_),
-                                       *it);
+                 dest_container.addItem(OptionDescriptor(
+                    src_opt->option_, src_opt->persistent_,
+                    src_opt->formatted_value_), *it);
             }
         }
     }
index 1dec234ebf53133a768c44aeee22446c47575f5f..b4e20c78567e4f1a537550ee40e81ee5353827e9 100644 (file)
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <config.h>
+
 #include <dhcpsrv/cql_connection.h>
-#include <string>
+#include <dhcpsrv/cql_exchange.h>
+
+#include <boost/shared_ptr.hpp>
 
 using namespace std;
 
 namespace isc {
 namespace dhcp {
 
-CqlConnection::CqlConnection(const ParameterMap& parameters) :
-        DatabaseConnection(parameters), cluster_(NULL), session_(NULL),
-        tagged_statements_(NULL) {
+CqlConnection::CqlConnection(const ParameterMap& parameters)
+    : DatabaseConnection(parameters), cluster_(NULL), session_(NULL),
+      force_consistency_(true), consistency_(CASS_CONSISTENCY_QUORUM),
+      tagged_statements_(NULL) {
 }
 
 CqlConnection::~CqlConnection() {
-    // Free up the prepared statements, ignoring errors.
-    // Session and connection resources are deallocated.
+    // Free up the prepared statements, ignoring errors. Session and connection
+    // resources are deallocated.
     CassError rc = CASS_OK;
     std::string error;
 
@@ -43,7 +48,8 @@ CqlConnection::~CqlConnection() {
     if (session_) {
         CassFuture* close_future = cass_session_close(session_);
         cass_future_wait(close_future);
-        checkStatementError(error, close_future, "could not close connection to DB");
+        checkStatementError(error, close_future, "could not close connection to"
+                                                 " DB");
         rc = cass_future_error_code(close_future);
         cass_future_free(close_future);
         cass_session_free(session_);
@@ -55,10 +61,9 @@ CqlConnection::~CqlConnection() {
         cluster_ = NULL;
     }
 
-    // We're closing the connection anyway. Let's not throw at this
-    // stage
     if (rc != CASS_OK) {
-        isc_throw(DbOpenError, error);
+        // We're closing the connection anyway. Let's not throw at this stage.
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_CQL_DEALLOC_ERROR).arg(error);
     }
 }
 
@@ -67,25 +72,25 @@ CqlConnection::openDatabase() {
     CassError rc;
     // Set up the values of the parameters
     const char* contact_points = "127.0.0.1";
-    string scontact_points;
+    std::string scontact_points;
     try {
         scontact_points = getParameter("contact_points");
         contact_points = scontact_points.c_str();
     } catch (...) {
-        // No host. Fine, we'll use "localhost".
+        // No host. Fine, we'll use "127.0.0.1".
     }
 
     const char* port = NULL;
-    string sport;
+    std::string sport;
     try {
         sport = getParameter("port");
         port = sport.c_str();
     } catch (...) {
-        // No port. Fine, we'll use "default".
+        // No port. Fine, we'll use the default "9042".
     }
 
     const char* user = NULL;
-    string suser;
+    std::string suser;
     try {
         suser = getParameter("user");
         user = suser.c_str();
@@ -94,7 +99,7 @@ CqlConnection::openDatabase() {
     }
 
     const char* password = NULL;
-    string spassword;
+    std::string spassword;
     try {
         spassword = getParameter("password");
         password = spassword.c_str();
@@ -103,36 +108,170 @@ CqlConnection::openDatabase() {
     }
 
     const char* keyspace = "keatest";
-    string skeyspace;
+    std::string skeyspace;
     try {
         skeyspace = getParameter("keyspace");
         keyspace = skeyspace.c_str();
     } catch (...) {
-        // No keyspace name. Fine, we'll use default "keatest".
+        // No keyspace name. Fine, we'll use "keatest".
+    }
+
+    const char* reconnect_wait_time = NULL;
+    std::string sreconnect_wait_time;
+    try {
+        sreconnect_wait_time = getParameter("reconnect-wait-time");
+        reconnect_wait_time = sreconnect_wait_time.c_str();
+    } catch (...) {
+        // No reconnect wait time. Fine, we'll use the default "2000".
+    }
+
+    const char* connect_timeout = NULL;
+    std::string sconnect_timeout;
+    try {
+        sconnect_timeout = getParameter("connect-timeout");
+        connect_timeout = sconnect_timeout.c_str();
+    } catch (...) {
+        // No connect timeout. Fine, we'll use the default "5000".
+    }
+
+    const char* request_timeout = NULL;
+    std::string srequest_timeout;
+    try {
+        srequest_timeout = getParameter("request-timeout");
+        request_timeout = srequest_timeout.c_str();
+    } catch (...) {
+        // No request timeout. Fine, we'll use the default "12000".
+    }
+
+    const char* tcp_keepalive = NULL;
+    std::string stcp_keepalive;
+    try {
+        stcp_keepalive = getParameter("tcp-keepalive");
+        tcp_keepalive = stcp_keepalive.c_str();
+    } catch (...) {
+        // No tcp-keepalive. Fine, we'll not use TCP keepalive.
+    }
+
+    std::string stcp_nodelay;
+    try {
+        stcp_nodelay = getParameter("tcp-nodelay");
+    } catch (...) {
+        // No tcp-nodelay. Fine, we'll use the default false.
     }
 
     cluster_ = cass_cluster_new();
     cass_cluster_set_contact_points(cluster_, contact_points);
 
-    if (user != NULL && password != NULL) {
+    if (user && password) {
         cass_cluster_set_credentials(cluster_, user, password);
     }
 
-    if (port != NULL) {
+    if (port) {
         int port_number;
         try {
             port_number = boost::lexical_cast<int>(port);
-        } catch (const std::exception& ex) {
-            isc_throw(DbOperationError, "Invalid int data: " << port
-                      << " : " << ex.what());
+            if (port_number < 1 || port_number > 65535) {
+                isc_throw(
+                    DbOperationError,
+                    "Port outside of range, expected 1-65535, instead got "
+                        << port);
+            }
+        } catch (const boost::bad_lexical_cast& ex) {
+            isc_throw(DbOperationError,
+                      "Invalid port, castable to int expected, instead got \""
+                          << port << "\", " << ex.what());
         }
         cass_cluster_set_port(cluster_, port_number);
     }
 
+    if (reconnect_wait_time) {
+        int reconnect_wait_time_number;
+        try {
+            reconnect_wait_time_number =
+                boost::lexical_cast<int>(reconnect_wait_time);
+            if (reconnect_wait_time_number < 0) {
+                isc_throw(DbOperationError,
+                          "Invalid reconnect wait time, positive number "
+                          "expected, instead got "
+                              << reconnect_wait_time);
+            }
+        } catch (const boost::bad_lexical_cast& ex) {
+            isc_throw(DbOperationError, "Invalid reconnect wait time, castable "
+                                        "to int expected, instead got \""
+                                            << reconnect_wait_time << "\", "
+                                            << ex.what());
+        }
+        cass_cluster_set_reconnect_wait_time(cluster_,
+                                             reconnect_wait_time_number);
+    }
+
+    if (connect_timeout) {
+        int connect_timeout_number;
+        try {
+            connect_timeout_number = boost::lexical_cast<int>(connect_timeout);
+            if (connect_timeout_number < 0) {
+                isc_throw(DbOperationError,
+                          "Invalid connect timeout, positive number expected, "
+                          "instead got "
+                              << connect_timeout);
+            }
+        } catch (const boost::bad_lexical_cast& ex) {
+            isc_throw(DbOperationError, "Invalid connect timeout, castable to "
+                                        "int expected, instead got \""
+                                            << connect_timeout << "\", "
+                                            << ex.what());
+        }
+        cass_cluster_set_connect_timeout(cluster_, connect_timeout_number);
+    }
+
+    if (request_timeout) {
+        int request_timeout_number;
+        try {
+            request_timeout_number = boost::lexical_cast<int>(request_timeout);
+            if (request_timeout_number < 0) {
+                isc_throw(DbOperationError,
+                          "Invalid request timeout, positive number expected, "
+                          "instead got "
+                              << request_timeout);
+            }
+        } catch (const boost::bad_lexical_cast& ex) {
+            isc_throw(DbOperationError, "Invalid request timeout, castable to "
+                                        "int expected, instead got \""
+                                            << request_timeout << "\", "
+                                            << ex.what());
+        }
+        cass_cluster_set_request_timeout(cluster_, request_timeout_number);
+    }
+
+    if (tcp_keepalive) {
+        int tcp_keepalive_number;
+        try {
+            tcp_keepalive_number = boost::lexical_cast<int>(tcp_keepalive);
+            if (tcp_keepalive_number < 0) {
+                isc_throw(DbOperationError,
+                          "Invalid TCP keepalive, positive number expected, "
+                          "instead got "
+                              << tcp_keepalive);
+            }
+        } catch (const boost::bad_lexical_cast& ex) {
+            isc_throw(
+                DbOperationError,
+                "Invalid TCP keepalive, castable to int expected, instead got "
+                "\"" << tcp_keepalive
+                     << "\", " << ex.what());
+        }
+        cass_cluster_set_tcp_keepalive(cluster_, cass_true,
+                                       tcp_keepalive_number);
+    }
+
+    if (stcp_nodelay == "true") {
+        cass_cluster_set_tcp_nodelay(cluster_, cass_true);
+    }
+
     session_ = cass_session_new();
 
-    CassFuture* connect_future = cass_session_connect_keyspace(session_,
-        cluster_, keyspace);
+    CassFuture* connect_future =
+        cass_session_connect_keyspace(session_, cluster_, keyspace);
     cass_future_wait(connect_future);
     std::string error;
     checkStatementError(error, connect_future, "could not connect to DB");
@@ -148,11 +287,12 @@ CqlConnection::openDatabase() {
 }
 
 void
-CqlConnection::prepareStatements(CqlTaggedStatement *statements) {
+CqlConnection::prepareStatements(CqlTaggedStatementstatements) {
     CassError rc = CASS_OK;
     uint32_t size = 0;
     tagged_statements_ = statements;
-    for (; tagged_statements_[size].params_; size++);
+    for (; tagged_statements_[size].params_; size++) {
+    }
     statements_.resize(size);
     for (uint32_t i = 0; i < size; i++) {
         const char* query = tagged_statements_[i].text_;
@@ -174,6 +314,19 @@ CqlConnection::prepareStatements(CqlTaggedStatement *statements) {
     }
 }
 
+void
+CqlConnection::setConsistency(bool force, CassConsistency consistency) {
+    force_consistency_ = force;
+    consistency_ = consistency;
+}
+
+void
+CqlConnection::startTransaction() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_BEGIN_TRANSACTION);
+    // No-op
+}
+
 void
 CqlConnection::commit() {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_COMMIT);
@@ -185,27 +338,31 @@ CqlConnection::rollback() {
 }
 
 void
-CqlConnection::checkStatementError(std::string& error, CassFuture* future,
-                                   uint32_t stindex, const char* what) const {
+CqlConnection::checkStatementError(std::string& error,
+                                   CassFuture* future,
+                                   uint32_t stindex,
+                                   const char* what) const {
     CassError rc;
     const char* errorMessage;
     size_t errorMessageSize;
     std::stringstream stream;
-    stream << "no error for: " << tagged_statements_[stindex].name_;
+    stream << "no error for statement " << tagged_statements_[stindex].name_;
 
     rc = cass_future_error_code(future);
     cass_future_error_message(future, &errorMessage, &errorMessageSize);
 
     if (rc != CASS_OK) {
         stream.str(std::string());
-        stream << what << " for: " << tagged_statements_[stindex].name_
-               << " reason: " << errorMessage << " error code: " << rc;
+        stream << what << " for statement " << tagged_statements_[stindex].name_
+               << ". Future error: " << errorMessage
+               << ". Error description: " << cass_error_desc(rc);
     }
     error = stream.str();
 }
 
 void
-CqlConnection::checkStatementError(std::string& error, CassFuture* future,
+CqlConnection::checkStatementError(std::string& error,
+                                   CassFuture* future,
                                    const char* what) const {
     CassError rc;
     const char* errorMessage;
@@ -218,10 +375,29 @@ CqlConnection::checkStatementError(std::string& error, CassFuture* future,
 
     if (rc != CASS_OK) {
         stream.str(std::string());
-        stream << what << " reason: " << errorMessage << " error code: " << rc;
+        stream << what << ". Future error: " << errorMessage
+               << ". Error description: " << cass_error_desc(rc);
     }
     error = stream.str();
 }
 
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+CqlTransaction::CqlTransaction(CqlConnection& conn)
+    : conn_(conn), committed_(false) {
+    conn_.startTransaction();
+}
+
+CqlTransaction::~CqlTransaction() {
+    // Rollback if commit() wasn't explicitly called.
+    if (!committed_) {
+        conn_.rollback();
+    }
+}
+
+void
+CqlTransaction::commit() {
+    conn_.commit();
+    committed_ = true;
+}
+
+}  // namespace dhcp
+}  // namespace isc
index 30bbeed1f0ff535f289a0a0fcb631e6ff9cd507b..aa6e5a411d730b5ebaa54f11e0ad26f8e6da1015 100644 (file)
 
 #include <dhcpsrv/database_connection.h>
 #include <dhcpsrv/dhcpsrv_log.h>
-#include <inttypes.h>
+
 #include <cassandra.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <inttypes.h>
+
+#include <map>
+#include <string>
 #include <vector>
 
 namespace isc {
 namespace dhcp {
 
-/// @brief  Defines a single query
+/// @brief Defines a single statement
 ///
-/// @param params_ Bind parameter names
-/// @param name_ Short name of the query.
-/// @param text_ Text representation of the actual query.
+/// @param params_ parameter names
+/// @param name_ short description of the query
+/// @param text_ text representation of the actual query
 struct CqlTaggedStatement {
     const char** params_;
     const char* name_;
     const char* text_;
 };
 
-// Defines CQL backend version: 2.3
+// Define CQL backend version: 2.3
 const uint32_t CQL_DRIVER_VERSION_MAJOR = CASS_VERSION_MAJOR;
 const uint32_t CQL_DRIVER_VERSION_MINOR = CASS_VERSION_MINOR;
 
-/// Defines CQL schema version: 1.0
-const uint32_t CQL_SCHEMA_VERSION_MAJOR = 1;
+/// Define CQL schema version: 2.0
+const uint32_t CQL_SCHEMA_VERSION_MAJOR = 2;
 const uint32_t CQL_SCHEMA_VERSION_MINOR = 0;
 
+/// @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
+/// use instances of CqlConnection.
 class CqlConnection : public DatabaseConnection {
 public:
-
     /// @brief Constructor
     ///
     /// Initialize CqlConnection object with parameters needed for connection.
-    CqlConnection(const ParameterMap& parameters);
+    explicit CqlConnection(const ParameterMap& parameters);
 
     /// @brief Destructor
     virtual ~CqlConnection();
@@ -61,21 +74,27 @@ public:
     /// Creates the prepared statements for all of the CQL statements used
     /// by the CQL backend.
     ///
-    /// @throw isc::dhcp::DbOperationError An operation on the open database has
-    ///        failed.
-    /// @throw isc::InvalidParameter 'index' is not valid for the vector. This
-    ///        represents an internal error within the code.
-    void prepareStatements(CqlTaggedStatement *statements);
+    /// @throw isc::dhcp::DbOperationError if an operation on the open database
+    ///     has failed
+    /// @throw isc::InvalidParameter if there is an invalid access in the
+    ///     vector. This represents an internal error within the code.
+    void prepareStatements(CqlTaggedStatementstatements);
 
-    /// @brief Open Database
+    /// @brief Open database
     ///
     /// 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 (keyspace keatest).
+    /// values will be used (e.g. keyspace 'keatest', port 9042).
     ///
-    /// @throw DbOpenError Error opening the database
+    /// @throw DbOpenError error opening the database
     void openDatabase();
 
+    /// @brief Set consistency
+    void setConsistency(bool force, CassConsistency consistency);
+
+    /// @brief Start transaction
+    void startTransaction();
+
     /// @brief Commit Transactions
     ///
     /// This is a no-op for Cassandra.
@@ -86,17 +105,20 @@ public:
     /// This is a no-op for Cassandra.
     virtual void rollback();
 
-    /// @brief Check Error
+    /// @brief Check for errors
     ///
-    /// Chech error for current database operation.
-    void checkStatementError(std::string& error, CassFuture* future,
-        uint32_t stindex, const char* what) const;
+    /// Check for errors on the current database operation.
+    void checkStatementError(std::string& error,
+                             CassFuture* future,
+                             uint32_t stindex,
+                             const char* what) const;
 
-    /// @brief Check Error
+    /// @brief Check for errors
     ///
-    /// Chech error for current database operation.
-    void checkStatementError(std::string& error, CassFuture* future,
-        const char* what) const;
+    /// Check for errors on the current database operation.
+    void checkStatementError(std::string& error,
+                             CassFuture* future,
+                             const char* what) const;
 
     /// @brief CQL connection handle
     CassCluster* cluster_;
@@ -104,16 +126,69 @@ public:
     /// @brief CQL session handle
     CassSession* session_;
 
-    /// @brief CQL prepared statements - used for faster statement execution using
-    /// bind functionality
+    /// @brief CQL consistency enabled
+    bool force_consistency_;
+
+    /// @brief CQL consistency
+    CassConsistency consistency_;
+
+    /// @brief CQL prepared statements - used for faster statement execution
+    ///     using bind functionality
     std::vector<const CassPrepared*> statements_;
 
-    /// @brief Pointer to external array of tagged statements containing statement
-    /// name, array of names of bind parameters and text query
+    /// @brief Pointer to external array of tagged statements containing
+    ///     statement name, array of names of bind parameters and text query
     CqlTaggedStatement* tagged_statements_;
 };
 
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+/// @brief RAII object representing CQL transaction.
+///
+/// An instance of this class should be created in a scope where multiple
+/// INSERT statements should be executed within a single transaction. The
+/// transaction is started when the constructor of this class is invoked.
+/// The transaction is ended when the @ref CqlTransaction::commit is
+/// explicitly called or when the instance of this class is destroyed.
+/// The @ref CqlTransaction::commit commits changes to the database
+/// and the changes remain in the database when the instance of the
+/// class is destroyed. If the class instance is destroyed before the
+/// @ref CqlTransaction::commit is called, the transaction is rolled
+/// back. The rollback on destruction guarantees that partial data is
+/// not stored in the database when there is an error during any
+/// of the operations belonging to a transaction.
+class CqlTransaction : public boost::noncopyable {
+public:
+    /// @brief Constructor
+    ///
+    /// Starts transaction by making a "START TRANSACTION" query.
+    ///
+    /// @param conn CQL connection to use for the transaction. This connection
+    ///     will be later used to commit or rollback changes.
+    ///
+    /// @throw DbOperationError if "START TRANSACTION" query fails.
+    explicit CqlTransaction(CqlConnection& conn);
+
+    /// @brief Destructor
+    ///
+    /// Rolls back the transaction if changes haven't been committed.
+    ~CqlTransaction();
+
+    /// @brief Commits transaction
+    ///
+    /// Calls @ref CqlConnection::commit()..
+    void commit();
+
+private:
+    /// @brief Holds reference to the CQL database connection.
+    CqlConnection& conn_;
+
+    /// @brief Boolean flag indicating if the transaction has been committed.
+    ///
+    /// This flag is used in the class destructor to assess if the
+    /// transaction should be rolled back.
+    bool committed_;
+};
+
+}  // namespace dhcp
+}  // namespace isc
 
-#endif // CQL_CONNECTION_H
+#endif  // CQL_CONNECTION_H
diff --git a/src/lib/dhcpsrv/cql_exchange.cc b/src/lib/dhcpsrv/cql_exchange.cc
new file mode 100644 (file)
index 0000000..c73f74b
--- /dev/null
@@ -0,0 +1,485 @@
+// Copyright (C) 2016 Deutsche Telekom AG.
+//
+// Author: 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 <config.h>
+
+#include <dhcpsrv/cql_exchange.h>
+
+#include <dhcpsrv/cql_connection.h>
+#include <dhcpsrv/db_exceptions.h>
+
+/// @name CqlBind auxiliary functions for binding data into Cassandra format:
+/// @{
+
+/// @todo These void* cast are unsafe. See ticket #4525.
+static CassError
+CqlBindNone(CassStatement* statement, size_t index, void*) {
+    return cass_statement_bind_null(statement, index);
+}
+
+static CassError
+CqlBindBool(CassStatement* statement, size_t index, void* value) {
+    return cass_statement_bind_bool(statement, index,
+                                    *(static_cast<cass_bool_t*>(value)));
+}
+
+static CassError
+CqlBindInt32(CassStatement* statement, size_t index, void* value) {
+    return cass_statement_bind_int32(statement, index,
+                                     *(static_cast<cass_int32_t*>(value)));
+}
+
+static CassError
+CqlBindInt64(CassStatement* statement, size_t index, void* value) {
+    return cass_statement_bind_int64(statement, index,
+                                     *(static_cast<cass_int64_t*>(value)));
+}
+
+static CassError
+CqlBindTimestamp(CassStatement* statement, size_t index, void* value) {
+    return cass_statement_bind_int64(statement, index,
+                                     *(static_cast<cass_int64_t*>(value)));
+}
+
+static CassError
+CqlBindString(CassStatement* statement, size_t index, void* value) {
+    return cass_statement_bind_string(
+        statement, index, static_cast<std::string*>(value)->c_str());
+}
+
+static CassError
+CqlBindBytes(CassStatement* statement, size_t index, void* value) {
+    return cass_statement_bind_bytes(
+        statement, index, static_cast<std::vector<cass_byte_t>*>(value)->data(),
+        static_cast<std::vector<cass_byte_t>*>(value)->size());
+}
+
+static CassError
+CqlBindUuid(CassStatement* statement, size_t index, void* value) {
+    return cass_statement_bind_uuid(statement, index,
+                                    *static_cast<CassUuid*>(value));
+}
+/// @}
+
+static CassError
+CqlGetNone(const CassValue*, void*) {
+    return CASS_OK;
+}
+
+static CassError
+CqlGetBool(const CassValue* value, void* data) {
+    return cass_value_get_bool(value, static_cast<cass_bool_t*>(data));
+}
+
+static CassError
+CqlGetInt32(const CassValue* value, void* data) {
+    return cass_value_get_int32(value, static_cast<cass_int32_t*>(data));
+}
+
+static CassError
+CqlGetInt64(const CassValue* value, void* data) {
+    return cass_value_get_int64(value, static_cast<cass_int64_t*>(data));
+}
+
+static CassError
+CqlGetTimestamp(const CassValue* value, void* data) {
+    return cass_value_get_int64(value, static_cast<cass_int64_t*>(data));
+}
+
+static CassError
+CqlGetString(const CassValue* value, void* data) {
+    const char* dataValue;
+    size_t sizeValue;
+    CassError cassError = cass_value_get_string(
+        value, static_cast<const char**>(&dataValue), &sizeValue);
+    static_cast<std::string*>(data)->assign(dataValue, dataValue + sizeValue);
+    return cassError;
+}
+
+static CassError
+CqlGetBytes(const CassValue* value, void* data) {
+    const cass_byte_t* dataValue;
+    size_t sizeValue;
+    CassError cassError = cass_value_get_bytes(
+        value, static_cast<const cass_byte_t**>(&dataValue), &sizeValue);
+    static_cast<std::vector<cass_byte_t>*>(data)->assign(dataValue,
+                                                         dataValue + sizeValue);
+    return cassError;
+}
+
+static CassError
+CqlGetUuid(const CassValue* value, void* data) {
+    return cass_value_get_uuid(value, static_cast<CassUuid*>(data));
+}
+
+namespace isc {
+namespace dhcp {
+
+struct CqlFunctionData CqlFunctions[] = {
+    {CqlBindNone, CqlGetNone},           {CqlBindBool, CqlGetBool},
+    {CqlBindInt32, CqlGetInt32},         {CqlBindInt64, CqlGetInt64},
+    {CqlBindTimestamp, CqlGetTimestamp}, {CqlBindString, CqlGetString},
+    {CqlBindBytes, CqlGetBytes},         {CqlBindUuid, CqlGetUuid}};
+
+ExchangeDataType
+CqlCommon::getDataType(const uint32_t stindex,
+                       int pindex,
+                       const SqlExchange& exchange,
+                       const CqlTaggedStatement* tagged_statements) {
+    if (tagged_statements[stindex].params_ &&
+        tagged_statements[stindex].params_[pindex]) {
+        const ExchangeColumnInfoContainerName& idx =
+            exchange.parameters_.get<1>();
+        const ExchangeColumnInfoContainerNameRange& range =
+            idx.equal_range(tagged_statements[stindex].params_[pindex]);
+        if (std::distance(range.first, range.second) > 0) {
+            return (*range.first)->type_;
+        }
+    }
+    return EXCHANGE_DATA_TYPE_NONE;
+}
+
+void
+CqlCommon::bindData(CassStatement* statement,
+                    uint32_t stindex,
+                    const CqlDataArray& data,
+                    const SqlExchange& exchange,
+                    const CqlTaggedStatement* tagged_statements) {
+    if (!tagged_statements[stindex].params_) {
+        return;
+    }
+    for (int i = 0; tagged_statements[stindex].params_[i]; i++) {
+        ExchangeDataType type =
+            CqlCommon::getDataType(stindex, i, exchange, tagged_statements);
+        if (type >= sizeof(CqlFunctions) / sizeof(CqlFunctions[0])) {
+            isc_throw(BadValue, "index " << stindex << " out of bounds");
+        }
+        CqlFunctions[type].cqlBindFunction_(statement, i, data.values_[i]);
+    }
+}
+
+void
+CqlCommon::getData(const CassRow* row,
+                   int pindex,
+                   int dindex,
+                   const SqlExchange& exchange,
+                   CqlDataArray& data) {
+    if (pindex >= exchange.parameters_.size()) {
+        return;
+    }
+    const ExchangeColumnInfoContainerIndex& idx = exchange.parameters_.get<2>();
+    const ExchangeColumnInfoContainerIndexRange& range =
+        idx.equal_range(pindex);
+    if (std::distance(range.first, range.second) > 0) {
+        std::string name = (*range.first)->name_;
+        ExchangeDataType type = (*range.first)->type_;
+        const CassValue* value = cass_row_get_column_by_name(row, name.c_str());
+        if (!value) {
+            isc_throw(BadValue, "column name " << name << " doesn't exist");
+        }
+        if (type >= sizeof(CqlFunctions) / sizeof(CqlFunctions[0])) {
+            isc_throw(BadValue, "index " << type << " out of bounds");
+        }
+        CassError cassError =
+            CqlFunctions[type].cqlGetFunction_(value, data.values_[dindex]);
+        if (cassError != CASS_OK) {
+            isc_throw(BadValue, "Cassandra error for column "
+                                    << name << " with message: "
+                                    << cass_error_desc(cassError));
+        }
+    }
+}
+
+CqlExchange::CqlExchange() {
+}
+
+CqlExchange::~CqlExchange() {
+}
+
+void
+CqlExchange::convertToDatabaseTime(const time_t& cltt,
+                                   const cass_int64_t& valid_lifetime,
+                                   cass_int64_t& expire) {
+    // Calculate expiry time. Store it in the 64-bit value so as we can
+    // detect overflows.
+    cass_int64_t expire_time = static_cast<cass_int64_t>(cltt) + valid_lifetime;
+
+    if (expire_time > DatabaseConnection::MAX_DB_TIME) {
+        isc_throw(BadValue, "Time value is too large: " << expire_time);
+    }
+
+    expire = expire_time;
+}
+
+void
+CqlExchange::convertFromDatabaseTime(const cass_int64_t& expire,
+                                     const cass_int64_t& valid_lifetime,
+                                     time_t& cltt) {
+    // Convert to local time
+    cltt = static_cast<time_t>(expire - valid_lifetime);
+}
+
+void
+CqlExchange::createBindForReceive(CqlDataArray& /* data */,
+                                  const int /* statementIndex = -1 */) {
+    isc_throw(NotImplemented,
+              "CqlExchange::createBindForReceive() not implemented yet.");
+}
+
+CqlDataArray
+CqlExchange::executeRead(const CqlConnection& connection,
+                         const CqlDataArray& data,
+                         const int statementIndex,
+                         const bool single /* = false */,
+                         const std::vector<std::string>&
+                             parameters /* = std::vector<std::string>() */) {
+    CassError rc;
+    CassStatement* statement = NULL;
+    CassFuture* future = NULL;
+
+    statement = cass_prepared_bind(connection.statements_[statementIndex]);
+    if (!statement) {
+        isc_throw(DbOperationError,
+                  "unable to bind statement "
+                      << connection.tagged_statements_[statementIndex].name_);
+    }
+
+    if (connection.force_consistency_) {
+        rc = cass_statement_set_consistency(statement, connection.consistency_);
+        if (rc != CASS_OK) {
+            cass_statement_free(statement);
+            isc_throw(
+                DbOperationError,
+                "unable to set statement consistency for statement "
+                    << connection.tagged_statements_[statementIndex].name_);
+        }
+    }
+
+    CqlCommon::bindData(statement, statementIndex, data, *this,
+                        connection.tagged_statements_);
+
+    future = cass_session_execute(connection.session_, statement);
+    if (!future) {
+        cass_statement_free(statement);
+        isc_throw(DbOperationError,
+                  "unable to execute statement "
+                      << connection.tagged_statements_[statementIndex].name_);
+    }
+    cass_future_wait(future);
+    std::string error;
+    connection.checkStatementError(error, future, statementIndex,
+                                   "unable to execute statement");
+    rc = cass_future_error_code(future);
+    if (rc != CASS_OK) {
+        cass_future_free(future);
+        cass_statement_free(statement);
+        isc_throw(DbOperationError, error);
+    }
+
+    // Get column values.
+    const CassResult* resultCollection = cass_future_get_result(future);
+    if (single && cass_result_row_count(resultCollection) > 1) {
+        cass_result_free(resultCollection);
+        cass_future_free(future);
+        cass_statement_free(statement);
+        isc_throw(MultipleRecords,
+                  "multiple records were found in the "
+                  "database where only one was expected for statement "
+                      << connection.tagged_statements_[statementIndex].name_);
+    }
+
+    // Get results.
+    CqlDataArray returnValues;
+    CqlDataArray collection;
+    CassIterator* rows = cass_iterator_from_result(resultCollection);
+    while (cass_iterator_next(rows)) {
+        const CassRow* row = cass_iterator_get_row(rows);
+        createBindForReceive(returnValues, statementIndex);
+        for (size_t i = 0U; i < returnValues.size(); i++) {
+            uint32_t pindex = i;
+            if (!parameters.empty()) {
+                const ExchangeColumnInfoContainerName& idx =
+                    parameters_.get<1>();
+                const ExchangeColumnInfoContainerNameRange& range =
+                    idx.equal_range(parameters[i]);
+                if (std::distance(range.first, range.second) > 0) {
+                    pindex = (*range.first)->index_;
+                }
+            }
+            CqlCommon::getData(row, pindex, i, *this, returnValues);
+        }
+        collection.add(retrieve());
+    }
+
+    // Free resources.
+    cass_iterator_free(rows);
+    cass_result_free(resultCollection);
+    cass_future_free(future);
+    cass_statement_free(statement);
+
+    return collection;
+}
+
+void
+CqlExchange::executeWrite(const CqlConnection& connection,
+                          const CqlDataArray& data,
+                          const int statementIndex) {
+    CassError rc;
+    CassStatement* statement = NULL;
+    CassFuture* future = NULL;
+
+    statement = cass_prepared_bind(connection.statements_[statementIndex]);
+    if (!statement) {
+        isc_throw(DbOperationError,
+                  "unable to bind statement "
+                      << connection.tagged_statements_[statementIndex].name_);
+    }
+
+    if (connection.force_consistency_) {
+        rc = cass_statement_set_consistency(statement, connection.consistency_);
+        if (rc != CASS_OK) {
+            cass_statement_free(statement);
+            isc_throw(
+                DbOperationError,
+                "unable to set statement consistency for statement "
+                    << connection.tagged_statements_[statementIndex].name_);
+        }
+    }
+
+    CqlCommon::bindData(statement, statementIndex, data, *this,
+                        connection.tagged_statements_);
+
+    future = cass_session_execute(connection.session_, statement);
+    if (!future) {
+        cass_statement_free(statement);
+        isc_throw(DbOperationError,
+                  "unable to execute statement "
+                      << connection.tagged_statements_[statementIndex].name_);
+    }
+    cass_future_wait(future);
+    std::string error;
+    connection.checkStatementError(error, future, statementIndex,
+                                   "unable to execute statement");
+    rc = cass_future_error_code(future);
+    if (rc != CASS_OK) {
+        cass_future_free(future);
+        cass_statement_free(statement);
+        isc_throw(DbOperationError, error);
+    }
+
+    // Check if statement has been applied.
+    bool applied = hasStatementBeenApplied(future);
+
+    // Free resources.
+    cass_future_free(future);
+    cass_statement_free(statement);
+
+    if (!applied) {
+        isc_throw(DuplicateEntry,
+                  "[applied] is false. Entry already exists. "
+                  "Statement "
+                      << connection.tagged_statements_[statementIndex].name_
+                      << "has not been applied");
+    }
+}
+
+bool
+CqlExchange::hasStatementBeenApplied(CassFuture* future,
+                                     size_t* row_count,
+                                     size_t* column_count) {
+    const CassResult* resultCollection = cass_future_get_result(future);
+    if (row_count) {
+        *row_count = cass_result_row_count(resultCollection);
+    }
+    if (column_count) {
+        *column_count = cass_result_column_count(resultCollection);
+    }
+    CassIterator* rows = cass_iterator_from_result(resultCollection);
+    CqlDataArray data;
+    cass_bool_t applied = cass_true;
+    while (cass_iterator_next(rows)) {
+        const CassRow* row = cass_iterator_get_row(rows);
+        // [applied]: bool
+        data.add(reinterpret_cast<void*>(&applied));
+
+        const ExchangeColumnInfoContainerName& idx = parameters_.get<1>();
+        const ExchangeColumnInfoContainerNameRange& range =
+            idx.equal_range("[applied]");
+        if (std::distance(range.first, range.second) > 0) {
+            CqlCommon::getData(row, (*range.first)->index_, 0, *this, data);
+        }
+    }
+    cass_iterator_free(rows);
+    cass_result_free(resultCollection);
+    return applied == cass_true;
+}
+
+void*
+CqlExchange::retrieve() {
+    isc_throw(NotImplemented, "CqlExchange::retrieve() not implemented yet.");
+}
+
+CqlVersionExchange::CqlVersionExchange() {
+    // Set the column names
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "version", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "minor", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    BOOST_ASSERT(parameters_.size() == 2U);
+}
+
+CqlVersionExchange::~CqlVersionExchange() {
+}
+
+void
+CqlVersionExchange::createBindForReceive(CqlDataArray& data,
+                                         const int /* statementIndex = -1 */) {
+    // Start with a fresh array.
+    data.clear();
+    // id: blob
+    data.add(reinterpret_cast<void*>(&version_));
+    // host_identifier: blob
+    data.add(reinterpret_cast<void*>(&minor_));
+}
+
+void*
+CqlVersionExchange::retrieve() {
+    pair_ = VersionPair(version_, minor_);
+    return reinterpret_cast<void*>(&pair_);
+}
+
+VersionPair
+CqlVersionExchange::retrieveVersion(const CqlConnection& connection,
+                                    int statementIndex) {
+    // Run statement.
+    const CqlDataArray whereValues;
+    CqlDataArray versionCollection =
+        executeRead(connection, whereValues, statementIndex, true);
+
+    VersionPair result;
+
+    if (!versionCollection.empty()) {
+        result = *(reinterpret_cast<VersionPair*>(
+            *versionCollection.begin()));
+    }
+
+    return result;
+}
+
+}  // namespace dhcp
+}  // namespace isc
diff --git a/src/lib/dhcpsrv/cql_exchange.h b/src/lib/dhcpsrv/cql_exchange.h
new file mode 100644 (file)
index 0000000..2d2e5e4
--- /dev/null
@@ -0,0 +1,325 @@
+// Copyright (C) 2016 Deutsche Telekom AG.
+//
+// Author: 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 CQL_EXCHANGE_H
+#define CQL_EXCHANGE_H
+
+#include <cassandra.h>
+#include <dhcpsrv/cql_connection.h>
+#include <dhcpsrv/sql_common.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Binds a C++ object to a Cassandra statement's parameter. Used in all
+///     statements.
+typedef CassError (*CqlBindFunction)(CassStatement* statement,
+                                     size_t index,
+                                     void* value);
+
+/// @brief Converts a single Cassandra column value to a C++ object. Used in
+///     SELECT statements.
+typedef CassError (*CqlGetFunction)(const CassValue* value, void* data);
+
+/// @brief Pair containing major and minor versions
+typedef std::pair<uint32_t, uint32_t> VersionPair;
+
+/// @brief Wrapper over the bind and get functions that interface with Cassandra
+struct CqlFunctionData {
+    /// @brief Binds a C++ object to a Cassandra statement's parameter. Used in
+    /// all
+    ///     statements.
+    CqlBindFunction cqlBindFunction_;
+    /// @brief Converts a single Cassandra column value to a C++ object. Used in
+    ///     SELECT statements.
+    CqlGetFunction cqlGetFunction_;
+};
+
+/// @brief Collection of bind and get functions, one pair for each
+///     parameter/column
+extern struct CqlFunctionData CqlFunctions[];
+
+/// @brief Structure used to bind C++ input values to dynamic CQL parameters
+///
+/// The structure contains a vector which stores the input values. The vector is
+/// passed directly into the CQL execute call. Note that the data values are
+/// stored as pointers. These pointers need to be valid for the duration of the
+/// CQL statement execution. In other words, populating them with pointers that
+/// go out of scope before the statement is executed results in undefined
+/// behaviour.
+struct CqlDataArray {
+    /// @brief Constructor
+    CqlDataArray() {
+    }
+
+    /// @brief Copy constructor
+    CqlDataArray(const CqlDataArray& other) : values_(other.values_) {
+    }
+
+    /// @brief Destructor
+    virtual ~CqlDataArray() {
+    }
+
+    /// @brief Add a at the end of the vector.
+    void add(void* value) {
+        values_.push_back(value);
+    }
+
+    /// @brief Remove the void pointer to the data value from a specified
+    ///     position inside the vector.
+    void remove(int index) {
+        if (values_.size() <= index) {
+            isc_throw(BadValue, "Index " << index << " out of bounds: [0, "
+                                         << (values_.size() - 1) << "]");
+        }
+        values_.erase(values_.begin() + index);
+    }
+
+    /// @brief Remove all data from the vector.
+    void clear() {
+        values_.clear();
+    }
+
+    /// @brief Get the number of elements inside the vector.
+    size_t size() const {
+        return values_.size();
+    }
+
+    /// @brief Check if the vector is empty.
+    bool empty() const {
+        return values_.empty();
+    }
+
+    /// @brief Square brackets operator overload
+    ///
+    /// Retrieves void pointer at specified position.
+    void* operator[](int i) const {
+        return values_[i];
+    }
+
+    /// @brief Iterator pointing to the first element
+    std::vector<void*>::const_iterator begin() const {
+        return values_.begin();
+    }
+
+    /// @brief Iterator pointing to the past-the-end element
+    std::vector<void*>::const_iterator end() const {
+        return values_.end();
+    }
+
+    /// @brief Vector of pointers to the data values
+    std::vector<void*> values_;
+};
+
+/// @brief Cassandra Exchange
+///
+/// Used to convert between Cassandra CQL and C++ data types. A different
+/// exchange is made for every distinct set of columns. Multiple tables may use
+/// the same exchange if they have the same columns.
+class CqlExchange : public virtual SqlExchange {
+public:
+    /// @brief Constructor
+    ///
+    /// Empty body. Derived constructors specify table columns.
+    CqlExchange();
+
+    /// @brief Destructor
+    virtual ~CqlExchange();
+
+    /// @name Time conversion:
+    /// @{
+    static void convertToDatabaseTime(const time_t& cltt,
+                                      const cass_int64_t& valid_lifetime,
+                                      cass_int64_t& expire);
+    static void convertFromDatabaseTime(const cass_int64_t& expire,
+                                        const cass_int64_t& valid_lifetime,
+                                        time_t& cltt);
+    /// @}
+
+    /// @brief Create BIND array to receive C++ data.
+    ///
+    /// Used in executeRead() to retrieve from database
+    ///
+    /// @param data array of bound objects representing data to be retrieved
+    /// @param statementIndex prepared statement to be executed; defaults to an
+    ///     invalid index
+    virtual void createBindForReceive(CqlDataArray& data,
+                                      const int statementIndex = -1);
+
+    /// @brief Executes select statements.
+    ///
+    /// @param connection connection used to communicate with the Cassandra
+    ///     database
+    /// @param whereValues array of bound objects used to filter the results
+    /// @param statementIndex prepared statement being executed
+    /// @param single true if a single row should be returned; by default,
+    /// multiple rows are allowed
+    /// @param parameters Output parameters of a statement ( used in WHERE
+    ///     clause ); optional, needed only if parameters in the statement are
+    ///     in a different order than in the schema
+    ///
+    /// @return collection of void* objects
+    ///
+    /// @throw DbOperationError
+    /// @throw MultipleRecords
+    CqlDataArray executeRead(const CqlConnection& connection,
+                             const CqlDataArray& whereValues,
+                             const int statementIndex,
+                             const bool single = false,
+                             const std::vector<std::string>& parameters =
+                                 std::vector<std::string>());
+
+    /// @brief Executes insert, update, delete or other statements.
+    ///
+    /// @param connection connection used to communicate with the Cassandra
+    ///     database
+    /// @param assignedValues array of bound objects to be used when inserting
+    ///     values
+    /// @param statementIndex prepared statement to be executed
+    void executeWrite(const CqlConnection& connection,
+                      const CqlDataArray& assignedValues,
+                      const int statementIndex);
+
+    /// @brief Check if CQL statement has been applied.
+    ///
+    /// @param future structure used to wait on statement executions
+    /// @param row_count number of rows returned
+    /// @param column_count number of columns queried
+    ///
+    /// @return true if statement has been succesfully applied, false otherwise
+    bool hasStatementBeenApplied(CassFuture* future,
+                                 size_t* row_count = NULL,
+                                 size_t* column_count = NULL);
+
+    /// @brief Copy received data into the derived class' object.
+    ///
+    /// Copies information about the entity to be retrieved into a holistic
+    /// object. Called in @ref executeRead(). Not implemented for base class
+    /// CqlExchange. To be implemented in derived classes.
+    ///
+    /// @return a pointer to the object retrieved.
+    virtual void* retrieve();
+};
+
+/// @brief Exchange used to retrieve schema version from the keyspace.
+class CqlVersionExchange : public virtual CqlExchange {
+public:
+    /// @brief Constructor
+    ///
+    /// Specifies table columns.
+    CqlVersionExchange();
+
+    /// @brief Destructor
+    virtual ~CqlVersionExchange();
+
+    /// @brief Create BIND array to receive C++ data.
+    ///
+    /// Used in executeRead() to retrieve from database
+    ///
+    /// @param data array of bound objects representing data to be retrieved
+    /// @param statementIndex prepared statement to be executed; defaults to an
+    ///     invalid index
+    virtual void createBindForReceive(CqlDataArray& data,
+                                      const int statementIndex = -1);
+
+    /// @brief Standalone method used to retrieve schema version
+    ///
+    /// @param connection array of bound objects representing data to be
+    /// retrieved
+    /// @param statementIndex prepared statement to be executed
+    ///
+    /// @return version of schema specified in the prepared statement in the
+    /// @ref CqlConnection parameter
+    virtual VersionPair
+    retrieveVersion(const CqlConnection& connection, int statementIndex);
+
+    /// @brief Copy received data into the <version,minor> pair.
+    ///
+    /// Copies information about the version to be retrieved into a pair. Called
+    /// in executeRead().
+    ///
+    /// @return a pointer to the object retrieved.
+    virtual void* retrieve();
+
+private:
+    cass_int32_t version_;
+    cass_int32_t minor_;
+    VersionPair pair_;
+};
+
+/// @brief Common operations in Cassandra exchanges
+class CqlCommon {
+public:
+    /// @brief Returns type of data for specific parameter.
+    ///
+    /// Returns type of a given parameter of a given statement.
+    ///
+    /// @param stindex Index of statement being executed.
+    /// @param pindex Index of the parameter for a given statement.
+    /// @param exchange Exchange object to use
+    /// @param tagged_statements CqlTaggedStatement array to use
+    static ExchangeDataType
+    getDataType(const uint32_t stindex,
+                int pindex,
+                const SqlExchange& exchange,
+                const CqlTaggedStatement* tagged_statements);
+
+    /// @brief Binds data specified in data
+    ///
+    /// Calls one of cass_value_bind_([none|bool|int32|int64|string|bytes]).
+    /// It is used to bind C++ data types used by Kea into formats used by
+    /// Cassandra.
+    ///
+    /// @param statement Statement object representing the query
+    /// @param stindex Index of statement being executed
+    /// @param data array that has been created for the type of lease in
+    ///     question.
+    /// @param exchange Exchange object to use
+    /// @param tagged_statements CqlTaggedStatement array to use
+    static void bindData(CassStatement* statement,
+                         uint32_t stindex,
+                         const CqlDataArray& data,
+                         const SqlExchange& exchange,
+                         const CqlTaggedStatement* tagged_statements);
+
+    /// @brief Retrieves data returned by Cassandra.
+    ///
+    /// Calls one of cass_value_bind_([none|bool|int32|int64|string|bytes]).
+    /// Used to retrieve data returned by Cassandra into standard C++ types used
+    /// by Kea.
+    ///
+    /// @param row row of data returned by CQL library
+    /// @param pindex Index of statement being executed
+    /// @param dindex data index (specifies which entry in size array is used)
+    /// @param exchange Exchange object to use
+    /// @param data array that has been created for the type of lease in
+    ///     question.
+    static void getData(const CassRow* row,
+                        int pindex,
+                        int dindex,
+                        const SqlExchange& exchange,
+                        CqlDataArray& data);
+};
+
+}  // namespace dhcp
+}  // namespace isc
+
+#endif  // CQL_EXCHANGE_H
diff --git a/src/lib/dhcpsrv/cql_host_data_source.cc b/src/lib/dhcpsrv/cql_host_data_source.cc
new file mode 100644 (file)
index 0000000..43bebf0
--- /dev/null
@@ -0,0 +1,1981 @@
+// Copyright (C) 2016 Deutsche Telekom AG.
+//
+// Author: Andrei Pavel <andrei.pavel@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 <config.h>
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cql_host_data_source.h>
+#include <dhcpsrv/cql_lease_mgr.h>
+#include <dhcpsrv/db_exceptions.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <util/buffer.h>
+#include <util/optional_value.h>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/tuple/tuple.hpp>
+#include <boost/unordered_map.hpp>
+
+#include <openssl/md5.h>
+
+#include <list>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Host identifier consisting of DUID or hardware address
+typedef std::vector<uint8_t> HostIdentifier;
+
+/// @brief Host identifier converted to Cassandra data type
+typedef std::vector<cass_byte_t> CassHostIdentifier;
+
+/// @brief Host identifier converted to Cassandra data type
+typedef std::vector<cass_byte_t> CassOptionBuffer;
+
+/// @brief key for HostMap containing objects which uniquely identify a
+///     host: host identifier, host identifier type, subnets for IPv4 and IPv6
+typedef boost::tuple<HostIdentifier,
+                     Host::IdentifierType,
+                     SubnetID,
+                     SubnetID,
+                     asiolink::IOAddress>
+    HostKey;
+
+/// @brief Map used to merge reservations and options into a single host on
+///     retrieve from database
+typedef boost::unordered_map<HostKey, HostPtr, boost::hash<HostKey> > HostMap;
+
+/// @brief Maximum size of an IPv4 address represented as a text string. 12
+///     digits plus 3 full stops (dots).
+static const size_t ADDRESS4_TEXT_MAX_LENGTH = 15U;
+
+/// @brief Maximum size of an IPv6 address represented as a text string. 32
+///     hexadecimal characters written in 8 groups of four, plus 7 colon
+///     separators.
+static const size_t ADDRESS6_TEXT_MAX_LENGTH = 39U;
+
+/// @brief Maximum length of classes stored in a host_ipv4/6_client_classes
+///     column.
+static const size_t CLIENT_CLASSES_MAX_LENGTH = 255U;
+
+/// @brief Maximum length of the hostname stored in DNS. This length is
+///     restricted by the length of the domain-name carried in the Client FQDN
+///     Option (see RFC4702 and RFC4704).
+static const size_t HOSTNAME_MAX_LENGTH = 255U;
+
+/// @brief Maximum length of option value
+static const size_t OPTION_VALUE_MAX_LENGTH = 4096U;
+
+/// @brief Maximum length of option value specified in textual format
+static const size_t OPTION_FORMATTED_VALUE_MAX_LENGTH = 8192U;
+
+/// @brief Maximum length of option space name
+static const size_t OPTION_SPACE_MAX_LENGTH = 128U;
+
+/// @brief Numeric value representing the last supported identifier. This value
+///     is used to validate whether the identifier type stored in a database is
+///     within bounds of supported identifiers.
+static const cass_int8_t MAX_IDENTIFIER_TYPE =
+    static_cast<cass_int8_t>(Host::IDENT_CIRCUIT_ID);
+
+/// @{
+/// @brief Invalid values in the Cassandra database
+static const char NULL_RESERVED_IPV6_PREFIX_ADDRESS[] = "";
+static const cass_int32_t NULL_RESERVED_IPV6_PREFIX_LENGTH = 0;
+static const cass_int32_t NULL_RESERVED_IPV6_PREFIX_ADDRESS_TYPE = -1;
+static const cass_int32_t NULL_IAID = -1;
+static const cass_int32_t NULL_OPTION_UNIVERSE = -1;
+static const cass_int32_t NULL_OPTION_CODE = -1;
+static const CassOptionBuffer NULL_OPTION_VALUE = CassOptionBuffer();
+static const char NULL_OPTION_FORMATTED_VALUE[] = "";
+static const char NULL_OPTION_SPACE[] = "";
+static const cass_bool_t NULL_OPTION_IS_PERSISTENT = cass_false;
+static const char NULL_OPTION_CLIENT_CLASS[] = "";
+static const cass_int32_t NULL_OPTION_SUBNET_ID = -1;
+/// @}
+
+/// @brief Invalid reservation used to check for an invalid IPv6Resrv formed
+///     from database values.
+static const IPv6Resrv NULL_IPV6_RESERVATION =
+    IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("::"), 128);
+
+}  // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @{
+/// @brief Statement parameters
+// INSERT_HOST
+static const char* INSERT_HOST_PARAMS[] = {
+    static_cast<const char*>("id"),
+    static_cast<const char*>("host_identifier"),
+    static_cast<const char*>("host_identifier_type"),
+    static_cast<const char*>("host_ipv4_subnet_id"),
+    static_cast<const char*>("host_ipv6_subnet_id"),
+    static_cast<const char*>("host_ipv4_address"),
+    static_cast<const char*>("hostname"),
+    static_cast<const char*>("host_ipv4_client_classes"),
+    static_cast<const char*>("host_ipv6_client_classes"),
+    static_cast<const char*>("reserved_ipv6_prefix_address"),
+    static_cast<const char*>("reserved_ipv6_prefix_length"),
+    static_cast<const char*>("reserved_ipv6_prefix_address_type"),
+    static_cast<const char*>("iaid"),
+    static_cast<const char*>("option_universe"),
+    static_cast<const char*>("option_code"),
+    static_cast<const char*>("option_value"),
+    static_cast<const char*>("option_formatted_value"),
+    static_cast<const char*>("option_space"),
+    static_cast<const char*>("option_is_persistent"),
+    static_cast<const char*>("option_client_class"),
+    static_cast<const char*>("option_subnet_id"),
+    NULL};
+// GET_HOST_BY_HOST_ID
+static const char* GET_HOST_BY_HOST_ID_PARAMS[] = {
+    static_cast<const char*>("host_identifier"),
+    static_cast<const char*>("host_identifier_type"),
+    NULL};
+// GET_HOST_BY_IPV4_ADDRESS
+static const char* GET_HOST_BY_IPV4_ADDRESS_PARAMS[] = {
+    static_cast<const char*>("host_ipv4_address"),
+    NULL};
+// GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID
+static const char* GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID_PARAMS[] = {
+    static_cast<const char*>("host_ipv4_subnet_id"),
+    static_cast<const char*>("host_identifier"),
+    static_cast<const char*>("host_identifier_type"),
+    NULL};
+// GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID,
+static const char* GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID_PARAMS[] = {
+    static_cast<const char*>("host_ipv6_subnet_id"),
+    static_cast<const char*>("host_identifier"),
+    static_cast<const char*>("host_identifier_type"),
+    NULL};
+// GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS,
+static const char* GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS_PARAMS[] = {
+    static_cast<const char*>("host_ipv4_subnet_id"),
+    static_cast<const char*>("host_ipv4_address"),
+    NULL};
+// GET_HOST_BY_IPV6_PREFIX
+static const char* GET_HOST_BY_IPV6_PREFIX_PARAMS[] = {
+    static_cast<const char*>("reserved_ipv6_prefix_address"),
+    static_cast<const char*>("reserved_ipv6_prefix_length"),
+    NULL};
+// GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS
+static const char* GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS_PARAMS[] = {
+    static_cast<const char*>("host_ipv6_subnet_id"),
+    static_cast<const char*>("reserved_ipv6_prefix_address"),
+    NULL};
+// GET_VERSION
+static const char* GET_VERSION_PARAMS[] = {NULL};
+/// @}
+
+/// @brief Prepared CQL statements used by the backend to insert and retrieve
+///     hosts from the database.
+static CqlTaggedStatement tagged_statements[] = {
+    // INSERT_HOST
+    {INSERT_HOST_PARAMS, "INSERT_HOST",
+     "INSERT INTO host_reservations ( "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     ") "
+     "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "
+     "IF NOT EXISTS "
+    },
+
+    // GET_HOST_BY_HOST_ID
+    {GET_HOST_BY_HOST_ID_PARAMS, "GET_HOST_BY_HOST_ID",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_identifier = ? "
+     "AND host_identifier_type = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV4_ADDRESS
+    {GET_HOST_BY_IPV4_ADDRESS_PARAMS, "GET_HOST_BY_IPV4_ADDRESS",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_ipv4_address = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID
+    {GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID_PARAMS,
+     "GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_ipv4_subnet_id = ? "
+     "AND host_identifier = ? "
+     "AND host_identifier_type = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID
+    {GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID_PARAMS,
+     "GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_ipv6_subnet_id = ? "
+     "AND host_identifier = ? "
+     "AND host_identifier_type = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS
+    {GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS_PARAMS,
+     "GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_ipv4_subnet_id = ? "
+     "AND host_ipv4_address = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV6_PREFIX
+    {GET_HOST_BY_IPV6_PREFIX_PARAMS, "GET_HOST_BY_IPV6_PREFIX",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE reserved_ipv6_prefix_address = ? "
+     "AND reserved_ipv6_prefix_length = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS
+    {GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS_PARAMS,
+     "GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_ipv6_subnet_id = ? "
+     "AND reserved_ipv6_prefix_address = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_VERSION
+    {GET_VERSION_PARAMS, "GET_VERSION",
+     "SELECT version, minor FROM schema_version"},
+
+    // End of list sentinel
+    {NULL, NULL, NULL}};  // tagged_statements
+
+/// @brief Provides mechanisms for sending and retrieving data from the
+///     host_reservations table.
+class CqlHostExchange : public virtual CqlExchange {
+public:
+    /// @brief Statement Tags
+    ///
+    /// The contents of the enum are indexes into the list of CQL statements
+    enum StatementIndex {
+        // Inserts all options belonging to any reservation from a single host.
+        INSERT_HOST,
+        // Retrieves host information, IPv6 reservations and both IPv4 and IPv6
+        // options associated with the host.
+        GET_HOST_BY_HOST_ID,
+        // Retrieves host information along with the IPv4 options associated
+        // with it.
+        GET_HOST_BY_IPV4_ADDRESS,
+        // Retrieves host information and IPv4 options using subnet identifier
+        // and client's identifier (i.e. hardware address or DUID).
+        GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID,
+        // Retrieves host information, IPv6 reservations and IPv6 options
+        // associated with a host using subnet identifier and client's
+        // identifier (i.e. hardware address or DUID).
+        GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID,
+        // Retrieves host information and IPv4 options for the host using subnet
+        // identifier and IPv4 reservation.
+        GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS,
+        // Retrieves host information, IPv6 reservations and IPv6 options
+        // associated with a host using prefix and prefix length. This query
+        // returns host information for a single host. However, multiple rows
+        // are returned due to left joining IPv6 reservations and IPv6 options.
+        // The number of rows returned is multiplication of number of existing
+        // IPv6 reservations and IPv6 options.
+        GET_HOST_BY_IPV6_PREFIX,
+        // Retrieves host information and IPv6 options for the host using subnet
+        // identifier and IPv6 reservation.
+        GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS,
+        // Get CQL schema version.
+        GET_VERSION,
+        // Number of statements
+        NUM_STATEMENTS
+    };
+
+    /// @brief Constructor
+    ///
+    /// Specifies table columns.
+    CqlHostExchange();
+
+    /// @brief Virtual destructor.
+    virtual ~CqlHostExchange();
+
+    /// @brief Binds member variables to data array to receive @ref Host data.
+    ///
+    /// Creates a bind array to receive @ref Host data from the Cassandra
+    /// database. After data is successfully received, @ref retrieve() can be
+    /// called to retrieve the @ref Host object. Called in @ref
+    /// CqlExchange::executeRead().
+    ///
+    /// @param data array of objects representing data being retrieved
+    /// @param statement_index prepared statement being executed; defaults to an
+    ///     invalid statement
+    virtual void createBindForReceive(CqlDataArray& data,
+                                      const int statement_index = -1);
+
+    /// @brief Binds @ref Host to data array to send data to the Cassandra
+    ///     database.
+    ///
+    /// Fills in the bind array for sending data stored in the @ref Host object
+    /// to the database.
+    ///
+    /// @param host @ref Host object being added to the Cassandra database
+    /// @param subnet_id identifier of the subnet to which the host belongs
+    /// @param reservation reservation belonging to the host
+    /// @param option_space option space
+    /// @param option_descriptor structure used to hold option information
+    /// @param data array being filled with data from to the Host object
+    void createBindForSend(const HostPtr& host,
+                           const OptionalValue<SubnetID>& subnet_id,
+                           const IPv6Resrv* const reservation,
+                           const std::string& option_space,
+                           const OptionDescriptor& option_descriptor,
+                           CqlDataArray& data);
+
+    /// @brief Create unique hash for storage in table id.
+    ///
+    /// Hash function used for creating a pseudo-unique hash from member
+    /// values which uniquely determine an entry in the table. Uses OpenSSL's
+    /// MD5 implementation.
+    ///
+    /// The primary key aggregates: host_ipv4_subnet_id, host_ipv6_subnet_id,
+    /// host_ipv4_address, reserved_ipv6_prefix_address,
+    /// reserved_ipv6_prefix_length, option_code, option_space.
+    cass_int64_t hashIntoId() const;
+
+    /// @brief Copy received data into Host object
+    ///
+    /// Copies information about the host into a newly created @ref Host object
+    /// Called in @ref executeRead after @ref createBindForReceive().
+    ///
+    /// @return Host Pointer to a @ref HostPtr object holding a pointer to the
+    /// @ref Host object returned.
+    virtual void* retrieve();
+
+    /// @brief Creates IPv6 reservation from the data contained in the
+    /// currently processed row.
+    ///
+    /// Called after createBindForReceive().
+    ///
+    /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
+    const IPv6Resrv retrieveReservation() const;
+
+    /// @brief Retrieves option from members.
+    OptionDescriptorPtr retrieveOption() const;
+
+private:
+    /// Pointer to Host object holding information being inserted into database.
+    HostPtr host_;
+
+    /// @brief Primary key. Aggregates: host_identifier, host_identifier_type,
+    /// reserved_ipv6_prefix_address, reserved_ipv6_prefix_length, option_code,
+    /// option_space.
+    cass_int64_t id_;
+
+    /// @brief Client's identifier (e.g. DUID, HW address) in binary format
+    CassHostIdentifier host_identifier_;
+
+    /// @brief Type of the identifier in the host_identifier_
+    /// This value corresponds to the @ref Host::IdentifierType value.
+    cass_int32_t host_identifier_type_;
+
+    /// @brief IPv4 subnet identifier
+    cass_int32_t host_ipv4_subnet_id_;
+
+    /// @brief Ipv6 subnet identifier
+    cass_int32_t host_ipv6_subnet_id_;
+
+    /// @brief Reserved IPv4 address
+    cass_int32_t host_ipv4_address_;
+
+    /// @brief Name reserved for the host
+    std::string hostname_;
+
+    /// @brief A string holding comma separated list of IPv4 client classes
+    std::string host_ipv4_client_classes_;
+
+    /// @brief A string holding comma separated list of IPv6 client classes
+    std::string host_ipv6_client_classes_;
+
+    /// @brief Address belonging to the reserved IPv6 prefix
+    std::string reserved_ipv6_prefix_address_;
+
+    /// @brief Length of the reserved IPv6 prefix
+    cass_int32_t reserved_ipv6_prefix_length_;
+
+    /// @brief Reserver IPv6 prefix type
+    /// This value corresponds to the @ref Host::IdentifierType value.
+    cass_int32_t reserved_ipv6_prefix_address_type_;
+
+    /// @brief The reservation's IAID
+    cass_int32_t iaid_;
+
+    /// @brief Version of DHCP (i.e. 0 for DHCPv4 and 1 for DHCPv6) to which the
+    ///     option belongs.
+    cass_int32_t option_universe_;
+
+    /// @brief Option code
+    cass_int32_t option_code_;
+
+    /// @brief Option value
+    CassOptionBuffer option_value_;
+
+    /// @brief The textual value of an option
+    std::string option_formatted_value_;
+
+    /// @brief Option space name
+    std::string option_space_;
+
+    /// @brief Flag indicating if option is always sent or only on request
+    cass_bool_t option_is_persistent_;
+
+    /// @brief Option client class
+    std::string option_client_class_;
+
+    /// @brief Subnet identifier
+    cass_int32_t option_subnet_id_;
+};  // CqlHostExchange
+
+CqlHostExchange::CqlHostExchange()
+    : host_identifier_type_(0), host_ipv4_subnet_id_(0),
+      host_ipv6_subnet_id_(0), host_ipv4_address_(0),
+      reserved_ipv6_prefix_length_(0), reserved_ipv6_prefix_address_type_(0),
+      iaid_(0), option_universe_(0), option_code_(0),
+      option_is_persistent_(cass_false), option_subnet_id_(0) {
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT64)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_identifier", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_BYTES)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_identifier_type", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_ipv4_subnet_id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_ipv6_subnet_id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_ipv4_address", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "hostname", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_ipv4_client_classes", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_ipv6_client_classes", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "reserved_ipv6_prefix_address", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "reserved_ipv6_prefix_length", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "reserved_ipv6_prefix_address_type", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "iaid", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_universe", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_code", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_value", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_BYTES)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_formatted_value", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_space", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_is_persistent", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BOOL)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_client_class", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_subnet_id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "[applied]", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_BOOL)));
+    BOOST_ASSERT(parameters_.size() == 22U);
+}
+
+CqlHostExchange::~CqlHostExchange() {
+}
+
+void
+CqlHostExchange::createBindForReceive(CqlDataArray& data,
+                                      const int /* statement_index = -1 */) {
+    // Start with a fresh array.
+    data.clear();
+    // id: blob
+    data.add(reinterpret_cast<void*>(&id_));
+    // host_identifier: blob
+    data.add(reinterpret_cast<void*>(&host_identifier_));
+    // host_identifier_type: int
+    data.add(reinterpret_cast<void*>(&host_identifier_type_));
+    // host_ipv4_subnet_id: int
+    data.add(reinterpret_cast<void*>(&host_ipv4_subnet_id_));
+    // host_ipv6_subnet_id: int
+    data.add(reinterpret_cast<void*>(&host_ipv6_subnet_id_));
+    // host_ipv4_address: int
+    data.add(reinterpret_cast<void*>(&host_ipv4_address_));
+    // hostname: text
+    data.add(reinterpret_cast<void*>(&hostname_));
+    // host_ipv4_client_classes: text
+    data.add(reinterpret_cast<void*>(&host_ipv4_client_classes_));
+    // host_ipv6_client_classes: text
+    data.add(reinterpret_cast<void*>(&host_ipv6_client_classes_));
+    // reserved_ipv6_prefix_address: text
+    data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address_));
+    // reserved_ipv6_prefix_length: int
+    data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_length_));
+    // reserved_ipv6_prefix_length: int
+    data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address_type_));
+    // iaid: int
+    data.add(reinterpret_cast<void*>(&iaid_));
+    // option_universe: int
+    data.add(reinterpret_cast<void*>(&option_universe_));
+    // option_code: int
+    data.add(reinterpret_cast<void*>(&option_code_));
+    // option_value: blob
+    data.add(reinterpret_cast<void*>(&option_value_));
+    // option_formatted_value: text
+    data.add(reinterpret_cast<void*>(&option_formatted_value_));
+    // option_space: text
+    data.add(reinterpret_cast<void*>(&option_space_));
+    // option_is_persistent: boolean
+    data.add(reinterpret_cast<void*>(&option_is_persistent_));
+    // option_client_class: text
+    data.add(reinterpret_cast<void*>(&option_client_class_));
+    // option_subnet_id: int
+    data.add(reinterpret_cast<void*>(&option_subnet_id_));
+}
+
+void
+CqlHostExchange::createBindForSend(const HostPtr& host,
+                                   const OptionalValue<SubnetID>& subnet_id,
+                                   const IPv6Resrv* const reservation,
+                                   const std::string& option_space,
+                                   const OptionDescriptor& option_descriptor,
+                                   CqlDataArray& data) {
+    // Store host object to ensure it remains valid.
+    host_ = host;
+
+    // Set up the structures for the various components of the host
+    // structure.
+    try {
+        // host_identifier: blob
+        // Convert from std::vector<uint8_t> to std::vector<cass_byte_t>.
+        HostIdentifier host_identifier = host->getIdentifier();
+        host_identifier_ = CassHostIdentifier(host_identifier.begin(),
+                                              host_identifier.end());
+        if (host_identifier_.size() > DUID::MAX_DUID_LEN) {
+            isc_throw(BadValue, "host identifier "
+                                    << host_identifier_.data() << " of length "
+                                    << host_identifier_.size()
+                                    << " is greater than allowed of "
+                                    << DUID::MAX_DUID_LEN);
+        }
+
+        // host_identifier_type: int
+        host_identifier_type_ =
+            static_cast<cass_int32_t>(host->getIdentifierType());
+        if (host_identifier_type_ > MAX_IDENTIFIER_TYPE) {
+            isc_throw(BadValue, "invalid host identifier type returned: "
+                                    << host_identifier_type_);
+        }
+
+        // host_ipv4_subnet_id: int
+        host_ipv4_subnet_id_ =
+            static_cast<cass_int32_t>(host->getIPv4SubnetID());
+
+        // host_ipv6_subnet_id: int
+        host_ipv6_subnet_id_ =
+            static_cast<cass_int32_t>(host->getIPv6SubnetID());
+
+        // host_ipv4_address: int
+        host_ipv4_address_ =
+            static_cast<cass_int32_t>(host->getIPv4Reservation().toUint32());
+
+        // hostname: text
+        hostname_ = host->getHostname();
+        if (hostname_.size() > HOSTNAME_MAX_LENGTH) {
+            isc_throw(BadValue, "hostname " << hostname_ << " of length "
+                                            << hostname_.size()
+                                            << " is greater than allowed of "
+                                            << HOSTNAME_MAX_LENGTH);
+        }
+
+        // host_ipv4_client_classes: text
+        host_ipv4_client_classes_ = host->getClientClasses4().toText(",");
+        if (host_ipv4_client_classes_.size() > CLIENT_CLASSES_MAX_LENGTH) {
+            isc_throw(BadValue, "IPv4 client classes "
+                                    << host_ipv4_client_classes_
+                                    << " of length "
+                                    << host_ipv4_client_classes_.size()
+                                    << " is greater than allowed of "
+                                    << CLIENT_CLASSES_MAX_LENGTH);
+        }
+
+        // host_ipv6_client_classes: text
+        host_ipv6_client_classes_ = host->getClientClasses6().toText(",");
+        if (host_ipv6_client_classes_.size() > CLIENT_CLASSES_MAX_LENGTH) {
+            isc_throw(BadValue, "IPv6 client classes "
+                                    << host_ipv6_client_classes_
+                                    << " of length "
+                                    << host_ipv6_client_classes_.size()
+                                    << " is greater than allowed of "
+                                    << CLIENT_CLASSES_MAX_LENGTH);
+        }
+
+        if (reservation == NULL) {
+            reserved_ipv6_prefix_address_ = NULL_RESERVED_IPV6_PREFIX_ADDRESS;
+            reserved_ipv6_prefix_length_ = NULL_RESERVED_IPV6_PREFIX_LENGTH;
+            reserved_ipv6_prefix_address_type_ =
+                NULL_RESERVED_IPV6_PREFIX_ADDRESS_TYPE;
+            iaid_ = NULL_IAID;
+        } else {
+            // reserved_ipv6_prefix_address: text
+            reserved_ipv6_prefix_address_ = reservation->getPrefix().toText();
+
+            // reserved_ipv6_prefix_length: int
+            reserved_ipv6_prefix_length_ =
+                static_cast<cass_int32_t>(reservation->getPrefixLen());
+
+            // reserved_ipv6_prefix_address: int
+            reserved_ipv6_prefix_address_type_ =
+                reservation->getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
+
+            // iaid: int
+            /// @todo: We don't support iaid in the IPv6Resrv yet.
+            iaid_ = 0;
+        }
+
+        if (option_descriptor.option_ == NULL) {
+            option_universe_ = NULL_OPTION_UNIVERSE;
+            option_code_ = NULL_OPTION_CODE;
+            option_value_ = NULL_OPTION_VALUE;
+            option_formatted_value_ = NULL_OPTION_FORMATTED_VALUE;
+            option_space_ = NULL_OPTION_SPACE;
+            option_is_persistent_ = NULL_OPTION_IS_PERSISTENT;
+            option_client_class_ = NULL_OPTION_CLIENT_CLASS;
+            option_subnet_id_ = NULL_OPTION_SUBNET_ID;
+        } else {
+            // option_universe: int
+            option_universe_ = option_descriptor.option_->getUniverse();
+
+            // option_code: int
+            option_code_ = option_descriptor.option_->getType();
+
+            // option_value: blob
+            // option_formatted_value: text
+            if (option_descriptor.formatted_value_.empty()) {
+                if (option_descriptor.option_->len() >
+                    option_descriptor.option_->getHeaderLen()) {
+                    // The formatted_value is empty and the option value
+                    // is not empty so we need to prepare on-wire format
+                    // for the option and store it in the database as a
+                    // blob.
+                    OutputBuffer buffer(option_descriptor.option_->len());
+                    option_descriptor.option_->pack(buffer);
+                    const char* buffer_ptr =
+                        static_cast<const char*>(buffer.getData());
+                    option_value_.assign(
+                        buffer_ptr + option_descriptor.option_->getHeaderLen(),
+                        buffer_ptr + buffer.getLength());
+                } else {
+                    option_value_.clear();
+                }
+                option_formatted_value_.clear();
+            } else {
+                option_value_.clear();
+                option_formatted_value_ = option_descriptor.formatted_value_;
+            }
+
+            // option_space: text
+            option_space_ = option_space;
+
+            // option_is_persistent: boolean
+            option_is_persistent_ =
+                option_descriptor.persistent_ ? cass_true : cass_false;
+
+            // option_client_class: text
+            /// @todo Assign actual value to client class string.
+            option_client_class_.clear();
+
+            // option_subnet_id: int
+            if (subnet_id.isSpecified()) {
+                option_subnet_id_ = subnet_id;
+            } else {
+                option_subnet_id_ = 0;
+            }
+        }
+
+        // id: bigint
+        id_ = hashIntoId();
+
+        // Add all parameters to bind array.
+        data.clear();
+        data.add(reinterpret_cast<void*>(&id_));
+        data.add(reinterpret_cast<void*>(&host_identifier_));
+        data.add(reinterpret_cast<void*>(&host_identifier_type_));
+        data.add(reinterpret_cast<void*>(&host_ipv4_subnet_id_));
+        data.add(reinterpret_cast<void*>(&host_ipv6_subnet_id_));
+        data.add(reinterpret_cast<void*>(&host_ipv4_address_));
+        data.add(reinterpret_cast<void*>(&hostname_));
+        data.add(reinterpret_cast<void*>(&host_ipv4_client_classes_));
+        data.add(reinterpret_cast<void*>(&host_ipv6_client_classes_));
+        data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address_));
+        data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_length_));
+        data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address_type_));
+        data.add(reinterpret_cast<void*>(&iaid_));
+        data.add(reinterpret_cast<void*>(&option_universe_));
+        data.add(reinterpret_cast<void*>(&option_code_));
+        data.add(reinterpret_cast<void*>(&option_value_));
+        data.add(reinterpret_cast<void*>(&option_formatted_value_));
+        data.add(reinterpret_cast<void*>(&option_space_));
+        data.add(reinterpret_cast<void*>(&option_is_persistent_));
+        data.add(reinterpret_cast<void*>(&option_client_class_));
+        data.add(reinterpret_cast<void*>(&option_subnet_id_));
+    } catch (const std::exception& ex) {
+        isc_throw(DbOperationError, "could not create bind array from host "
+                                        << host->getHostname()
+                                        << ", reason: " << ex.what());
+    }
+}
+
+uint64_t
+md5Hash(const std::string& input) {
+    // Prepare structures for MD5().
+    const size_t word_size = MD5_DIGEST_LENGTH / sizeof(uint64_t);
+    uint64_t hash[word_size];
+    unsigned char* digest = reinterpret_cast<unsigned char*>(hash);
+    unsigned char* string =
+        reinterpret_cast<unsigned char*>(const_cast<char*>(input.c_str()));
+    std::fill(hash, hash + word_size, 0);
+
+    // Get MD5 hash value.
+    MD5(string, input.size(), digest);
+
+    // Return the first part of the hash value which still retains all
+    // properties of the full hash value.
+    return hash[0];
+}
+
+cass_int64_t
+CqlHostExchange::hashIntoId() const {
+    // Allocates a fixed maximum length in the stringstream for each
+    // aggregated field to avoid collisions between distinct entries.
+
+    // Get key.
+    std::stringstream key_stream;
+    key_stream << std::setw(10) << std::setfill('0') << host_ipv4_subnet_id_;
+    key_stream << std::setw(10) << std::setfill('0') << host_ipv6_subnet_id_;
+    key_stream << std::setw(ADDRESS4_TEXT_MAX_LENGTH) << host_ipv4_address_;
+    key_stream << std::setw(ADDRESS6_TEXT_MAX_LENGTH)
+               << reserved_ipv6_prefix_address_;
+    key_stream << std::setw(4) << std::setfill('0')
+               << reserved_ipv6_prefix_length_;
+    key_stream << std::setw(4) << std::setfill('0') << option_code_;
+    key_stream << std::setw(OPTION_SPACE_MAX_LENGTH) << option_space_;
+    const std::string key = key_stream.str();
+
+    const uint64_t md5 = md5Hash(key);
+
+    return static_cast<cass_int64_t>(md5);
+}
+
+void*
+CqlHostExchange::retrieve() {
+    uint64_t id = static_cast<uint64_t>(id_);
+
+    HostIdentifier host_identifier =
+        HostIdentifier(host_identifier_.begin(), host_identifier_.end());
+
+    // Set the host identifier type in a variable of the appropriate data type.
+    Host::IdentifierType host_identifier_type =
+        static_cast<Host::IdentifierType>(host_identifier_type_);
+
+    // Set IPv4 subnet ID to the value returned.
+    SubnetID ipv4_subnet_id = static_cast<SubnetID>(host_ipv4_subnet_id_);
+
+    // Set IPv6 subnet ID to the value returned.
+    SubnetID ipv6_subnet_id = static_cast<SubnetID>(host_ipv6_subnet_id_);
+
+    // Set IPv4 address reservation.
+    asiolink::IOAddress ipv4_reservation =
+        asiolink::IOAddress(host_ipv4_address_);
+
+    Host* host = new Host(host_identifier.data(), host_identifier.size(),
+                          host_identifier_type, ipv4_subnet_id, ipv6_subnet_id,
+                          ipv4_reservation, hostname_,
+                          host_ipv4_client_classes_, host_ipv6_client_classes_);
+    host->setHostId(id);
+
+    const IPv6Resrv reservation = retrieveReservation();
+    if (reservation != NULL_IPV6_RESERVATION &&
+        !host->hasReservation(reservation)) {
+        host->addReservation(reservation);
+    }
+
+    OptionDescriptorPtr option_descriptor_ptr = retrieveOption();
+    if (option_descriptor_ptr) {
+        if (option_descriptor_ptr->option_->getUniverse() == Option::V4) {
+            host->getCfgOption4()->add(*option_descriptor_ptr, option_space_);
+        } else if (option_descriptor_ptr->option_->getUniverse() ==
+                   Option::V6) {
+            host->getCfgOption6()->add(*option_descriptor_ptr, option_space_);
+        }
+    }
+
+    return reinterpret_cast<void*>(host);
+}
+
+const IPv6Resrv
+CqlHostExchange::retrieveReservation() const {
+    // Set the IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
+    IPv6Resrv::Type type;
+    switch (reserved_ipv6_prefix_address_type_) {
+    case 0:
+        type = IPv6Resrv::TYPE_NA;
+        break;
+    case 2:
+        type = IPv6Resrv::TYPE_PD;
+        break;
+    case NULL_RESERVED_IPV6_PREFIX_ADDRESS_TYPE:
+        return NULL_IPV6_RESERVATION;
+    default:
+        isc_throw(BadValue,
+                  "invalid IPv6 reservation type returned: "
+                      << reserved_ipv6_prefix_address_type_
+                      << ". Only 0 (IA_NA) or 2 (IA_PD) are allowed.");
+    }
+
+    return IPv6Resrv(type, IOAddress(reserved_ipv6_prefix_address_),
+                     reserved_ipv6_prefix_length_);
+}
+
+OptionDescriptorPtr
+CqlHostExchange::retrieveOption() const {
+    // Options are held in a binary or textual format in the database.
+    // This is similar to having an option specified in a server
+    // configuration file. Such option is converted to appropriate C++
+    // class, using option definition. Thus, we need to find the
+    // option definition for this option code and option space.
+
+    // If the option space is a standard DHCPv4 or DHCPv6 option space,
+    // this is most likely a standard option, for which we have a
+    // definition created within libdhcp++.
+    if (option_space_.empty() || option_universe_ == NULL_OPTION_UNIVERSE) {
+        return OptionDescriptorPtr();
+    }
+
+    OptionDefinitionPtr option_definition_ptr =
+        LibDHCP::getOptionDef(option_space_, option_code_);
+
+    // Otherwise, we may check if this an option encapsulated within the
+    // vendor space.
+    if (!option_definition_ptr && option_space_ != DHCP4_OPTION_SPACE &&
+        option_space_ != DHCP6_OPTION_SPACE) {
+        uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space_);
+        if (vendor_id > 0) {
+            option_definition_ptr = LibDHCP::getVendorOptionDef(
+                static_cast<Option::Universe>(option_universe_), vendor_id,
+                option_code_);
+        }
+    }
+
+    // In all other cases, we use runtime option definitions, which
+    // should be also registered within the libdhcp++.
+    if (!option_definition_ptr) {
+        option_definition_ptr =
+            LibDHCP::getRuntimeOptionDef(option_space_, option_code_);
+    }
+
+    OptionPtr option;
+    if (!option_definition_ptr) {
+        // If no definition found, we use generic option type.
+        OptionBuffer option_buffer(option_value_.begin(), option_value_.end());
+        option.reset(new Option(static_cast<Option::Universe>(option_universe_),
+                                static_cast<uint16_t>(option_code_),
+                                option_buffer.begin(), option_buffer.end()));
+    } else {
+        // The option value may be specified in textual or binary format in the
+        // database. If formatted_value is empty, the binary format is used.
+        // Depending on the format we use a different variant of @ref
+        // optionFactory().
+        if (option_formatted_value_.empty()) {
+            OptionBuffer option_buffer(option_value_.begin(),
+                                       option_value_.end());
+            option = option_definition_ptr->optionFactory(
+                static_cast<Option::Universe>(option_universe_),
+                static_cast<uint16_t>(option_code_), option_buffer.begin(),
+                option_buffer.end());
+        } else {
+            // Spit the value specified in comma separated values format.
+            std::vector<std::string> split_vector;
+            boost::split(split_vector, option_formatted_value_,
+                         boost::is_any_of(","));
+            option = option_definition_ptr->optionFactory(
+                static_cast<Option::Universe>(option_universe_),
+                static_cast<uint16_t>(option_code_), split_vector);
+        }
+    }
+
+    return OptionDescriptorPtr(new OptionDescriptor(
+        option, option_is_persistent_, option_formatted_value_));
+}
+
+/// @brief Implementation of the @ref CqlHostDataSource.
+class CqlHostDataSourceImpl {
+public:
+    /// @brief Constructor.
+    ///
+    /// This constructor opens database connection and initializes prepared
+    /// statements used in the queries.
+    explicit CqlHostDataSourceImpl(
+        const CqlConnection::ParameterMap& parameters);
+
+    /// @brief Destructor.
+    virtual ~CqlHostDataSourceImpl();
+
+    /// @brief Implementation of @ref CqlHostDataSource::add()
+    virtual void add(const HostPtr& host);
+
+    /// @brief Implementation of @ref CqlHostDataSource::get4()
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const asiolink::IOAddress& address) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get4()
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const HWAddrPtr& hwaddr,
+                              const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get4()
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const Host::IdentifierType& identifier_type,
+                              const uint8_t* identifier_begin,
+                              const size_t identifier_len) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get6()
+    virtual ConstHostPtr get6(const asiolink::IOAddress& prefix,
+                              const uint8_t prefix_len) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get6()
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const DuidPtr& duid,
+                              const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get6()
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const Host::IdentifierType& identifier_type,
+                              const uint8_t* identifier_begin,
+                              const size_t identifier_len) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get6()
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const asiolink::IOAddress& address) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::getAll
+    virtual ConstHostCollection getAll(const HWAddrPtr& hwaddr,
+                                       const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::getAll()
+    virtual ConstHostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin,
+           const size_t identifier_len) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::getAll4()
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::getName()
+    virtual std::string getName() const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::getVersion()
+    virtual VersionPair getVersion() const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::commit()
+    virtual void commit();
+
+    /// @brief Implementation of @ref CqlHostDataSource::rollback()
+    virtual void rollback();
+
+protected:
+    /// @brief Adds any options found in the @ref Host object to a separate
+    ///     table entry.
+    ///
+    /// @param host @ref Host object from which options are retrieved and
+    ///     inserted into the Cassandra database
+    /// @param reservation reservation for the current denormalized table entry
+    /// @param option_spaces list of option spaces to search for
+    /// @param cfg_option option configuration used to match option spaces in
+    ///     order to obtain actual options
+    virtual void insertHostWithOptions(
+        const HostPtr& host,
+        const IPv6Resrv* const reservation = NULL,
+        const std::list<std::string>& option_spaces = std::list<std::string>(),
+        const ConstCfgOptionPtr cfg_option = ConstCfgOptionPtr());
+
+    /// @brief Adds any reservations found in the @ref Host object to a separate
+    ///     table entry.
+    ///
+    /// @param host @ref Host object from which reservations are retrieved and
+    ///     inserted into the Cassandra database
+    /// @param reservation reservation for the current denormalized table entry
+    /// @param option_spaces4 list of option spaces for universe Option::V4 to
+    ///     search in
+    /// @param cfg_option4 option configuration for universe Option::V4 used to
+    ///     match option spaces in order to obtain actual options
+    /// @param option_spaces6 list of option spaces for universe Option::V6 to
+    ///     search in
+    /// @param cfg_option6 option configuration for universe Option::V6 used to
+    ///     match option spaces in order to obtain actual options
+    virtual void
+    insertHostWithReservations(const HostPtr& host,
+                               const IPv6Resrv* const reservation,
+                               const std::list<std::string>& option_spaces4,
+                               const ConstCfgOptionPtr cfg_option4,
+                               const std::list<std::string>& option_spaces6,
+                               const ConstCfgOptionPtr cfg_option6);
+
+    /// @brief Retrieves a single host.
+    ///
+    /// Calls @ref getHostCollection() and checks if a single host was returned.
+    ///
+    /// @param where_values array of bound objects used to filter the results
+    /// @param statement_index prepared statement being executed
+    ///
+    /// @return one host or a null pointer to a host
+    ///
+    /// @throw MultipleRecords exception if two or more hosts are returned
+    virtual ConstHostPtr
+    getHost(const CqlDataArray& where_values,
+            const CqlHostExchange::StatementIndex statement_index) const;
+
+    /// @brief Retrieves a collection of hosts.
+    ///
+    /// Calls @ref CqlExchange::executeRead().
+    ///
+    /// @param where_values array of bound objects used to filter the results
+    /// @param statement_index prepared statement being executed
+    ///
+    /// @return a collection of hosts containing one or more hosts
+    virtual ConstHostCollection getHostCollection(
+        const CqlDataArray& where_values,
+        const CqlHostExchange::StatementIndex statement_index) const;
+
+    /// @brief Inserts a single host.
+    ///
+    /// All information is available here. Calls @ref
+    /// CqlExchange::executeWrite().
+    ///
+    /// @param host @ref Host object from which options are retrieved and
+    ///     inserted into the Cassandra database
+    /// @param subnet_id identifier of the subnet to which the host belongs
+    /// @param reservation reservation for the current denormalized table entry
+    /// @param option_space option space for the current denormalized table
+    ///     entry's option
+    /// @param option_descriptor option descriptor containing information for
+    ///     the current denormalized table entry's option
+    virtual void insertHost(
+        const HostPtr& host,
+        const OptionalValue<SubnetID>& subnet_id = OptionalValue<SubnetID>(),
+        const IPv6Resrv* const reservation = NULL,
+        const std::string& option_space = NULL_OPTION_SPACE,
+        const OptionDescriptor& option_descriptor = OptionDescriptor(false));
+
+    /// @brief Merge denormalized table entries that belong to the same host
+    /// into a single host, one by one.
+    ///
+    /// @param target_host host which can contain multiple reservations and
+    ///     options to which other distinct reservations and options are added.
+    /// @param source_host host that is being search for new reservations and
+    ///     options that will be merged into the old host.
+    virtual void mergeHosts(HostPtr& target_host,
+                            const ConstHostPtr& source_host) const;
+
+private:
+    /// @brief pointer to the exchange responsible for trading host information
+    boost::shared_ptr<CqlHostExchange> host_exchange_;
+    /// @brief pointer to the exchange responsible for retrieving schema version
+    boost::shared_ptr<CqlVersionExchange> version_exchange_;
+
+    /// @brief CQL connection
+    mutable CqlConnection dbconn_;
+};  // class CqlHostDataSourceImpl
+
+
+/// @brief hash function for HostMap
+///
+/// @param key being hashed
+///
+/// @return hash value
+std::size_t
+hash_value(const HostKey& key) {
+    // Get key.
+    std::stringstream key_stream;
+    HostIdentifier host_identifier = key.get<0>();
+    key_stream << std::setw(DUID::MAX_DUID_LEN) << std::setfill('0')
+               << DUID(host_identifier).toText();
+    key_stream << std::setw(2) << std::setfill('0') << key.get<1>();
+    key_stream << std::setw(10) << std::setfill('0') << key.get<2>();
+    key_stream << std::setw(10) << std::setfill('0') << key.get<3>();
+    key_stream << std::setw(ADDRESS4_TEXT_MAX_LENGTH) << std::setfill('0')
+               << key.get<4>();
+    const std::string key_string = key_stream.str();
+
+    const uint64_t md5 = md5Hash(key_string);
+
+    return static_cast<std::size_t>(md5);
+}
+
+/// @brief equals operator for HostKey
+///
+/// @param key1 left hand side operand
+/// @param key2 right hand side operand
+///
+/// @return true if keys are equal. Deep comparison is made.
+bool
+operator==(const HostKey& key1, const HostKey& key2) {
+    return key1.get<0>() == key2.get<0>() && key1.get<1>() == key2.get<1>() &&
+           key1.get<2>() == key2.get<2>() && key1.get<3>() == key2.get<3>() &&
+           key1.get<4>() == key2.get<4>();
+}
+
+CqlHostDataSourceImpl::CqlHostDataSourceImpl(
+    const CqlConnection::ParameterMap& parameters)
+    : host_exchange_(new CqlHostExchange()),
+      version_exchange_(new CqlVersionExchange()), dbconn_(parameters) {
+    // Open the database.
+    dbconn_.openDatabase();
+    // Prepare all possible statements.
+    dbconn_.prepareStatements(tagged_statements);
+}
+
+CqlHostDataSourceImpl::~CqlHostDataSourceImpl() {
+    // There is no need to close the database in this destructor: it is
+    // closed in the destructor of the dbconn_ member variable.
+}
+
+void
+CqlHostDataSourceImpl::add(const HostPtr& host) {
+    // Start transaction.
+    CqlTransaction transaction(dbconn_);
+
+    // Get option space names and vendor space names and combine them within a
+    // single list.
+    // For IPv4:
+    ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
+    std::list<std::string> option_spaces4 = cfg_option4->getOptionSpaceNames();
+    std::list<std::string> vendor_spaces4 =
+        cfg_option4->getVendorIdsSpaceNames();
+    option_spaces4.insert(option_spaces4.end(), vendor_spaces4.begin(),
+                          vendor_spaces4.end());
+
+    // For IPv6:
+    ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
+    std::list<std::string> option_spaces6 = cfg_option6->getOptionSpaceNames();
+    std::list<std::string> vendor_spaces6 =
+        cfg_option6->getVendorIdsSpaceNames();
+    option_spaces6.insert(option_spaces6.end(), vendor_spaces6.begin(),
+                          vendor_spaces6.end());
+
+    // For every IPv6 reservation, add each of their options to the database.
+    IPv6ResrvRange reservations = host->getIPv6Reservations();
+    if (std::distance(reservations.first, reservations.second) > 0) {
+        for (IPv6ResrvIterator it = reservations.first;
+             it != reservations.second; ++it) {
+            insertHostWithReservations(host, &it->second, option_spaces4,
+                                       cfg_option4, option_spaces6,
+                                       cfg_option6);
+        }
+    } else {
+        // If host has no reservation, add entries with null reservation.
+        // Options could still be present.
+        insertHostWithReservations(host, NULL, option_spaces4, cfg_option4,
+                                   option_spaces6, cfg_option6);
+    }
+    transaction.commit();
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get4(const SubnetID& subnet_id,
+                            const asiolink::IOAddress& address) const {
+    if (!address.isV4()) {
+        isc_throw(BadValue, "CqlHostDataSource::get4(2): wrong address type, "
+                            "address supplied is not an IPv4 address");
+    }
+
+    // Convert to CQL data types.
+    cass_int32_t host_ipv4_subnet_id = static_cast<cass_int32_t>(subnet_id);
+    cass_int32_t host_ipv4_address =
+        static_cast<cass_int32_t>(address.toUint32());
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_ipv4_subnet_id));
+    where_values.add(reinterpret_cast<void*>(&host_ipv4_address));
+
+    // Run statement.
+    return getHost(*const_cast<const CqlDataArray*>(&where_values),
+                   CqlHostExchange::GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS);
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get4(const SubnetID& subnet_id,
+                            const HWAddrPtr& hwaddr,
+                            const DuidPtr& duid) const {
+    /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr,
+    /// duid)
+    if (hwaddr && duid) {
+        isc_throw(BadValue, "CqlHostDataSource::get4(3) called with both "
+                            "hwaddr and duid, only one of them is allowed");
+    } else if (!hwaddr && !duid) {
+        isc_throw(BadValue, "CqlHostDataSource::get4(3) called with neither "
+                            "hwaddr or duid specified, one of them is "
+                            "required");
+    } else if (duid) {
+        HostIdentifier duid_vector = duid->getDuid();
+        // Delegate to get4(4).
+        return get4(subnet_id, Host::IDENT_DUID, duid_vector.data(),
+                    duid_vector.size());
+    } else if (hwaddr) {
+        // Delegate to get4(4).
+        return get4(subnet_id, Host::IDENT_HWADDR, hwaddr->hwaddr_.data(),
+                    hwaddr->hwaddr_.size());
+    }
+    return ConstHostPtr();
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get4(const SubnetID& subnet_id,
+                            const Host::IdentifierType& identifier_type,
+                            const uint8_t* identifier_begin,
+                            const size_t identifier_len) const {
+    // Convert to CQL data types.
+    cass_int32_t host_ipv4_subnet_id = static_cast<cass_int32_t>(subnet_id);
+    CassHostIdentifier host_identifier(identifier_begin,
+                                       identifier_begin + identifier_len);
+    cass_int32_t host_identifier_type =
+        static_cast<cass_int32_t>(identifier_type);
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_ipv4_subnet_id));
+    where_values.add(reinterpret_cast<void*>(&host_identifier));
+    where_values.add(reinterpret_cast<void*>(&host_identifier_type));
+
+    // Run statement.
+    return getHost(*const_cast<const CqlDataArray*>(&where_values),
+                   CqlHostExchange::GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID);
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get6(const asiolink::IOAddress& prefix,
+                            const uint8_t prefix_len) const {
+    // Convert to CQL data types.
+    std::string reserved_ipv6_prefix_address = prefix.toText();
+    cass_int32_t reserved_ipv6_prefix_length = prefix_len;
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address));
+    where_values.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_length));
+
+    // Get host id.
+    ConstHostPtr host = getHost(*const_cast<const CqlDataArray*>(&where_values),
+                                CqlHostExchange::GET_HOST_BY_IPV6_PREFIX);
+
+    if (host == ConstHostPtr()) {
+        return ConstHostPtr();
+    }
+
+    // Get host.
+    HostIdentifier host_identifier = host->getIdentifier();
+    ConstHostCollection collection =
+        getAll(host->getIdentifierType(), host_identifier.data(),
+               host_identifier.size());
+
+    if (collection.empty()) {
+        return ConstHostPtr();
+    }
+
+    if (collection.size() >= 2U) {
+        isc_throw(
+            MultipleRecords,
+            "CqlHostDataSource::get6(2): multiple records were found in the "
+            "database where only one was expected for statement "
+                << dbconn_
+                       .tagged_statements_[CqlHostExchange::GET_HOST_BY_HOST_ID]
+                       .name_);
+    }
+
+    return *collection.begin();
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get6(const SubnetID& subnet_id,
+                            const DuidPtr& duid,
+                            const HWAddrPtr& hwaddr) const {
+    /// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr,
+    /// duid)
+    if (hwaddr && duid) {
+        isc_throw(BadValue, "CqlHostDataSource::get6(3): both hardware address "
+                            "and DUID are specified, only one of them is "
+                            "allowed");
+    } else if (!hwaddr && !duid) {
+        isc_throw(BadValue, "CqlHostDataSource::get6(3): both hardware address "
+                            "and DUID are specified, one of them is required");
+    } else if (duid) {
+        HostIdentifier duid_vector = duid->getDuid();
+        // Delegate to get6(4).
+        return get6(subnet_id, Host::IDENT_DUID, duid_vector.data(),
+                    duid_vector.size());
+    } else if (hwaddr) {
+        // Delegate to get6(4).
+        return get6(subnet_id, Host::IDENT_HWADDR, hwaddr->hwaddr_.data(),
+                    hwaddr->hwaddr_.size());
+    }
+    return ConstHostPtr();
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get6(const SubnetID& subnet_id,
+                            const Host::IdentifierType& identifier_type,
+                            const uint8_t* identifier_begin,
+                            const size_t identifier_len) const {
+    // Convert to CQL data types.
+    cass_int32_t host_ipv6_subnet_id = static_cast<cass_int32_t>(subnet_id);
+    CassHostIdentifier host_identifier(identifier_begin,
+                                   identifier_begin + identifier_len);
+    cass_int32_t host_identifier_type =
+        static_cast<cass_int32_t>(identifier_type);
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_ipv6_subnet_id));
+    where_values.add(reinterpret_cast<void*>(&host_identifier));
+    where_values.add(reinterpret_cast<void*>(&host_identifier_type));
+
+    // Run statement.
+    return getHost(*const_cast<const CqlDataArray*>(&where_values),
+                   CqlHostExchange::GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID);
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get6(const SubnetID& subnet_id,
+                            const IOAddress& address) const {
+    // Convert to CQL data types.
+    cass_int32_t host_ipv6_subnet_id = static_cast<cass_int32_t>(subnet_id);
+    std::string reserved_ipv6_prefix_address = address.toText();
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_ipv6_subnet_id));
+    where_values.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address));
+
+    // Run statement.
+    return getHost(*const_cast<const CqlDataArray*>(&where_values),
+                   CqlHostExchange::GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS);
+}
+
+ConstHostCollection
+CqlHostDataSourceImpl::getAll(const HWAddrPtr& hwaddr,
+                              const DuidPtr& duid) const {
+    if (!duid && !hwaddr) {
+        return ConstHostCollection();
+    }
+
+    // Convert to CQL data types.
+    CassHostIdentifier host_identifier;
+    cass_int32_t host_identifier_type;
+    if (duid) {
+        HostIdentifier duid_vector = duid->getDuid();
+        host_identifier =
+            CassHostIdentifier(duid_vector.begin(), duid_vector.end());
+        host_identifier_type = static_cast<cass_int32_t>(Host::IDENT_DUID);
+    } else if (hwaddr) {
+        host_identifier = CassHostIdentifier(hwaddr->hwaddr_.begin(),
+                                                   hwaddr->hwaddr_.end());
+        host_identifier_type = static_cast<cass_int32_t>(Host::IDENT_HWADDR);
+    }
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_identifier));
+    where_values.add(reinterpret_cast<void*>(&host_identifier_type));
+
+    // Run statement.
+    return getHostCollection(*const_cast<const CqlDataArray*>(&where_values),
+                             CqlHostExchange::GET_HOST_BY_HOST_ID);
+}
+
+ConstHostCollection
+CqlHostDataSourceImpl::getAll(const Host::IdentifierType& identifier_type,
+                              const uint8_t* identifier_begin,
+                              const size_t identifier_len) const {
+    // Convert to CQL data types.
+    CassHostIdentifier host_identifier(identifier_begin,
+                                             identifier_begin + identifier_len);
+    cass_int32_t host_identifier_type =
+        static_cast<cass_int32_t>(identifier_type);
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_identifier));
+    where_values.add(reinterpret_cast<void*>(&host_identifier_type));
+
+    // Run statement.
+    return getHostCollection(*const_cast<const CqlDataArray*>(&where_values),
+                             CqlHostExchange::GET_HOST_BY_HOST_ID);
+}
+
+ConstHostCollection
+CqlHostDataSourceImpl::getAll4(const asiolink::IOAddress& address) const {
+    // Convert to CQL data types.
+    cass_int32_t host_ipv4_address =
+        static_cast<cass_int32_t>(address.toUint32());
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_ipv4_address));
+
+    // Run statement.
+    return getHostCollection(*const_cast<const CqlDataArray*>(&where_values),
+                             CqlHostExchange::GET_HOST_BY_IPV4_ADDRESS);
+}
+
+std::string
+CqlHostDataSourceImpl::getName() const {
+    std::string name = "";
+    try {
+        name = dbconn_.getParameter("name");
+    } catch (...) {
+        // Return an empty name
+    }
+    return name;
+}
+
+VersionPair
+CqlHostDataSourceImpl::getVersion() const {
+    return version_exchange_->retrieveVersion(dbconn_,
+                                              CqlHostExchange::GET_VERSION);
+}
+
+void
+CqlHostDataSourceImpl::commit() {
+    dbconn_.commit();
+}
+
+void
+CqlHostDataSourceImpl::rollback() {
+    dbconn_.rollback();
+}
+
+void
+CqlHostDataSourceImpl::insertHostWithOptions(
+    const HostPtr& host,
+    const IPv6Resrv* const reservation /* = NULL */,
+    const std::list<std::string>& option_spaces
+    /* = std::list<std::string>() */,
+    const ConstCfgOptionPtr cfg_option /* = ConstCfgOptionPtr() */) {
+    // For each option space retrieve all options and insert them into the
+    // database.
+    bool option_found = false;
+    for (std::list<std::string>::const_iterator space = option_spaces.begin();
+         space != option_spaces.end(); ++space) {
+        OptionContainerPtr options = cfg_option->getAll(*space);
+        if (options && !options->empty()) {
+            for (OptionContainer::const_iterator option = options->begin();
+                 option != options->end(); ++option) {
+                option_found = true;
+                insertHost(host, OptionalValue<SubnetID>(), reservation, *space,
+                           *option);
+            }
+        }
+    }
+    if (!option_found) {
+        insertHost(host, OptionalValue<SubnetID>(), reservation);
+    }
+}
+
+void
+CqlHostDataSourceImpl::insertHostWithReservations(
+    const HostPtr& host,
+    const IPv6Resrv* const reservation,
+    const std::list<std::string>& option_spaces4,
+    const ConstCfgOptionPtr cfg_option4,
+    const std::list<std::string>& option_spaces6,
+    const ConstCfgOptionPtr cfg_option6) {
+    // If host has no reservation, add entries with null reservation.
+    // Options could still be present.
+    if (cfg_option4 && !cfg_option4->empty()) {
+        insertHostWithOptions(host, reservation, option_spaces4, cfg_option4);
+    }
+    if (cfg_option6 && !cfg_option6->empty()) {
+        insertHostWithOptions(host, reservation, option_spaces6, cfg_option6);
+    }
+    if ((!cfg_option4 || cfg_option4->empty()) &&
+        (!cfg_option6 || cfg_option6->empty())) {
+        insertHostWithOptions(host, reservation);
+    }
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::getHost(
+    const CqlDataArray& where_values,
+    const CqlHostExchange::StatementIndex statement_index) const {
+    ConstHostCollection collection =
+        getHostCollection(where_values, statement_index);
+
+    if (collection.empty()) {
+        return ConstHostPtr();
+    }
+
+    if (collection.size() >= 2U) {
+        isc_throw(MultipleRecords,
+                  "multiple records were found in the database where only one "
+                  "was expected for statement "
+                      << dbconn_.tagged_statements_[statement_index].name_);
+    }
+
+    return *collection.begin();
+}
+
+ConstHostCollection
+CqlHostDataSourceImpl::getHostCollection(
+    const CqlDataArray& where_values,
+    const CqlHostExchange::StatementIndex statement_index) const {
+
+    // Run statement.
+    CqlDataArray collection = host_exchange_->executeRead(
+        dbconn_, where_values, statement_index, false);
+
+    // Form HostPtr objects.
+    HostCollection host_collection;
+    for (std::vector<void*>::const_iterator it = collection.begin();
+         it != collection.end(); ++it) {
+        host_collection.push_back(HostPtr(reinterpret_cast<Host*>(*it)));
+    }
+
+    // Merge the denormalized table entries that belong to the same host into a
+    // single host.
+    HostMap map;
+    for (HostCollection::iterator it = host_collection.begin();
+         it != host_collection.end(); ++it) {
+        HostKey key =
+            HostKey((*it)->getIdentifier(), (*it)->getIdentifierType(),
+                    (*it)->getIPv4SubnetID(), (*it)->getIPv6SubnetID(),
+                    (*it)->getIPv4Reservation());
+        if (map.find(key) == map.end()) {
+            map[key] = *it;
+        } else {
+            mergeHosts(map[key], *it);
+        }
+    }
+
+    ConstHostCollection result_collection;
+    for (HostMap::const_iterator it = map.begin(); it != map.end(); ++it) {
+        result_collection.push_back(it->second);
+    }
+    return result_collection;
+}
+
+void
+CqlHostDataSourceImpl::insertHost(
+    const HostPtr& host,
+    const OptionalValue<SubnetID>& subnet_id
+    /* = OptionalValue<SubnetID>() */,
+    const IPv6Resrv* const reservation /* = NULL */,
+    const std::string& option_space /* = NULL_OPTION_SPACE */,
+    const OptionDescriptor& option_descriptor
+    /* = OptionDescriptorPtr */) {
+    CqlDataArray assigned_values;
+    // @todo: Assign actual value to subnet id.
+    host_exchange_->createBindForSend(host, subnet_id, reservation,
+                                      option_space, option_descriptor,
+                                      assigned_values);
+    host_exchange_->executeWrite(dbconn_, assigned_values,
+                                 CqlHostExchange::INSERT_HOST);
+}
+
+void
+CqlHostDataSourceImpl::mergeHosts(HostPtr& target_host,
+                                  const ConstHostPtr& source_host) const {
+    // Merge reservations.
+    const IPv6ResrvRange reservations_range =
+        source_host->getIPv6Reservations();
+    if (std::distance(reservations_range.first, reservations_range.second) >
+        0) {
+        for (IPv6ResrvIterator reservations_iterator = reservations_range.first;
+             reservations_iterator != reservations_range.second;
+             ++reservations_iterator) {
+            if (!target_host->hasReservation(reservations_iterator->second)) {
+                target_host->addReservation(reservations_iterator->second);
+            }
+        }
+    }
+
+    // Merge DHCPv4 options.
+    source_host->getCfgOption4()->mergeTo(*target_host->getCfgOption4());
+
+    // Merge DHCPv6 options.
+    source_host->getCfgOption6()->mergeTo(*target_host->getCfgOption6());
+}
+
+CqlHostDataSource::CqlHostDataSource(
+    const CqlConnection::ParameterMap& parameters)
+    : impl_(new CqlHostDataSourceImpl(parameters)) {
+}
+
+CqlHostDataSource::~CqlHostDataSource() {
+    delete impl_;
+}
+
+void
+CqlHostDataSource::add(const HostPtr& host) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_ADD);
+
+    impl_->add(host);
+}
+
+ConstHostCollection
+CqlHostDataSource::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_HOST_GET_ALL);
+
+    return impl_->getAll(hwaddr, duid);
+}
+
+ConstHostCollection
+CqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier_begin,
+                          const size_t identifier_len) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_HOST_GET_ALL);
+
+    return impl_->getAll(identifier_type, identifier_begin, identifier_len);
+}
+
+ConstHostCollection
+CqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_HOST_GET_ALL);
+
+    return impl_->getAll4(address);
+}
+
+ConstHostPtr
+CqlHostDataSource::get4(const SubnetID& subnet_id,
+                        const HWAddrPtr& hwaddr,
+                        const DuidPtr& duid) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET4);
+
+    return impl_->get4(subnet_id, hwaddr, duid);
+}
+
+ConstHostPtr
+CqlHostDataSource::get4(const SubnetID& subnet_id,
+                        const Host::IdentifierType& identifier_type,
+                        const uint8_t* identifier_begin,
+                        const size_t identifier_len) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET4);
+
+    return impl_->get4(subnet_id, identifier_type, identifier_begin,
+                       identifier_len);
+}
+
+ConstHostPtr
+CqlHostDataSource::get4(const SubnetID& subnet_id,
+                        const asiolink::IOAddress& address) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET4);
+
+    return impl_->get4(subnet_id, address);
+}
+
+ConstHostPtr
+CqlHostDataSource::get6(const SubnetID& subnet_id,
+                        const DuidPtr& duid,
+                        const HWAddrPtr& hwaddr) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET6);
+
+    return impl_->get6(subnet_id, duid, hwaddr);
+}
+
+ConstHostPtr
+CqlHostDataSource::get6(const SubnetID& subnet_id,
+                        const Host::IdentifierType& identifier_type,
+                        const uint8_t* identifier_begin,
+                        const size_t identifier_len) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET6);
+
+    return impl_->get6(subnet_id, identifier_type, identifier_begin,
+                       identifier_len);
+}
+
+ConstHostPtr
+CqlHostDataSource::get6(const asiolink::IOAddress& prefix,
+                        const uint8_t prefix_len) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET6);
+
+    return impl_->get6(prefix, prefix_len);
+}
+
+ConstHostPtr
+CqlHostDataSource::get6(const SubnetID& subnet_id,
+                        const asiolink::IOAddress& address) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET6);
+
+    return impl_->get6(subnet_id, address);
+}
+
+std::string
+CqlHostDataSource::getType() const {
+    return std::string("cql");
+}
+
+std::string
+CqlHostDataSource::getName() const {
+    return impl_->getName();
+}
+
+std::string
+CqlHostDataSource::getDescription() const {
+    return std::string(
+        "Host data source that stores host information in the CQL database");
+}
+
+VersionPair
+CqlHostDataSource::getVersion() const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_HOST_DB_GET_VERSION);
+
+    return impl_->getVersion();
+}
+
+void
+CqlHostDataSource::commit() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_COMMIT);
+
+    impl_->commit();
+}
+
+void
+CqlHostDataSource::rollback() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_ROLLBACK);
+
+    impl_->rollback();
+}
+
+};  // namespace dhcp
+};  // namespace isc
diff --git a/src/lib/dhcpsrv/cql_host_data_source.h b/src/lib/dhcpsrv/cql_host_data_source.h
new file mode 100644 (file)
index 0000000..e22e1ed
--- /dev/null
@@ -0,0 +1,291 @@
+// Copyright (C) 2016 Deutsche Telekom AG.
+//
+// Author: Andrei Pavel <andrei.pavel@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 CQL_HOST_DATA_SOURCE_H
+#define CQL_HOST_DATA_SOURCE_H
+
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/cql_connection.h>
+
+#include <stdint.h>
+
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+
+/// Forward declaration to the implementation of @ref CqlHostDataSource.
+class CqlHostDataSourceImpl;
+
+/// @brief Cassandra host data source
+///
+/// Implements @ref isc::dhcp::BaseHostDataSource interface customized to
+/// Cassandra. Use of this backend implies that a Cassandra database is
+/// available and that the Kea schema has been created within it.
+class CqlHostDataSource : public BaseHostDataSource {
+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.
+    ///
+    /// Finally, all the CQL commands are pre-compiled.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    ///        concerned with the database.
+    ///
+    /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
+    /// @throw isc::dhcp::DbOpenError Error opening the database
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    explicit CqlHostDataSource(
+        const DatabaseConnection::ParameterMap& parameters);
+
+    /// @brief Virtual destructor.
+    ///
+    /// Releases prepared CQL statements used by the backend.
+    virtual ~CqlHostDataSource();
+
+    /// @brief Adds a new @ref Host to the Cassandra database along with it's
+    ///     reservations and options.
+    ///
+    /// The implementations of this method should guard against duplicate
+    /// reservations for the same @ref Host, where possible. For example, when
+    /// the reservation for the same @ref HWAddr and @ref SubnetID is added
+    /// twice, @ref add() should throw a @ref DuplicateEntry exception. Note,
+    /// that usually it is impossible to guard against adding duplicated @ref
+    /// Host, where one instance is identified by @ref HWAddr, another one by
+    /// @ref DUID.
+    ///
+    /// @param host pointer to the new @ref Host being added.
+    virtual void add(const HostPtr& host);
+
+    /// @brief Retrieves a single @ref Host connected to an IPv4 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// multiple instances of the @ref Host are present, e.g. when two @ref
+    /// Host objects are found, one for the @ref DUID, another one for the @ref
+    /// HWAddr. In such case, throw a @ref MultipleRecords exception.
+    ///
+    /// @param subnet_id subnet identifier to filter by
+    /// @param hwaddr hardware address of the client to filter by or NULL if not
+    ///     available
+    /// @param duid client identifier to filter by or NULL if not available
+    ///
+    /// @return @ref ConstHostPtr to a @ref Host object using a specified @ref
+    ///     HWAddr or @ref DUID
+    ///
+    /// @throw BadValue if both or neither of subnet_id and duid are specified
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const HWAddrPtr& hwaddr,
+                              const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Retrieves a @ref Host connected to an IPv4 subnet.
+    ///
+    /// @param subnet_id subnet identifier to filter by
+    /// @param identifier_type identifier type to filter by
+    /// @param identifier_begin pointer to the beginning of a buffer containing a
+    ///     host identifier to filter by
+    /// @param identifier_len length of the host identifier buffer
+    ///
+    /// @return @ref Host object for which a reservation has been made using the
+    ///     specified identifier
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const Host::IdentifierType& identifier_type,
+                              const uint8_t* identifier_begin,
+                              const size_t identifier_len) const;
+
+    /// @brief Retrieves a @ref Host connected to an IPv4 subnet.
+    ///
+    /// Note that dynamically allocated addresses and reserved addresses can
+    /// come into conflict. When the new address is assigned to a client, the
+    /// allocation mechanism should check if this address is not reserved for
+    /// some other @ref Host and not allocate it if a reservation is present.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param address reserved IPv4 address.
+    ///
+    /// @return Const @ref Host object
+    ///
+    /// @throw BadValue if address in not a valid IPv4address
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const asiolink::IOAddress& address) const;
+
+    /// @brief Retrieves a @ref Host connected to an IPv6 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// multiple instances of the @ref Host are present, e.g. when two
+    /// @ref Host objects are found, one for the @ref DUID, another one for the
+    /// @ref HWAddr. In such case, throw a @ref MultipleRecords exception.
+    ///
+    /// @param subnet_id subnet identifier to filter by
+    /// @param hwaddr hardware address of the client to filter by or NULL if not
+    ///     available
+    /// @param duid client identifier to filter by or NULL if not available
+    ///
+    /// @return @ref Host object using a specified @ref HWAddr or @ref DUID
+    ///
+    /// @throw BadValue if both or neither of subnet_id and duid are specified
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const DuidPtr& duid,
+                              const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+    /// @brief Returns a @ref Host connected to an IPv6 subnet.
+    ///
+    /// @param subnet_id subnet identifier to filter by
+    /// @param identifier_type identifier type to filter by
+    /// @param identifier_begin pointer to the beginning of a buffer containing a
+    ///     host identifier to filter by
+    /// @param identifier_len length of the host identifier buffer
+    ///
+    /// @return Const @ref Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const Host::IdentifierType& identifier_type,
+                              const uint8_t* identifier_begin,
+                              const size_t identifier_len) const;
+
+    /// @brief Returns a @ref Host with the specified reservation prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @ref Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Const @ref Host object using a specified HW address or DUID.
+    ///
+    /// @throw MultipleRecords if two or more rows are returned from the
+    ///     Cassandra database
+    virtual ConstHostPtr get6(const asiolink::IOAddress& prefix,
+                              const uint8_t prefix_len) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet and having
+    /// a reservation for a specified IPv6 address or prefix.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param address reserved IPv6 address/prefix.
+    ///
+    /// @return Const @c Host object using a specified IPv6 address/prefix.
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const asiolink::IOAddress& address) const;
+
+    /// @brief Return all @ref Host objects for the specified @ref HWAddr or
+    /// @ref DUID.
+    ///
+    /// Returns all @ref Host objects which represent reservations
+    /// for the specified HW address or DUID. Note, that this method may
+    /// return multiple reservations because a particular client may have
+    /// reservations in multiple subnets and the same client may be identified
+    /// by HW address or DUID. The server is unable to verify that the specific
+    /// DUID and HW address belong to the same client, until the client sends
+    /// a DHCP message.
+    ///
+    /// Specifying both @ref HWAddr and @ref DUID is allowed for this method
+    /// and results in returning all objects that are associated with hardware
+    /// address OR duid. For example: if one @ref Host is associated with the
+    /// specified @ref HWAddr and another @ref Host is associated with the
+    /// specified @ref DUID, two hosts will be returned.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return collection of const @ref Host objects.
+    virtual ConstHostCollection getAll(const HWAddrPtr& hwaddr,
+                                       const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Return all hosts connected to any subnet for which reservations
+    /// have been made using a specified identifier.
+    ///
+    /// This method returns all @ref Host objects which represent reservations
+    /// for a specified identifier. This method may return multiple hosts
+    /// because a particular client may have reservations in multiple subnets.
+    ///
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a beginning of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Collection of const @ref Host objects.
+    virtual ConstHostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin,
+           const size_t identifier_len) const;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @ref Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @ref Host object is searched.
+    ///
+    /// @return Collection of const @ref Host objects.
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const;
+
+    /// @brief Returns description of the backend.
+    ///
+    /// This description may be multiline text that describes the backend.
+    ///
+    /// @return Description of the backend.
+    virtual std::string getDescription() const;
+
+    /// @brief Returns the name of the database.
+    ///
+    /// @return database name
+    virtual std::string getName() const;
+
+    /// @brief Return backend type
+    ///
+    /// @return backend type "cql"
+    virtual std::string getType() const;
+
+    /// @brief Retrieves schema version.
+    ///
+    /// @return Version number stored in the database, as a pair of unsigned
+    ///         integers. "first" is the major version number, "second" is the
+    ///         minor version number.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database
+    ///        has failed.
+    virtual std::pair<uint32_t, uint32_t> getVersion() const;
+
+    /// @brief Commit Transactions
+    ///
+    /// Commits all pending database operations.
+    virtual void commit();
+
+    /// @brief Rollback Transactions
+    ///
+    /// Rolls back all pending database operations.
+    virtual void rollback();
+
+private:
+    /// @brief Pointer to the implementation of the @ref CqlHostDataSource.
+    CqlHostDataSourceImpl* impl_;
+};  // class CqlHostDataSource
+
+};  // namespace dhcp
+};  // namespace isc
+
+#endif  // CQL_HOST_DATA_SOURCE_H
index f7486c6054cfead2769a0502f00026332f3d86d4..7175dd23eae24beeba7099aa74afb51336dbbf10 100644 (file)
 // limitations under the License.
 
 #include <config.h>
+
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
-#include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/cql_lease_mgr.h>
-#include <boost/static_assert.hpp>
-#include <iostream>
-#include <iomanip>
-#include <sstream>
-#include <string>
-#include <ctime>
+#include <dhcpsrv/dhcpsrv_log.h>
+
+#include <limits>
 
 using namespace isc;
 using namespace isc::dhcp;
@@ -37,247 +34,158 @@ namespace dhcp {
 static const size_t HOSTNAME_MAX_LEN = 255U;
 static const size_t ADDRESS6_TEXT_MAX_LEN = 39U;
 
-/// @name CqlBind auxiliary methods for binding data into Cassandra format:
 /// @{
-
-/// @todo These void* cast are unsafe. See ticket #4525.
-static CassError CqlBindNone(CassStatement* statement, size_t index, void*) {
-    return cass_statement_bind_null(statement, index);
-}
-
-static CassError CqlBindBool(CassStatement* statement, size_t index,
-        void* value) {
-    return cass_statement_bind_bool(statement, index,
-        *(static_cast<cass_bool_t*>(value)));
-}
-
-static CassError CqlBindInt32(CassStatement* statement, size_t index,
-        void* value) {
-    return cass_statement_bind_int32(statement, index,
-        *(static_cast<cass_int32_t*>(value)));
-}
-
-static CassError CqlBindInt64(CassStatement* statement, size_t index,
-        void* value) {
-    return cass_statement_bind_int64(statement, index,
-        *(static_cast<cass_int64_t*>(value)));
-}
-
-static CassError CqlBindTimestamp(CassStatement* statement, size_t index,
-        void* value) {
-    return cass_statement_bind_int64(statement, index,
-        *(static_cast<cass_int64_t*>(value)));
-}
-
-static CassError CqlBindString(CassStatement* statement, size_t index,
-        void* value) {
-    return cass_statement_bind_string(statement, index,
-        (static_cast<const char*>(value)));
-}
-
-static CassError CqlBindBytes(CassStatement* statement, size_t index,
-        void* value) {
-    return cass_statement_bind_bytes(statement, index,
-        static_cast<std::vector<cass_byte_t>*>(value)->data(),
-        static_cast<std::vector<cass_byte_t>*>(value)->size());
-}
-/// @}
-
-static CassError CqlGetNone(const CassValue*, void*, size_t*) {
-    return CASS_OK;
-}
-
-static CassError CqlGetBool(const CassValue* value, void* data, size_t*) {
-    return cass_value_get_bool(value, static_cast<cass_bool_t*>(data));
-}
-
-static CassError CqlGetInt32(const CassValue* value, void* data, size_t*) {
-    return cass_value_get_int32(value, static_cast<cass_int32_t*>(data));
-}
-
-static CassError CqlGetInt64(const CassValue* value, void* data, size_t*) {
-    return cass_value_get_int64(value, static_cast<cass_int64_t*>(data));
-}
-
-static CassError CqlGetTimestamp(const CassValue* value, void* data, size_t*) {
-    return cass_value_get_int64(value, static_cast<cass_int64_t*>(data));
-}
-
-static CassError CqlGetString(const CassValue* value, void* data,
-        size_t* size) {
-    return cass_value_get_string(value, static_cast<const char**>(data), size);
-}
-
-static CassError CqlGetBytes(const CassValue* value, void* data, size_t* size) {
-    return cass_value_get_bytes(value, static_cast<const cass_byte_t**>(data),
-        size);
-}
-
-typedef CassError (*CqlBindFunction)(CassStatement* statement, size_t index,
-        void* value);
-typedef CassError (*CqlGetFunction)(const CassValue* value, void* data,
-        size_t* size);
-
-struct CqlFunctionData {
-    CqlBindFunction sqlBindFunction_;
-    CqlGetFunction sqlGetFunction_;
-};
-
-static struct CqlFunctionData CqlFunctions[] = {
-    {CqlBindNone, CqlGetNone},
-    {CqlBindBool, CqlGetBool},
-    {CqlBindInt32, CqlGetInt32},
-    {CqlBindInt64, CqlGetInt64},
-    {CqlBindTimestamp, CqlGetTimestamp},
-    {CqlBindString, CqlGetString},
-    {CqlBindBytes, CqlGetBytes}
-};
-
-/// @brief Catalog of all the SQL statements currently supported. Note
+///
+/// @brief Catalog of all the CQL statements currently supported. Note
 /// that the order columns appear in statement body must match the order they
 /// that the occur in the table. This does not apply to the where clause.
-
 static const char* delete_lease4_params[] = {
-        static_cast<const char*>("address"),
-        NULL };
+    static_cast<const char*>("address"),
+    NULL };
 static const char* delete_expired_lease4_params[] = {
-        static_cast<const char*>("state"),
-        static_cast<const char*>("expire"),
-        NULL };
+    static_cast<const char*>("state"),
+    static_cast<const char*>("expire"),
+    NULL };
 static const char* delete_lease6_params[] = {
-        static_cast<const char*>("address"),
-        NULL };
+    static_cast<const char*>("address"),
+    NULL };
 static const char* delete_expired_lease6_params[] = {
-        static_cast<const char*>("state"),
-        static_cast<const char*>("expire"),
-        NULL };
+    static_cast<const char*>("state"),
+    static_cast<const char*>("expire"),
+    NULL };
 static const char* get_lease4_addr_params[] = {
-        static_cast<const char*>("address"),
-        NULL };
+    static_cast<const char*>("address"),
+    NULL };
 static const char* get_lease4_clientid_params[] = {
-        static_cast<const char*>("client_id"),
-        NULL };
+    static_cast<const char*>("client_id"),
+    NULL };
 static const char* get_lease4_clientid_subid_params[] = {
-        static_cast<const char*>("client_id"),
-        static_cast<const char*>("subnet_id"),
-        NULL };
+    static_cast<const char*>("client_id"),
+    static_cast<const char*>("subnet_id"),
+    NULL };
 static const char* get_lease4_hwaddr_params[] = {
-        static_cast<const char*>("hwaddr"),
-        NULL };
+    static_cast<const char*>("hwaddr"),
+    NULL };
 static const char* get_lease4_hwaddr_subid_params[] = {
-        static_cast<const char*>("hwaddr"),
-        static_cast<const char*>("subnet_id"),
-        NULL };
+    static_cast<const char*>("hwaddr"),
+    static_cast<const char*>("subnet_id"),
+    NULL };
 static const char* get_lease4_expired_params[] = {
-        static_cast<const char*>("state"),
-        static_cast<const char*>("expire"),
-        static_cast<const char*>("limit"),
-        NULL };
+    static_cast<const char*>("state"),
+    static_cast<const char*>("expire"),
+    static_cast<const char*>("limit"),
+    NULL };
 static const char* get_lease6_addr_params[] = {
-        static_cast<const char*>("address"),
-        static_cast<const char*>("lease_type"),
-        NULL };
+    static_cast<const char*>("address"),
+    static_cast<const char*>("lease_type"),
+    NULL };
 static const char* get_lease6_duid_iaid_params[] = {
-        static_cast<const char*>("duid"),
-        static_cast<const char*>("iaid"),
-        static_cast<const char*>("lease_type"),
-        NULL };
+    static_cast<const char*>("duid"),
+    static_cast<const char*>("iaid"),
+    static_cast<const char*>("lease_type"),
+    NULL };
 static const char* get_lease6_duid_iaid_subid_params[] = {
-        static_cast<const char*>("duid"),
-        static_cast<const char*>("iaid"),
-        static_cast<const char*>("subnet_id"),
-        static_cast<const char*>("lease_type"),
-        NULL };
+    static_cast<const char*>("duid"),
+    static_cast<const char*>("iaid"),
+    static_cast<const char*>("subnet_id"),
+    static_cast<const char*>("lease_type"),
+    NULL };
 static const char* get_lease6_expired_params[] = {
-        static_cast<const char*>("state"),
-        static_cast<const char*>("expire"),
-        static_cast<const char*>("limit"),
-        NULL };
+    static_cast<const char*>("state"),
+    static_cast<const char*>("expire"),
+    static_cast<const char*>("limit"),
+    NULL };
 static const char* get_version_params[] = {
-        NULL };
+    NULL };
 static const char* insert_lease4_params[] = {
-        static_cast<const char*>("address"),
-        static_cast<const char*>("hwaddr"),
-        static_cast<const char*>("client_id"),
-        static_cast<const char*>("valid_lifetime"),
-        static_cast<const char*>("expire"),
-        static_cast<const char*>("subnet_id"),
-        static_cast<const char*>("fqdn_fwd"),
-        static_cast<const char*>("fqdn_rev"),
-        static_cast<const char*>("hostname"),
-        static_cast<const char*>("state"),
-        NULL };
+    static_cast<const char*>("address"),
+    static_cast<const char*>("hwaddr"),
+    static_cast<const char*>("client_id"),
+    static_cast<const char*>("valid_lifetime"),
+    static_cast<const char*>("expire"),
+    static_cast<const char*>("subnet_id"),
+    static_cast<const char*>("fqdn_fwd"),
+    static_cast<const char*>("fqdn_rev"),
+    static_cast<const char*>("hostname"),
+    static_cast<const char*>("state"),
+    NULL };
 static const char* insert_lease6_params[] = {
-        static_cast<const char*>("address"),
-        static_cast<const char*>("duid"),
-        static_cast<const char*>("valid_lifetime"),
-        static_cast<const char*>("expire"),
-        static_cast<const char*>("subnet_id"),
-        static_cast<const char*>("pref_lifetime"),
-        static_cast<const char*>("lease_type"),
-        static_cast<const char*>("iaid"),
-        static_cast<const char*>("prefix_len"),
-        static_cast<const char*>("fqdn_fwd"),
-        static_cast<const char*>("fqdn_rev"),
-        static_cast<const char*>("hostname"),
-        static_cast<const char*>("hwaddr"),
-        static_cast<const char*>("hwtype"),
-        static_cast<const char*>("hwaddr_source"),
-        static_cast<const char*>("state"),
-        NULL };
+    static_cast<const char*>("address"),
+    static_cast<const char*>("duid"),
+    static_cast<const char*>("valid_lifetime"),
+    static_cast<const char*>("expire"),
+    static_cast<const char*>("subnet_id"),
+    static_cast<const char*>("pref_lifetime"),
+    static_cast<const char*>("lease_type"),
+    static_cast<const char*>("iaid"),
+    static_cast<const char*>("prefix_len"),
+    static_cast<const char*>("fqdn_fwd"),
+    static_cast<const char*>("fqdn_rev"),
+    static_cast<const char*>("hostname"),
+    static_cast<const char*>("hwaddr"),
+    static_cast<const char*>("hwtype"),
+    static_cast<const char*>("hwaddr_source"),
+    static_cast<const char*>("state"),
+    NULL };
 static const char* update_lease4_params[] = {
-        static_cast<const char*>("hwaddr"),
-        static_cast<const char*>("client_id"),
-        static_cast<const char*>("valid_lifetime"),
-        static_cast<const char*>("expire"),
-        static_cast<const char*>("subnet_id"),
-        static_cast<const char*>("fqdn_fwd"),
-        static_cast<const char*>("fqdn_rev"),
-        static_cast<const char*>("hostname"),
-        static_cast<const char*>("state"),
-        static_cast<const char*>("address"),
-        NULL };
+    static_cast<const char*>("hwaddr"),
+    static_cast<const char*>("client_id"),
+    static_cast<const char*>("valid_lifetime"),
+    static_cast<const char*>("expire"),
+    static_cast<const char*>("subnet_id"),
+    static_cast<const char*>("fqdn_fwd"),
+    static_cast<const char*>("fqdn_rev"),
+    static_cast<const char*>("hostname"),
+    static_cast<const char*>("state"),
+    static_cast<const char*>("address"),
+    NULL };
 static const char* update_lease6_params[] = {
-        static_cast<const char*>("duid"),
-        static_cast<const char*>("valid_lifetime"),
-        static_cast<const char*>("expire"),
-        static_cast<const char*>("subnet_id"),
-        static_cast<const char*>("pref_lifetime"),
-        static_cast<const char*>("lease_type"),
-        static_cast<const char*>("iaid"),
-        static_cast<const char*>("prefix_len"),
-        static_cast<const char*>("fqdn_fwd"),
-        static_cast<const char*>("fqdn_rev"),
-        static_cast<const char*>("hostname"),
-        static_cast<const char*>("hwaddr"),
-        static_cast<const char*>("hwtype"),
-        static_cast<const char*>("hwaddr_source"),
-        static_cast<const char*>("state"),
-        static_cast<const char*>("address"),
-        NULL };
+    static_cast<const char*>("duid"),
+    static_cast<const char*>("valid_lifetime"),
+    static_cast<const char*>("expire"),
+    static_cast<const char*>("subnet_id"),
+    static_cast<const char*>("pref_lifetime"),
+    static_cast<const char*>("lease_type"),
+    static_cast<const char*>("iaid"),
+    static_cast<const char*>("prefix_len"),
+    static_cast<const char*>("fqdn_fwd"),
+    static_cast<const char*>("fqdn_rev"),
+    static_cast<const char*>("hostname"),
+    static_cast<const char*>("hwaddr"),
+    static_cast<const char*>("hwtype"),
+    static_cast<const char*>("hwaddr_source"),
+    static_cast<const char*>("state"),
+    static_cast<const char*>("address"),
+    NULL };
+/// @}
 
 CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = {
     // DELETE_LEASE4
     { delete_lease4_params,
       "delete_lease4",
       "DELETE FROM lease4 WHERE address = ? "
-      "IF EXISTS" },
+      "IF EXISTS "
+    },
 
     // DELETE_LEASE4_STATE_EXPIRED
     { delete_expired_lease4_params,
       "delete_lease4_expired",
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, expire, subnet_id, "
-        "fqdn_fwd, fqdn_rev, hostname, state "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state "
       "FROM lease4 "
-      "WHERE state = ? AND expire < ? "
-      "ALLOW FILTERING" },
+      "WHERE state = ? "
+      "AND expire < ? "
+      "ALLOW FILTERING "
+    },
 
     // DELETE_LEASE6
     { delete_lease6_params,
       "delete_lease6",
       "DELETE FROM lease6 WHERE address = ? "
-      "IF EXISTS" },
+      "IF EXISTS "
+    },
 
     // DELETE_LEASE6_STATE_EXPIRED
     { delete_expired_lease6_params,
@@ -285,68 +193,86 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = {
       "SELECT address, duid, valid_lifetime, "
         "expire, subnet_id, pref_lifetime, "
         "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
-        "hwaddr, hwtype, hwaddr_source, state "
+        "hwaddr, hwtype, hwaddr_source, "
+        "state "
       "FROM lease6 "
-      "WHERE state = ? AND expire < ? "
-      "ALLOW FILTERING" },
+      "WHERE state = ? "
+      "AND expire < ? "
+      "ALLOW FILTERING "
+    },
 
     // GET_LEASE4_ADDR
     { get_lease4_addr_params,
       "get_lease4_addr",
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, expire, subnet_id, "
-        "fqdn_fwd, fqdn_rev, hostname, state "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state "
       "FROM lease4 "
-      "WHERE address = ?" },
+      "WHERE address = ? "
+    },
 
     // GET_LEASE4_CLIENTID
     { get_lease4_clientid_params,
       "get_lease4_clientid",
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, expire, subnet_id, "
-        "fqdn_fwd, fqdn_rev, hostname, state "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state "
       "FROM lease4 "
-      "WHERE client_id = ?" },
+      "WHERE client_id = ? "
+    },
 
     // GET_LEASE4_CLIENTID_SUBID
     { get_lease4_clientid_subid_params,
       "get_lease4_clientid_subid",
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, expire, subnet_id, "
-        "fqdn_fwd, fqdn_rev, hostname, state "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state "
       "FROM lease4 "
-      "WHERE client_id = ? AND subnet_id = ? "
-      "ALLOW FILTERING" },
+      "WHERE client_id = ? "
+      "AND subnet_id = ? "
+      "ALLOW FILTERING "
+    },
 
     // GET_LEASE4_HWADDR
     { get_lease4_hwaddr_params,
       "get_lease4_hwaddr",
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, expire, subnet_id, "
-        "fqdn_fwd, fqdn_rev, hostname, state "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state "
       "FROM lease4 "
-      "WHERE hwaddr = ?" },
+      "WHERE hwaddr = ? "
+    },
 
     // GET_LEASE4_HWADDR_SUBID
     { get_lease4_hwaddr_subid_params,
       "get_lease4_hwaddr_subid",
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, expire, subnet_id, "
-        "fqdn_fwd, fqdn_rev, hostname, state "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state "
       "FROM lease4 "
-      "WHERE hwaddr = ? AND subnet_id = ? "
-      "ALLOW FILTERING" },
+      "WHERE hwaddr = ? "
+      "AND subnet_id = ? "
+      "ALLOW FILTERING "
+    },
 
     // GET_LEASE4_EXPIRE
     { get_lease4_expired_params,
       "get_lease4_expired",
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, expire, subnet_id, "
-        "fqdn_fwd, fqdn_rev, hostname, state "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state "
       "FROM lease4 "
-      "WHERE state = ? AND expire < ? "
+      "WHERE state = ? "
+      "AND expire < ? "
       "LIMIT ? "
-      "ALLOW FILTERING" },
+      "ALLOW FILTERING "
+    },
 
     // GET_LEASE6_ADDR
     { get_lease6_addr_params,
@@ -354,21 +280,28 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = {
       "SELECT address, duid, valid_lifetime, "
         "expire, subnet_id, pref_lifetime, "
         "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
-        "hwaddr, hwtype, hwaddr_source, state "
+        "hwaddr, hwtype, hwaddr_source, "
+        "state "
       "FROM lease6 "
-      "WHERE address = ? AND lease_type = ? "
-      "ALLOW FILTERING" },
+      "WHERE address = ? "
+      "AND lease_type = ? "
+      "ALLOW FILTERING "
+    },
 
     // GET_LEASE6_DUID_IAID
     { get_lease6_duid_iaid_params,
-       "get_lease6_duid_iaid",
-       "SELECT address, duid, valid_lifetime, "
-         "expire, subnet_id, pref_lifetime, "
-         "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
-         "hwaddr, hwtype, hwaddr_source, state "
-       "FROM lease6 "
-       "WHERE duid = ? AND iaid = ? AND lease_type = ? "
-       "ALLOW FILTERING" },
+      "get_lease6_duid_iaid",
+      "SELECT address, duid, valid_lifetime, "
+        "expire, subnet_id, pref_lifetime, "
+        "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+        "hwaddr, hwtype, hwaddr_source, "
+        "state "
+      "FROM lease6 "
+      "WHERE duid = ? "
+      "AND iaid = ? "
+      "AND lease_type = ? "
+      "ALLOW FILTERING "
+    },
 
     // GET_LEASE6_DUID_IAID_SUBID
     { get_lease6_duid_iaid_subid_params,
@@ -376,10 +309,15 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = {
       "SELECT address, duid, valid_lifetime, "
         "expire, subnet_id, pref_lifetime, "
         "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
-        "hwaddr, hwtype, hwaddr_source, state "
+        "hwaddr, hwtype, hwaddr_source, "
+        "state "
       "FROM lease6 "
-      "WHERE duid = ? AND iaid = ? AND subnet_id = ? AND lease_type = ? "
-      "ALLOW FILTERING" },
+      "WHERE duid = ? "
+      "AND iaid = ? "
+      "AND subnet_id = ? "
+      "AND lease_type = ? "
+      "ALLOW FILTERING "
+    },
 
     // GET_LEASE6_EXPIRE
     { get_lease6_expired_params,
@@ -387,16 +325,20 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = {
       "SELECT address, duid, valid_lifetime, "
         "expire, subnet_id, pref_lifetime, "
         "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
-        "hwaddr, hwtype, hwaddr_source, state "
+        "hwaddr, hwtype, hwaddr_source, "
+        "state "
       "FROM lease6 "
-      "WHERE state = ? AND expire < ? "
+      "WHERE state = ? "
+      "AND expire < ? "
       "LIMIT ? "
-      "ALLOW FILTERING" },
+      "ALLOW FILTERING "
+    },
 
     // GET_VERSION
     { get_version_params,
       "get_version",
-      "SELECT version, minor FROM schema_version" },
+      "SELECT version, minor FROM schema_version "
+    },
 
     // INSERT_LEASE4
     { insert_lease4_params,
@@ -404,8 +346,10 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = {
       "INSERT INTO lease4(address, hwaddr, client_id, "
         "valid_lifetime, expire, subnet_id, fqdn_fwd, fqdn_rev, hostname, "
         "state) "
-      "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "
-      "IF NOT EXISTS" },
+      "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, "
+      "?) "
+      "IF NOT EXISTS "
+    },
 
     // INSERT_LEASE6
     { insert_lease6_params,
@@ -413,18 +357,23 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = {
       "INSERT INTO lease6(address, duid, valid_lifetime, "
         "expire, subnet_id, pref_lifetime, "
         "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, hwaddr, "
-        "hwtype, hwaddr_source, state) "
-      "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "
-      "IF NOT EXISTS" },
+        "hwtype, hwaddr_source, "
+        "state) "
+      "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "
+      "?) "
+      "IF NOT EXISTS "
+    },
 
     // UPDATE_LEASE4
     { update_lease4_params,
       "update_lease4",
       "UPDATE lease4 SET hwaddr = ?, "
         "client_id = ?, valid_lifetime = ?, expire = ?, "
-        "subnet_id = ?, fqdn_fwd = ?, fqdn_rev = ?, hostname = ?, state = ? "
+        "subnet_id = ?, fqdn_fwd = ?, fqdn_rev = ?, hostname = ?, "
+        "state = ? "
       "WHERE address = ? "
-      "IF EXISTS" },
+      "IF EXISTS "
+    },
 
     // UPDATE_LEASE6
     { update_lease6_params,
@@ -433,9 +382,12 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = {
         "valid_lifetime = ?, expire = ?, subnet_id = ?, "
         "pref_lifetime = ?, lease_type = ?, iaid = ?, "
         "prefix_len = ?, fqdn_fwd = ?, fqdn_rev = ?, hostname = ?, "
-        "hwaddr = ?, hwtype = ?, hwaddr_source = ?, state = ? "
+        "hwaddr = ?, hwtype = ?, hwaddr_source = ?, "
+        "state = ? "
       "WHERE address = ? "
-      "IF EXISTS" },
+      "IF EXISTS "
+    },
+
 
     // End of list sentinel
     { NULL, NULL, NULL }
@@ -449,47 +401,22 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = {
 /// base to both of them, containing some common methods.
 class CqlLeaseExchange : public CqlExchange {
 public:
-    CqlLeaseExchange() : hwaddr_length_(0), expire_(0), subnet_id_(0),
-                         valid_lifetime_(0), fqdn_fwd_(false), fqdn_rev_(false),
-                         hostname_length_(0), state_(0) {
-        memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
-        memset(hostname_buffer_, 0, sizeof(hostname_buffer_));
+    CqlLeaseExchange()
+        : valid_lifetime_(0), expire_(0), subnet_id_(0), fqdn_fwd_(cass_false),
+          fqdn_rev_(cass_false), state_(0) {
     }
-protected:
-    std::vector<uint8_t> hwaddr_;       ///< Hardware address
-    uint8_t         hwaddr_buffer_[HWAddr::MAX_HWADDR_LEN];
-                                        ///< Hardware address buffer
-    unsigned long   hwaddr_length_;     ///< Hardware address length
-    uint64_t        expire_;            ///< Lease expiry time
-    uint32_t        subnet_id_;         ///< Subnet identification
-    uint64_t        valid_lifetime_;    ///< Lease time
-    uint32_t        fqdn_fwd_;          ///< Has forward DNS update been
-                                        ///< performed
-    uint32_t        fqdn_rev_;          ///< Has reverse DNS update been
-                                        ///< performed
-    char            hostname_buffer_[HOSTNAME_MAX_LEN + 1];
-                                        ///< Client hostname
-    unsigned long   hostname_length_;   ///< Client hostname length
-    uint32_t        state_;             ///< Lease state
-};
 
-class CqlVersionExchange : public virtual CqlExchange {
-public:
-    /// @brief Constructor
-    ///
-    /// The initialization of the variables here is only to satisfy cppcheck -
-    /// all variables are initialized/set in the methods before they are used.
-    CqlVersionExchange() {
-        const size_t MAX_COLUMNS = 2U;
-        // Set the column names
-        size_t offset = 0U;
-        BOOST_STATIC_ASSERT(2U == MAX_COLUMNS);
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("version",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("minor",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        BOOST_ASSERT(parameters_.size() == MAX_COLUMNS);
-    }
+protected:
+    std::vector<cass_byte_t> hwaddr_;  ///< Hardware address
+    cass_int64_t valid_lifetime_;      ///< Lease time
+    cass_int64_t expire_;              ///< Lease expiry time
+    cass_int32_t subnet_id_;           ///< Subnet identification
+    cass_bool_t fqdn_fwd_;             ///< Has forward DNS update
+                                       /// been performed
+    cass_bool_t fqdn_rev_;             ///< Has reverse DNS update
+                                       /// been performed
+    std::string hostname_;             ///< Client hostname
+    cass_int32_t state_;               ///< Lease state
 };
 
 /// @brief Exchange CQL and Lease4 Data
@@ -511,39 +438,45 @@ public:
     ///
     /// The initialization of the variables here is only to satisfy cppcheck -
     /// all variables are initialized/set in the methods before they are used.
-    CqlLease4Exchange() : addr4_(0), client_id_length_(0),
-                            client_id_null_(false) {
-        const size_t MAX_COLUMNS = 12U;
-        memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
-
+    CqlLease4Exchange() : addr4_(0) {
         // Set the column names
-        size_t offset = 0U;
-        BOOST_STATIC_ASSERT(12U == MAX_COLUMNS);
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("address",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("hwaddr",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BYTES)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("client_id",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BYTES)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("valid_lifetime",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT64)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("expire",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_TIMESTAMP)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("subnet_id",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("fqdn_fwd",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BOOL)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("fqdn_rev",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BOOL)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("hostname",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("state",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("limit",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("[applied]",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BOOL)));
-        BOOST_ASSERT(parameters_.size() == MAX_COLUMNS);
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "address", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "hwaddr", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_BYTES)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "client_id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_BYTES)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "valid_lifetime", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT64)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "expire", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_TIMESTAMP)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "subnet_id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "fqdn_fwd", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_BOOL)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "fqdn_rev", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_BOOL)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "hostname", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_STRING)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "state", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "limit", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "[applied]", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_BOOL)));
+        BOOST_ASSERT(parameters_.size() == 12U);
     }
 
     /// @brief Create CQL_BIND objects for Lease4 Pointer
@@ -552,7 +485,7 @@ public:
     /// the database.
     void createBindForSend(const Lease4Ptr& lease, CqlDataArray& data) {
         if (!lease) {
-            isc_throw(BadValue, "createBindForSend:: Lease4 object is NULL");
+            isc_throw(BadValue, "createBindForSend(): Lease4 object is NULL");
         }
         // Store lease object to ensure it remains valid.
         lease_ = lease;
@@ -563,24 +496,24 @@ public:
             // address: int
             // The address in the Lease structure is an IOAddress object.
             // Convert this to an integer for storage.
-            addr4_ = lease_->addr_.toUint32();
-            data.add(&addr4_);
+            addr4_ = static_cast<cass_int32_t>(lease_->addr_.toUint32());
+            data.add(reinterpret_cast<void*>(&addr4_));
 
             // hwaddr: blob
-            HWAddrPtr hwaddr = lease_->hwaddr_;
-            if (hwaddr) {
-                if (hwaddr->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) {
-                    isc_throw(DbOperationError, "Hardware address length " <<
-                              lease_->hwaddr_->hwaddr_.size() <<
-                              " exceeds maximum allowed of " <<
-                              HWAddr::MAX_HWADDR_LEN);
+            if (lease_->hwaddr_) {
+                if (lease_->hwaddr_->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) {
+                    isc_throw(DbOperationError,
+                              "hardware address "
+                                  << lease_->hwaddr_->toText() << " of length "
+                                  << lease_->hwaddr_->hwaddr_.size()
+                                  << " exceeds maximum allowed length of "
+                                  << HWAddr::MAX_HWADDR_LEN);
                 }
-                hwaddr_ = hwaddr->hwaddr_;
+                hwaddr_ = lease_->hwaddr_->hwaddr_;
             } else {
                 hwaddr_.clear();
             }
-            hwaddr_length_ = hwaddr_.size();
-            data.add(&hwaddr_);
+            data.add(reinterpret_cast<void*>(&hwaddr_));
 
             // client_id: blob
             if (lease_->client_id_) {
@@ -588,174 +521,149 @@ public:
             } else {
                 client_id_.clear();
             }
-            client_id_length_ = client_id_.size();
-            data.add(&client_id_);
+            data.add(reinterpret_cast<void*>(&client_id_));
 
             // valid lifetime: bigint
-            valid_lifetime_ = lease_->valid_lft_;
-            data.add(&valid_lifetime_);
+            valid_lifetime_ = static_cast<cass_int32_t>(lease_->valid_lft_);
+            data.add(reinterpret_cast<void*>(&valid_lifetime_));
 
             // expire: bigint
-            // The lease structure holds the client last transmission time (cltt_)
+            // The lease structure holds the client last transmission time
+            /// (cltt_)
             // For convenience for external tools, this is converted to lease
             // expiry time (expire). The relationship is given by:
-            //
             // expire = cltt_ + valid_lft_
-            CqlLeaseExchange::convertToDatabaseTime(lease_->cltt_,
-                lease_->valid_lft_, expire_);
-            data.add(&expire_);
+            CqlExchange::convertToDatabaseTime(lease_->cltt_,
+                                               lease_->valid_lft_, expire_);
+            data.add(reinterpret_cast<void*>(&expire_));
 
             // subnet_id: int
-            // Can use lease_->subnet_id_ directly as it is of type uint32_t.
-            subnet_id_ = lease_->subnet_id_;
-            data.add(&subnet_id_);
+            subnet_id_ = static_cast<cass_int32_t>(lease_->subnet_id_);
+            data.add(reinterpret_cast<void*>(&subnet_id_));
 
             // fqdn_fwd: boolean
-            fqdn_fwd_ = lease_->fqdn_fwd_;
-            data.add(&fqdn_fwd_);
+            fqdn_fwd_ = lease_->fqdn_fwd_ ? cass_true : cass_false;
+            data.add(reinterpret_cast<void*>(&fqdn_fwd_));
 
             // fqdn_rev: boolean
-            fqdn_rev_ = lease_->fqdn_rev_;
-            data.add(&fqdn_rev_);
+            fqdn_rev_ = lease_->fqdn_rev_ ? cass_true : cass_false;
+            data.add(reinterpret_cast<void*>(&fqdn_rev_));
 
             // hostname: varchar
-            hostname_length_ = lease_->hostname_.length();
-            if (hostname_length_ >= sizeof(hostname_buffer_)) {
-                isc_throw(BadValue, "hostname value is too large: " <<
-                    lease_->hostname_.c_str());
-            }
-            if (hostname_length_) {
-                memcpy(hostname_buffer_, lease_->hostname_.c_str(),
-                    hostname_length_);
+            if (lease_->hostname_.size() > HOSTNAME_MAX_LEN) {
+                isc_throw(BadValue, "hostname "
+                                        << lease_->hostname_ << " of length "
+                                        << lease_->hostname_.size()
+                                        << " exceeds maximum allowed length of "
+                                        << HOSTNAME_MAX_LEN);
             }
-            hostname_buffer_[hostname_length_] = '\0';
-            data.add(hostname_buffer_);
+            hostname_ = lease_->hostname_;
+            data.add(reinterpret_cast<void*>(&hostname_));
 
             // state: int
-            state_ = lease_->state_;
-            data.add(&state_);
-
+            state_ = static_cast<cass_int32_t>(lease_->state_);
+            data.add(reinterpret_cast<void*>(&state_));
         } catch (const std::exception& ex) {
             isc_throw(DbOperationError,
-                      "Could not create bind array from Lease4: "
-                      << lease_->addr_.toText() << ", reason: " << ex.what());
+                      "could not create bind array from Lease4: "
+                          << lease_->addr_.toText()
+                          << ", reason: " << ex.what());
         }
     }
 
     /// @brief Create BIND array to receive data
     ///
     /// Creates a CQL_BIND array to receive Lease4 data from the database.
-    Lease4Ptr createBindForReceive(const CassRow* row) {
-        try {
-            unsigned char* hwaddr_buffer = NULL;
-            const char* client_id_buffer = NULL;
-            const char* hostname_buffer = NULL;
-            CqlDataArray data;
-            CqlDataArray size;
-
-            // address: int
-            data.add(reinterpret_cast<void*>(&addr4_));
-            size.add(NULL);
-
-            // hwaddr: blob
-            data.add(reinterpret_cast<void*>(&hwaddr_buffer));
-            size.add(reinterpret_cast<void*>(&hwaddr_length_));
+    virtual void createBindForReceive(CqlDataArray& data,
+                                      const int /* statementIndex */ = -1) {
+        // address: int
+        data.add(reinterpret_cast<void*>(&addr4_));
 
-            // client_id: blob
-            data.add(reinterpret_cast<void*>(&client_id_buffer));
-            size.add(reinterpret_cast<void*>(&client_id_length_));
+        // hwaddr: blob
+        data.add(reinterpret_cast<void*>(&hwaddr_));
 
-            // valid_lifetime: bigint
-            data.add(reinterpret_cast<void*>(&valid_lifetime_));
-            size.add(NULL);
+        // client_id: blob
+        data.add(reinterpret_cast<void*>(&client_id_));
 
-            // expire: bigint
-            data.add(reinterpret_cast<void*>(&expire_));
-            size.add(NULL);
+        // valid_lifetime: bigint
+        data.add(reinterpret_cast<void*>(&valid_lifetime_));
 
-            // subnet_id: int
-            data.add(reinterpret_cast<void*>(&subnet_id_));
-            size.add(NULL);
+        // expire: bigint
+        data.add(reinterpret_cast<void*>(&expire_));
 
-            // fqdn_fwd: boolean
-            data.add(reinterpret_cast<void*>(&fqdn_fwd_));
-            size.add(NULL);
+        // subnet_id: int
+        data.add(reinterpret_cast<void*>(&subnet_id_));
 
-            // fqdn_rev: boolean
-            data.add(reinterpret_cast<void*>(&fqdn_rev_));
-            size.add(NULL);
+        // fqdn_fwd: boolean
+        data.add(reinterpret_cast<void*>(&fqdn_fwd_));
 
-            // hostname: varchar
-            data.add(reinterpret_cast<void*>(&hostname_buffer));
-            size.add(reinterpret_cast<void*>(&hostname_length_));
-
-            // state: int
-            data.add(reinterpret_cast<void*>(&state_));
-            size.add(NULL);
+        // fqdn_rev: boolean
+        data.add(reinterpret_cast<void*>(&fqdn_rev_));
 
-            for (size_t i = 0; i < data.size(); i++) {
-                CqlLeaseMgr::getData(row, i, data, size, i, *this);
-            }
+        // hostname: varchar
+        data.add(reinterpret_cast<void*>(&hostname_));
 
-            // hwaddr: blob
-            hwaddr_.assign(hwaddr_buffer, hwaddr_buffer + hwaddr_length_);
+        // state: int
+        data.add(reinterpret_cast<void*>(&state_));
+    }
 
-            // client_id: blob
-            client_id_.assign(client_id_buffer, client_id_buffer +
-                client_id_length_);
-            if (client_id_length_ >= sizeof(client_id_buffer_)) {
-                isc_throw(BadValue, "client id value is too large: " <<
-                    client_id_buffer);
-            }
-            if (client_id_length_) {
-                memcpy(client_id_buffer_, client_id_buffer, client_id_length_);
+    Lease4Ptr retrieveLease() {
+        try {
+            // Sanity checks
+            if (hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) {
+                isc_throw(BadValue, "hardware address "
+                                        << HWAddr(hwaddr_, HTYPE_ETHER).toText()
+                                        << " of length " << hwaddr_.size()
+                                        << " exceeds maximum allowed length of "
+                                        << HWAddr::MAX_HWADDR_LEN);
             }
-            client_id_buffer_[client_id_length_] = '\0';
-
-            // hostname: varchar
-            if (hostname_length_ >= sizeof(hostname_buffer_)) {
-                isc_throw(BadValue, "hostname value is too large: " <<
-                    hostname_buffer);
+            if (client_id_.size() > ClientId::MAX_CLIENT_ID_LEN) {
+                isc_throw(BadValue, "client ID "
+                                        << ClientId(client_id_).toText()
+                                        << " of length " << client_id_.size()
+                                        << " exceeds maximum allowed length of "
+                                        << ClientId::MAX_CLIENT_ID_LEN);
             }
-            if (hostname_length_) {
-                memcpy(hostname_buffer_, hostname_buffer, hostname_length_);
+            if (hostname_.size() > HOSTNAME_MAX_LEN) {
+                isc_throw(BadValue, "hostname"
+                                        << hostname_ << " of length "
+                                        << hostname_.size()
+                                        << " exceeds maximum allowed length of "
+                                        << HOSTNAME_MAX_LEN);
             }
-            hostname_buffer_[hostname_length_] = '\0';
 
             time_t cltt = 0;
-            CqlLeaseExchange::convertFromDatabaseTime(expire_, valid_lifetime_,
-                cltt);
+            CqlExchange::convertFromDatabaseTime(expire_, valid_lifetime_,
+                                                 cltt);
 
             // Recreate the hardware address.
             HWAddrPtr hwaddr(new HWAddr(hwaddr_, HTYPE_ETHER));
 
-            std::string hostname(hostname_buffer_,
-                                 hostname_buffer_ + hostname_length_);
-
-            Lease4Ptr result(new Lease4(addr4_, hwaddr, client_id_buffer_,
-                                        client_id_length_, valid_lifetime_, 0,
+            Lease4Ptr result(new Lease4(addr4_, hwaddr, client_id_.data(),
+                                        client_id_.size(), valid_lifetime_, 0,
                                         0, cltt, subnet_id_, fqdn_fwd_,
-                                        fqdn_rev_, hostname));
+                                        fqdn_rev_, hostname_));
 
             result->state_ = state_;
 
-            return (result);
+            return result;
         } catch (const std::exception& ex) {
             isc_throw(DbOperationError,
-                      "Could not convert data to Lease4, reason: "
-                       << ex.what());
+                      "createBindForReceive(): "
+                      "could not convert data to Lease4, reason: "
+                          << ex.what());
         }
-        return (Lease4Ptr());
+        return Lease4Ptr();
+    }
+
+    void* retrieve() {
+        isc_throw(NotImplemented, "retrieve(): Not implemented yet.");
     }
 
 private:
-    Lease4Ptr       lease_;             ///< Pointer to lease object
-    uint32_t        addr4_;             ///< IPv4 address
-    std::vector<uint8_t> client_id_;    ///< Client identification
-    uint8_t         client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
-                                        ///< Client ID buffer
-    unsigned long   client_id_length_;  ///< Client ID address length
-    bool            client_id_null_;    ///< Is Client ID null?
+    Lease4Ptr lease_;                     ///< Pointer to lease object
+    cass_int32_t addr4_;                  ///< IPv4 address
+    std::vector<cass_byte_t> client_id_;  ///< Client identification
 };
 
 /// @brief Exchange CQL and Lease6 Data
@@ -777,53 +685,65 @@ public:
     ///
     /// The initialization of the variables here is nonly to satisfy cppcheck -
     /// all variables are initialized/set in the methods before they are used.
-    CqlLease6Exchange() : addr6_length_(0), duid_length_(0), iaid_(0),
-                          lease_type_(0), prefixlen_(0), pref_lifetime_(0),
-                          hwaddr_null_(false), hwtype_(0), hwaddr_source_(0) {
-        const size_t MAX_COLUMNS = 18U;
-        memset(addr6_buffer_, 0, sizeof(addr6_buffer_));
-        memset(duid_buffer_, 0, sizeof(duid_buffer_));
-
+    CqlLease6Exchange()
+        : pref_lifetime_(0), lease_type_(0), iaid_(0), prefixlen_(0),
+          hwtype_(0), hwaddr_source_(0) {
         // Set the column names
-        size_t offset = 0U;
-        BOOST_STATIC_ASSERT(18U == MAX_COLUMNS);
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("address",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("duid",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BYTES)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("valid_lifetime",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT64)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("expire",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_TIMESTAMP)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("subnet_id",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("pref_lifetime",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT64)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("lease_type",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("iaid",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("prefix_len",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("fqdn_fwd",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BOOL)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("fqdn_rev",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BOOL)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("hostname",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("hwaddr",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BYTES)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("hwtype",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("hwaddr_source",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("state",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("limit",
-            offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
-        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("[applied]",
-            offset++, EXCHANGE_DATA_TYPE_IO_OUT, EXCHANGE_DATA_TYPE_BOOL)));
-        BOOST_ASSERT(parameters_.size() == MAX_COLUMNS);
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "address", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_STRING)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "duid", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_BYTES)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "valid_lifetime", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT64)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "expire", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_TIMESTAMP)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "subnet_id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "pref_lifetime", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT64)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "lease_type", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "iaid", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "prefix_len", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "fqdn_fwd", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_BOOL)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "fqdn_rev", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_BOOL)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "hostname", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_STRING)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "hwaddr", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_BYTES)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "hwtype", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "hwaddr_source", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "state", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "limit", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_INT32)));
+        parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+            "[applied]", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+            EXCHANGE_DATA_TYPE_BOOL)));
+        BOOST_ASSERT(parameters_.size() == 18U);
     }
 
     /// @brief Create CQL_BIND objects for Lease6 Pointer
@@ -832,7 +752,7 @@ public:
     /// the database.
     void createBindForSend(const Lease6Ptr& lease, CqlDataArray& data) {
         if (!lease) {
-            isc_throw(BadValue, "createBindForSend:: Lease6 object is NULL");
+            isc_throw(BadValue, "createBindForSend(): Lease6 object is NULL");
         }
         // Store lease object to ensure it remains valid.
         lease_ = lease;
@@ -841,245 +761,216 @@ public:
         // structure.
         try {
             // address: varchar
-            std::string text_buffer = lease_->addr_.toText();
-            addr6_length_ = text_buffer.size();
-            if (addr6_length_ >= sizeof(addr6_buffer_)) {
-                isc_throw(BadValue, "address value is too large: " <<
-                    text_buffer);
+            addr6_ = lease_->addr_.toText();
+            if (addr6_.size() > ADDRESS6_TEXT_MAX_LEN) {
+                isc_throw(BadValue,
+                          "address " << addr6_ << " of length " << addr6_.size()
+                                     << " exceeds maximum allowed length of "
+                                     << ADDRESS6_TEXT_MAX_LEN);
             }
-            if (addr6_length_) {
-                memcpy(addr6_buffer_, text_buffer.c_str(), addr6_length_);
-            }
-            addr6_buffer_[addr6_length_] = '\0';
-            data.add(addr6_buffer_);
+            data.add(reinterpret_cast<void*>(&addr6_));
 
             // duid: blob
             if (!lease_->duid_) {
-                isc_throw(DbOperationError, "lease6 for address " <<
-                    addr6_buffer_ << " is missing mandatory client-id");
+                isc_throw(DbOperationError,
+                          "lease6 for address "
+                              << addr6_ << " is missing mandatory duid");
             }
             duid_ = lease_->duid_->getDuid();
-            duid_length_ = duid_.size();
-            data.add(&duid_);
+            data.add(reinterpret_cast<void*>(&duid_));
 
             // valid lifetime: bigint
-            valid_lifetime_ = lease_->valid_lft_;
-            data.add(&valid_lifetime_);
+            valid_lifetime_ = static_cast<cass_int64_t>(lease_->valid_lft_);
+            data.add(reinterpret_cast<void*>(&valid_lifetime_));
 
             // expire: bigint
-            // The lease structure holds the client last transmission time (cltt_)
+            // The lease structure holds the client last transmission time
+            // (cltt_)
             // For convenience for external tools, this is converted to lease
             // expiry time (expire). The relationship is given by:
-            //
             // expire = cltt_ + valid_lft_
-            CqlLeaseExchange::convertToDatabaseTime(lease_->cltt_,
-                lease_->valid_lft_, expire_);
-            data.add(&expire_);
+            CqlExchange::convertToDatabaseTime(lease_->cltt_,
+                                               lease_->valid_lft_, expire_);
+            data.add(reinterpret_cast<void*>(&expire_));
 
             // subnet_id: int
-            // Can use lease_->subnet_id_ directly as it is of type uint32_t.
-            subnet_id_ = lease_->subnet_id_;
-            data.add(&subnet_id_);
+            subnet_id_ = static_cast<cass_int32_t>(lease_->subnet_id_);
+            data.add(reinterpret_cast<void*>(&subnet_id_));
 
             // pref_lifetime: bigint
-            // Can use lease_->preferred_lft_ directly as it is of type uint32_t.
-            pref_lifetime_ = lease_->preferred_lft_;
-            data.add(&pref_lifetime_);
+            pref_lifetime_ = static_cast<cass_int64_t>(lease_->preferred_lft_);
+            data.add(reinterpret_cast<void*>(&pref_lifetime_));
 
             // lease_type: int
-            // Must convert to uint8_t as lease_->type_ is a LeaseType variable.
-            lease_type_ = lease_->type_;
-            data.add(&lease_type_);
+            lease_type_ = static_cast<cass_int32_t>(lease_->type_);
+            data.add(reinterpret_cast<void*>(&lease_type_));
 
             // iaid: int
-            // Can use lease_->iaid_ directly as it is of type uint32_t.
-            iaid_ = lease_->iaid_;
-            data.add(&iaid_);
+            iaid_ = static_cast<cass_int32_t>(lease_->iaid_);
+            data.add(reinterpret_cast<void*>(&iaid_));
 
             // prefix_len: int
-            // Can use lease_->prefixlen_ directly as it is uint32_t.
-            prefixlen_ = lease_->prefixlen_;
-            data.add(&prefixlen_);
+            prefixlen_ = static_cast<cass_int32_t>(lease_->prefixlen_);
+            data.add(reinterpret_cast<void*>(&prefixlen_));
 
             // fqdn_fwd: boolean
-            fqdn_fwd_ = lease_->fqdn_fwd_;
-            data.add(&fqdn_fwd_);
+            fqdn_fwd_ = lease_->fqdn_fwd_ ? cass_true : cass_false;
+            data.add(reinterpret_cast<void*>(&fqdn_fwd_));
 
             // fqdn_rev: boolean
-            fqdn_rev_ = lease_->fqdn_rev_;
-            data.add(&fqdn_rev_);
+            fqdn_rev_ = lease_->fqdn_rev_ ? cass_true : cass_false;
+            data.add(reinterpret_cast<void*>(&fqdn_rev_));
 
             // hostname: varchar
-            hostname_length_ = lease_->hostname_.length();
-            if (hostname_length_ >= sizeof(hostname_buffer_)) {
-                isc_throw(BadValue, "hostname value is too large: " <<
-                    lease_->hostname_.c_str());
-            }
-            if (hostname_length_) {
-                memcpy(hostname_buffer_, lease_->hostname_.c_str(),
-                    hostname_length_);
+            if (lease_->hostname_.size() > HOSTNAME_MAX_LEN) {
+                isc_throw(BadValue, "hostname"
+                                        << lease_->hostname_ << " of length "
+                                        << lease_->hostname_.size()
+                                        << " exceeds maximum allowed length of "
+                                        << HOSTNAME_MAX_LEN);
             }
-            hostname_buffer_[hostname_length_] = '\0';
-            data.add(hostname_buffer_);
+            hostname_ = lease_->hostname_;
+            data.add(reinterpret_cast<void*>(&hostname_));
 
             // hwaddr: blob
-            HWAddrPtr hwaddr = lease_->hwaddr_;
-            if (hwaddr) {
-                if (hwaddr->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) {
-                    isc_throw(DbOperationError, "Hardware address length : "
-                              << lease_->hwaddr_->hwaddr_.size()
-                              << " exceeds maximum allowed of: "
-                              << HWAddr::MAX_HWADDR_LEN);
+            if (lease_->hwaddr_) {
+                if (lease_->hwaddr_->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) {
+                    isc_throw(BadValue,
+                              "hardware address "
+                                  << lease_->hwaddr_->toText() << " of length "
+                                  << lease_->hwaddr_->hwaddr_.size()
+                                  << " exceeds maximum allowed length of "
+                                  << HWAddr::MAX_HWADDR_LEN);
                 }
-                hwaddr_ = hwaddr->hwaddr_;
+                hwaddr_ = lease_->hwaddr_->hwaddr_;
             } else {
                 hwaddr_.clear();
             }
-            hwaddr_length_ = hwaddr_.size();
-            data.add(&hwaddr_);
+            data.add(reinterpret_cast<void*>(&hwaddr_));
 
             // hwtype: int
-            if (hwaddr) {
-                hwtype_ = lease->hwaddr_->htype_;
+            if (lease_->hwaddr_) {
+                hwtype_ = static_cast<cass_int32_t>(lease_->hwaddr_->htype_);
             } else {
                 hwtype_ = 0;
             }
-            data.add(&hwtype_);
+            data.add(reinterpret_cast<void*>(&hwtype_));
 
             // hwaddr_source: int
-            if (hwaddr) {
-                hwaddr_source_ = lease->hwaddr_->source_;
+            if (lease_->hwaddr_) {
+                hwaddr_source_ =
+                    static_cast<cass_int32_t>(lease_->hwaddr_->source_);
             } else {
                 hwaddr_source_ = 0;
             }
-            data.add(&hwaddr_source_);
+            data.add(reinterpret_cast<void*>(&hwaddr_source_));
 
             // state: int
-            state_ = lease_->state_;
-            data.add(&state_);
-
+            state_ = static_cast<cass_int32_t>(lease_->state_);
+            data.add(reinterpret_cast<void*>(&state_));
         } catch (const std::exception& ex) {
             isc_throw(DbOperationError,
-                      "Could not create bind array from Lease6: "
-                      << lease_->addr_.toText() << ", reason: " << ex.what());
+                      "createBindForSend(): "
+                      "could not create bind array from Lease6: "
+                          << lease_->addr_.toText()
+                          << ", reason: " << ex.what());
         }
     }
 
     /// @brief Create BIND array to receive data
     ///
     /// Creates a CQL_BIND array to receive Lease6 data from the database.
-    Lease6Ptr createBindForReceive(const CassRow* row) {
-        try {
-            unsigned char* duid_buffer = NULL;
-            unsigned char* hwaddr_buffer = NULL;
-            const char* address_buffer = NULL;
-            const char* hostname_buffer = NULL;
-            CqlDataArray data;
-            CqlDataArray size;
+    void createBindForReceive(CqlDataArray& data,
+                              const int /* statementIndex */ = -1) {
+        // address: varchar
+        data.add(reinterpret_cast<void*>(&addr6_));
 
-            // address: varchar
-            data.add(reinterpret_cast<void*>(&address_buffer));
-            size.add(reinterpret_cast<void*>(&addr6_length_));
+        // duid: blob
+        data.add(reinterpret_cast<void*>(&duid_));
 
-            // duid: blob
-            data.add(reinterpret_cast<void*>(&duid_buffer));
-            size.add(reinterpret_cast<void*>(&duid_length_));
+        // valid_lifetime_: bigint
+        data.add(reinterpret_cast<void*>(&valid_lifetime_));
 
-            // valid_lifetime_: bigint
-            data.add(reinterpret_cast<void*>(&valid_lifetime_));
-            size.add(NULL);
+        // expire: bigint
+        data.add(reinterpret_cast<void*>(&expire_));
 
-            // expire: bigint
-            data.add(reinterpret_cast<void*>(&expire_));
-            size.add(NULL);
+        // subnet_id: int
+        data.add(reinterpret_cast<void*>(&subnet_id_));
 
-            // subnet_id: int
-            data.add(reinterpret_cast<void*>(&subnet_id_));
-            size.add(NULL);
+        // pref_lifetime: bigint
+        data.add(reinterpret_cast<void*>(&pref_lifetime_));
 
-            // pref_lifetime: bigint
-            data.add(reinterpret_cast<void*>(&pref_lifetime_));
-            size.add(NULL);
+        // lease_type: int
+        data.add(reinterpret_cast<void*>(&lease_type_));
 
-            // lease_type: int
-            data.add(reinterpret_cast<void*>(&lease_type_));
-            size.add(NULL);
-
-            // iaid: int
-            data.add(reinterpret_cast<void*>(&iaid_));
-            size.add(NULL);
-
-            // prefix_len: int
-            data.add(reinterpret_cast<void*>(&prefixlen_));
-            size.add(NULL);
+        // iaid: int
+        data.add(reinterpret_cast<void*>(&iaid_));
 
-            // fqdn_fwd: boolean
-            data.add(reinterpret_cast<void*>(&fqdn_fwd_));
-            size.add(NULL);
+        // prefix_len: int
+        data.add(reinterpret_cast<void*>(&prefixlen_));
 
-            // fqdn_rev: boolean
-            data.add(reinterpret_cast<void*>(&fqdn_rev_));
-            size.add(NULL);
+        // fqdn_fwd: boolean
+        data.add(reinterpret_cast<void*>(&fqdn_fwd_));
 
-            // hostname: varchar
-            data.add(reinterpret_cast<void*>(&hostname_buffer));
-            size.add(reinterpret_cast<void*>(&hostname_length_));
+        // fqdn_rev: boolean
+        data.add(reinterpret_cast<void*>(&fqdn_rev_));
 
-            // hwaddr: blob
-            data.add(reinterpret_cast<void*>(&hwaddr_buffer));
-            size.add(reinterpret_cast<void*>(&hwaddr_length_));
+        // hostname: varchar
+        data.add(reinterpret_cast<void*>(&hostname_));
 
-            // hwtype: int
-            data.add(reinterpret_cast<void*>(&hwtype_));
-            size.add(NULL);
+        // hwaddr: blob
+        data.add(reinterpret_cast<void*>(&hwaddr_));
 
-            // hwaddr_source: int
-            data.add(reinterpret_cast<void*>(&hwaddr_source_));
-            size.add(NULL);
+        // hwtype: int
+        data.add(reinterpret_cast<void*>(&hwtype_));
 
-            // state: int
-            data.add(reinterpret_cast<void*>(&state_));
-            size.add(NULL);
+        // hwaddr_source: int
+        data.add(reinterpret_cast<void*>(&hwaddr_source_));
 
-            for (size_t i = 0; i < data.size(); i++) {
-                CqlLeaseMgr::getData(row, i, data, size, i, *this);
-            }
+        // state: int
+        data.add(reinterpret_cast<void*>(&state_));
+    }
 
-            // address: varchar
-            if (addr6_length_ >= sizeof(addr6_buffer_)) {
-                isc_throw(BadValue, "address value is too large: " <<
-                    address_buffer);
+    Lease6Ptr retrieveLease() {
+        try {
+            // Sanity checks
+            if (addr6_.size() > ADDRESS6_TEXT_MAX_LEN) {
+                isc_throw(BadValue,
+                          "address " << addr6_ << " of length " << addr6_.size()
+                                     << " exceeds maximum allowed length of "
+                                     << ADDRESS6_TEXT_MAX_LEN);
             }
-            if (addr6_length_) {
-                memcpy(addr6_buffer_, address_buffer, addr6_length_);
+            if (duid_.size() > DUID::MAX_DUID_LEN) {
+                isc_throw(BadValue, "duid "
+                                        << DUID(duid_).toText() << " of length "
+                                        << duid_.size()
+                                        << " exceeds maximum allowed length of "
+                                        << DUID::MAX_DUID_LEN);
             }
-            addr6_buffer_[addr6_length_] = '\0';
-
-            // duid: blob
-            duid_.assign(duid_buffer, duid_buffer + duid_length_);
-
-            // hostname: varchar
-            if (hostname_length_ >= sizeof(hostname_buffer_)) {
-                isc_throw(BadValue, "hostname value is too large: " <<
-                    hostname_buffer);
+            if (lease_type_ != Lease::TYPE_NA &&
+                lease_type_ != Lease::TYPE_TA &&
+                lease_type_ != Lease::TYPE_PD) {
+                isc_throw(BadValue, "invalid lease type "
+                                        << lease_type_
+                                        << " for lease with address " << addr6_
+                                        << ". Expected 0, 1 or 2.");
             }
-            if (hostname_length_) {
-                memcpy(hostname_buffer_, hostname_buffer, hostname_length_);
+            if (hostname_.size() > HOSTNAME_MAX_LEN) {
+                isc_throw(BadValue, "hostname "
+                                        << hostname_ << " of length "
+                                        << hostname_.size()
+                                        << " exceeds maximum allowed length of "
+                                        << HOSTNAME_MAX_LEN);
             }
-            hostname_buffer_[hostname_length_] = '\0';
-
-            // hwaddr: blob
-            hwaddr_.assign(hwaddr_buffer, hwaddr_buffer + hwaddr_length_);
-
-            if (lease_type_ != Lease::TYPE_NA && lease_type_ != Lease::TYPE_TA &&
-                    lease_type_ != Lease::TYPE_PD) {
-                isc_throw(BadValue, "invalid lease type returned (" <<
-                    static_cast<int>(lease_type_) << ") for lease with "
-                    << "address " << addr6_buffer_ << ". Only 0, 1, or 2 are "
-                    << "allowed");
+            if (hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) {
+                isc_throw(BadValue,
+                          "hwaddr " << HWAddr(hwaddr_, hwtype_).toText(false)
+                                    << " of length " << hwaddr_.size()
+                                    << " exceeds maximum allowed length of "
+                                    << HWAddr::MAX_HWADDR_LEN);
             }
 
-            isc::asiolink::IOAddress addr(addr6_buffer_);
+            isc::asiolink::IOAddress addr(addr6_);
             DuidPtr duid(new DUID(duid_));
             HWAddrPtr hwaddr;
             if (hwaddr_.size()) {
@@ -1087,53 +978,50 @@ public:
                 hwaddr->source_ = hwaddr_source_;
             }
 
-            std::string hostname(hostname_buffer_,
-                                 hostname_buffer_ + hostname_length_);
-
             // Create the lease and set the cltt (after converting from the
             // expire time retrieved from the database).
-            Lease6Ptr result(new Lease6(static_cast<Lease::Type>(lease_type_),
-                                        addr, duid, iaid_, pref_lifetime_,
-                                        valid_lifetime_, 0, 0, subnet_id_,
-                                        fqdn_fwd_, fqdn_rev_, hostname, hwaddr,
-                                        prefixlen_));
+            Lease6Ptr result(new Lease6(
+                static_cast<Lease::Type>(lease_type_), addr, duid, iaid_,
+                pref_lifetime_, valid_lifetime_, 0, 0, subnet_id_, fqdn_fwd_,
+                fqdn_rev_, hostname_, hwaddr, prefixlen_));
 
             time_t cltt = 0;
-            CqlLeaseExchange::convertFromDatabaseTime(expire_, valid_lifetime_,
-                cltt);
+            CqlExchange::convertFromDatabaseTime(expire_, valid_lifetime_,
+                                                 cltt);
             result->cltt_ = cltt;
 
             result->state_ = state_;
-
-            return (result);
+            return result;
         } catch (const std::exception& ex) {
             isc_throw(DbOperationError,
-                      "Could not convert data to Lease4, reason: "
-                       << ex.what());
+                      "createBindForReceive(): "
+                      "could not convert data to Lease4, reason: "
+                          << ex.what());
         }
-        return (Lease6Ptr());
+        return Lease6Ptr();
+    }
+
+    void* retrieve() {
+        isc_throw(NotImplemented, "retrieve(): Not implemented yet.");
     }
 
 private:
-    Lease6Ptr       lease_;             ///< Pointer to lease object
-    char            addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];  ///< Character
-                                        ///< array form of V6 address
-    unsigned long   addr6_length_;      ///< Length of the address
-    std::vector<uint8_t> duid_;         ///< Client identification
-    uint8_t         duid_buffer_[DUID::MAX_DUID_LEN]; ///< Buffer form of DUID
-    unsigned long   duid_length_;       ///< Length of the DUID
-    uint32_t        iaid_;              ///< Identity association ID
-    uint32_t        lease_type_;        ///< Lease type
-    uint32_t        prefixlen_;         ///< Prefix length
-    uint32_t        pref_lifetime_;     ///< Preferred lifetime
-    bool            hwaddr_null_;       ///< Used when HWAddr is null
-    uint32_t        hwtype_;            ///< Hardware type
-    uint32_t        hwaddr_source_;     ///< Source of the hardware address
+    Lease6Ptr lease_;                ///< Pointer to lease object
+    std::string addr6_;              ///< IPv6 address
+    std::vector<cass_byte_t> duid_;  ///< Client identification
+    cass_int64_t pref_lifetime_;     ///< Preferred lifetime
+    cass_int32_t lease_type_;        ///< Lease type
+    cass_int32_t iaid_;              ///< Identity association ID
+    cass_int32_t prefixlen_;         ///< Prefix length
+    cass_int32_t hwtype_;            ///< Hardware type
+    cass_int32_t hwaddr_source_;     ///< Source of the hardware
+                                     /// address
 };
 
 CqlLeaseMgr::CqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
     : LeaseMgr(), dbconn_(parameters), exchange4_(new CqlLease4Exchange()),
-    exchange6_(new CqlLease6Exchange()), versionExchange_(new CqlVersionExchange()) {
+      exchange6_(new CqlLease6Exchange()),
+      versionExchange_(new CqlVersionExchange()) {
     dbconn_.openDatabase();
     dbconn_.prepareStatements(CqlLeaseMgr::tagged_statements_);
 }
@@ -1148,81 +1036,45 @@ CqlLeaseMgr::getDBVersion() {
     std::stringstream tmp;
     tmp << "CQL backend " << CQL_SCHEMA_VERSION_MAJOR;
     tmp << "." << CQL_SCHEMA_VERSION_MINOR;
-    tmp << ", library " << "cassandra_static";
-    return (tmp.str());
-}
-
-ExchangeDataType
-CqlLeaseMgr::getDataType(const StatementIndex stindex, int pindex,
-        const SqlExchange& exchange) {
-    if (CqlLeaseMgr::tagged_statements_[stindex].params_ &&
-            CqlLeaseMgr::tagged_statements_[stindex].params_[pindex]) {
-        const ExchangeColumnInfoContainerName& idx = exchange.parameters_.get<1>();
-        const ExchangeColumnInfoContainerNameRange& range =
-            idx.equal_range(CqlLeaseMgr::tagged_statements_[stindex].params_[pindex]);
-        if (std::distance(range.first, range.second) > 0) {
-            return (*range.first)->type_;
-        }
-    }
-    return EXCHANGE_DATA_TYPE_NONE;
-}
-
-void
-CqlLeaseMgr::bindData(CassStatement* statement, const StatementIndex stindex,
-        CqlDataArray& data, const SqlExchange& exchange) {
-    if (CqlLeaseMgr::tagged_statements_[stindex].params_ == NULL) {
-        return;
-    }
-    for (int i = 0; CqlLeaseMgr::tagged_statements_[stindex].params_[i]; i++) {
-        ExchangeDataType type = CqlLeaseMgr::getDataType(stindex, i, exchange);
-        if (type >= sizeof(CqlFunctions) / sizeof(CqlFunctions[0])) {
-            isc_throw(BadValue, "index " << stindex << " out of bounds");
-        }
-        CqlFunctions[type].sqlBindFunction_(statement, i, data.values_[i]);
-    }
-}
-
-void
-CqlLeaseMgr::getData(const CassRow* row, const int pindex, CqlDataArray& data,
-        CqlDataArray& size, const int dindex, const SqlExchange& exchange) {
-    if (pindex >= exchange.parameters_.size()) {
-        return;
-    }
-    const ExchangeColumnInfoContainerIndex& idx = exchange.parameters_.get<2>();
-    const ExchangeColumnInfoContainerIndexRange& range = idx.equal_range(pindex);
-    if (std::distance(range.first, range.second) > 0) {
-        std::string name = (*range.first)->name_;
-        ExchangeDataType type = (*range.first)->type_;
-        const CassValue* value = cass_row_get_column_by_name(row, name.c_str());
-        if (NULL == value) {
-            isc_throw(BadValue, "column name " << name << " doesn't exist");
-        }
-        if (type >= sizeof(CqlFunctions) / sizeof(CqlFunctions[0])) {
-            isc_throw(BadValue, "index " << type << " out of bounds");
-        }
-        CqlFunctions[type].sqlGetFunction_(value, data.values_[dindex],
-            reinterpret_cast<size_t *>(size.values_[dindex]));
-    }
+    tmp << ", library cassandra_static";
+    return tmp.str();
 }
 
 bool
 CqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
-                              CqlDataArray& data, CqlLeaseExchange& exchange) {
+                            CqlDataArray& data,
+                            CqlLeaseExchange& exchange) {
     CassError rc;
     CassStatement* statement = NULL;
     CassFuture* future = NULL;
 
     statement = cass_prepared_bind(dbconn_.statements_[stindex]);
-    if (NULL == statement) {
-        isc_throw(DbOperationError, "unable to bind statement");
+    if (!statement) {
+        isc_throw(DbOperationError,
+                  "unable to bind statement "
+                      << dbconn_.tagged_statements_[stindex].name_);
     }
 
-    CqlLeaseMgr::bindData(statement, stindex, data, exchange);
+    if (dbconn_.force_consistency_) {
+        rc = cass_statement_set_consistency(statement, dbconn_.consistency_);
+        if (rc != CASS_OK) {
+            cass_statement_free(statement);
+            isc_throw(
+                DbOperationError,
+                "unable to set statement consistency for statement "
+                    << dbconn_.tagged_statements_[stindex].name_);
+        }
+    }
+
+    CqlCommon::bindData(statement, stindex, data, exchange,
+                        CqlLeaseMgr::tagged_statements_);
 
     future = cass_session_execute(dbconn_.session_, statement);
-    if (NULL == future) {
+    if (!future) {
         cass_statement_free(statement);
-        isc_throw(DbOperationError, "unable to execute statement");
+        isc_throw(DbOperationError,
+                  "unable to execute statement "
+                      << dbconn_.tagged_statements_[stindex].name_);
     }
     cass_future_wait(future);
     std::string error;
@@ -1235,78 +1087,81 @@ CqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
     }
 
     // Check if statement has been applied.
-    const CassResult* resultCollection = cass_future_get_result(future);
-    CassIterator* rows = cass_iterator_from_result(resultCollection);
-    CqlDataArray appliedData;
-    CqlDataArray appliedSize;
-    bool applied = false;
-    while (cass_iterator_next(rows)) {
-        const CassRow* row = cass_iterator_get_row(rows);
-        // [applied]: bool
-        appliedData.add(reinterpret_cast<void*>(&applied));
-        appliedSize.add(NULL);
-        CqlLeaseMgr::getData(row, exchange.parameters_.size() - 1, appliedData,
-                             appliedSize, 0, exchange);
-    }
+    bool applied = exchange.hasStatementBeenApplied(future);
 
     // Free resources.
-    cass_iterator_free(rows);
-    cass_result_free(resultCollection);
     cass_future_free(future);
     cass_statement_free(statement);
-
     return applied;
 }
 
 bool
 CqlLeaseMgr::addLease(const Lease4Ptr& lease) {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_ADD_ADDR4).arg(lease->addr_.toText());
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_ADD_ADDR4)
+        .arg(lease->addr_.toText());
 
     CqlDataArray data;
     exchange4_->createBindForSend(lease, data);
-    return (addLeaseCommon(INSERT_LEASE4, data, *exchange4_));
+    return addLeaseCommon(INSERT_LEASE4, data, *exchange4_);
 }
 
 bool
 CqlLeaseMgr::addLease(const Lease6Ptr& lease) {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_ADD_ADDR6).arg(lease->addr_.toText());
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_ADD_ADDR6)
+        .arg(lease->addr_.toText());
 
     CqlDataArray data;
     exchange6_->createBindForSend(lease, data);
-    return (addLeaseCommon(INSERT_LEASE6, data, *exchange6_));
+    return addLeaseCommon(INSERT_LEASE6, data, *exchange6_);
 }
 
 template <typename Exchange, typename LeaseCollection>
-void CqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
-                                       CqlDataArray& data,
-                                       Exchange& exchange,
-                                       LeaseCollection& result,
-                                       bool single) const {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_GET_ADDR4).arg(dbconn_.tagged_statements_[stindex].name_);
+void
+CqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
+                                CqlDataArray& data,
+                                Exchange& exchange,
+                                LeaseCollection& result,
+                                bool single) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_GET_ADDR4)
+        .arg(dbconn_.tagged_statements_[stindex].name_);
 
     CassError rc;
     CassStatement* statement = NULL;
     CassFuture* future = NULL;
-    const CqlLeaseExchange& leaseExchange = static_cast<CqlLeaseExchange>(*exchange);
 
     statement = cass_prepared_bind(dbconn_.statements_[stindex]);
-    if (NULL == statement) {
+    if (!statement) {
         isc_throw(DbOperationError, "unable to bind statement");
     }
 
-    CqlLeaseMgr::bindData(statement, stindex, data, leaseExchange);
+    if (dbconn_.force_consistency_) {
+        rc = cass_statement_set_consistency(statement, dbconn_.consistency_);
+        if (rc != CASS_OK) {
+            cass_statement_free(statement);
+            isc_throw(
+                DbOperationError,
+                "unable to set statement consistency for statement "
+                    << dbconn_.tagged_statements_[stindex].name_);
+        }
+    }
+
+    CqlCommon::bindData(statement, stindex, data,
+                        static_cast<SqlExchange>(*exchange),
+                        CqlLeaseMgr::tagged_statements_);
 
     future = cass_session_execute(dbconn_.session_, statement);
-    if (NULL == future) {
+    if (!future) {
         cass_statement_free(statement);
-        isc_throw(DbOperationError, "unable to execute statement");
+        isc_throw(DbOperationError,
+                  "unable to execute statement "
+                      << dbconn_.tagged_statements_[stindex].name_);
     }
     cass_future_wait(future);
     std::string error;
-    dbconn_.checkStatementError(error, future, "unable to GET");
+    std::stringstream message;
+    message << "unable to GET using statement "
+            << CqlLeaseMgr::tagged_statements_[stindex].name_;
+    dbconn_.checkStatementError(error, future, message.str().c_str());
     rc = cass_future_error_code(future);
     if (rc != CASS_OK) {
         cass_future_free(future);
@@ -1324,7 +1179,13 @@ void CqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
             break;
         }
         const CassRow* row = cass_iterator_get_row(rows);
-        result.push_back(exchange->createBindForReceive(row));
+        CqlDataArray data;
+        exchange->createBindForReceive(data);
+        // Get data.
+        for (size_t i = 0U; i < data.size(); i++) {
+            CqlCommon::getData(row, i, i, *exchange, data);
+        }
+        result.push_back(exchange->retrieveLease());
     }
 
     cass_iterator_free(rows);
@@ -1332,15 +1193,17 @@ void CqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
     cass_future_free(future);
     cass_statement_free(statement);
     if (single && rowCount > 1) {
-        isc_throw(MultipleRecords, "multiple records were found in the "
-                      "database where only one was expected for query "
+        isc_throw(MultipleRecords,
+                  "multiple records were found in the "
+                  "database where only one was expected for statement "
                       << dbconn_.tagged_statements_[stindex].name_);
     }
 }
 
 void
-CqlLeaseMgr::getLease(StatementIndex stindex, CqlDataArray& data,
-                             Lease4Ptr& result) const {
+CqlLeaseMgr::getLease(StatementIndex stindex,
+                      CqlDataArray& data,
+                      Lease4Ptr& result) const {
     // Create appropriate collection object and get all leases matching
     // the selection criteria. The "single" parameter is true to indicate
     // that the called method should throw an exception if multiple
@@ -1358,8 +1221,9 @@ CqlLeaseMgr::getLease(StatementIndex stindex, CqlDataArray& data,
 }
 
 void
-CqlLeaseMgr::getLease(StatementIndex stindex, CqlDataArray& data,
-                             Lease6Ptr& result) const {
+CqlLeaseMgr::getLease(StatementIndex stindex,
+                      CqlDataArray& data,
+                      Lease6Ptr& result) const {
     // Create appropriate collection object and get all leases matching
     // the selection criteria. The "single" parameter is true to indicate
     // that the called method should throw an exception if multiple
@@ -1381,246 +1245,260 @@ CqlLeaseMgr::getLease(StatementIndex stindex, CqlDataArray& data,
 
 Lease4Ptr
 CqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_GET_ADDR4).arg(addr.toText());
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_GET_ADDR4)
+        .arg(addr.toText());
 
     // Set up the WHERE clause value
     CqlDataArray data;
 
-    uint32_t addr4_data = addr.toUint32();
-    data.add(&addr4_data);
+    cass_int32_t addr4_data = static_cast<cass_int32_t>(addr.toUint32());
+    data.add(reinterpret_cast<void*>(&addr4_data));
 
     // Get the data
     Lease4Ptr result;
     getLease(GET_LEASE4_ADDR, data, result);
 
-    return (result);
+    return result;
 }
 
 Lease4Collection
 CqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_GET_HWADDR).arg(hwaddr.toText());
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_GET_HWADDR)
+        .arg(hwaddr.toText());
 
     // Set up the WHERE clause value
     CqlDataArray data;
 
-    std::vector<uint8_t>hwaddr_data = hwaddr.hwaddr_;
-    data.add(&hwaddr_data);
+    std::vector<cass_byte_t> hwaddr_data = hwaddr.hwaddr_;
+    data.add(reinterpret_cast<void*>(&hwaddr_data));
 
     // Get the data
     Lease4Collection result;
     getLeaseCollection(GET_LEASE4_HWADDR, data, result);
 
-    return (result);
+    return result;
 }
 
 Lease4Ptr
 CqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_CQL_GET_SUBID_HWADDR)
-              .arg(subnet_id).arg(hwaddr.toText());
+        .arg(subnet_id)
+        .arg(hwaddr.toText());
 
     // Set up the WHERE clause value
     CqlDataArray data;
 
-    std::vector<uint8_t>hwaddr_data = hwaddr.hwaddr_;
-    data.add(&hwaddr_data);
+    std::vector<cass_byte_t> hwaddr_data = hwaddr.hwaddr_;
+    data.add(reinterpret_cast<void*>(&hwaddr_data));
 
-    uint32_t subnet_id_data = subnet_id;
-    data.add(&subnet_id_data);
+    cass_int32_t subnet_id_data = static_cast<cass_int32_t>(subnet_id);
+    data.add(reinterpret_cast<void*>(&subnet_id_data));
 
     // Get the data
     Lease4Ptr result;
     getLease(GET_LEASE4_HWADDR_SUBID, data, result);
 
-    return (result);
+    return result;
 }
 
 Lease4Collection
 CqlLeaseMgr::getLease4(const ClientId& clientid) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_GET_CLIENTID).arg(clientid.toText());
+              DHCPSRV_CQL_GET_CLIENTID)
+        .arg(clientid.toText());
 
     // Set up the WHERE clause value
     CqlDataArray data;
 
-    std::vector<uint8_t> client_id_data = clientid.getClientId();
-    data.add(&client_id_data);
+    std::vector<cass_byte_t> client_id_data = clientid.getClientId();
+    data.add(reinterpret_cast<void*>(&client_id_data));
 
     // Get the data
     Lease4Collection result;
     getLeaseCollection(GET_LEASE4_CLIENTID, data, result);
 
-    return (result);
+    return result;
 }
 
 Lease4Ptr
-CqlLeaseMgr::getLease4(const ClientId& clientid, const HWAddr& hwaddr,
-        SubnetID subnet_id) const {
-    /// This function is currently not implemented because allocation engine
+CqlLeaseMgr::getLease4(const ClientId& clientid,
+                       const HWAddr& hwaddr,
+                       SubnetID subnet_id) const {
+    /// This method is currently not implemented because allocation engine
     /// searches for the lease using HW address or client identifier.
     /// It never uses both parameters in the same time. We need to
-    /// consider if this function is needed at all.
+    /// consider if this method is needed at all.
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_GET_CLIENTID_HWADDR_SUBID).arg(clientid.toText())
-              .arg(hwaddr.toText()).arg(subnet_id);
+              DHCPSRV_CQL_GET_CLIENTID_HWADDR_SUBID)
+        .arg(clientid.toText())
+        .arg(hwaddr.toText())
+        .arg(subnet_id);
 
-    isc_throw(NotImplemented, "The CqlLeaseMgr::getLease4 function was"
-              " called, but it is not implemented");
+    isc_throw(NotImplemented, "The CqlLeaseMgr::getLease4 method was"
+                              " called, but it is not implemented");
 }
 
 Lease4Ptr
 CqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_CQL_GET_SUBID_CLIENTID)
-              .arg(subnet_id).arg(clientid.toText());
+        .arg(subnet_id)
+        .arg(clientid.toText());
 
     // Set up the WHERE clause value
     CqlDataArray data;
 
     std::vector<uint8_t> client_id_data = clientid.getClientId();
-    data.add(&client_id_data);
+    data.add(reinterpret_cast<void*>(&client_id_data));
 
-    uint32_t subnet_id_data = subnet_id;
-    data.add(&subnet_id_data);
+    cass_int32_t subnet_id_data = static_cast<cass_int32_t>(subnet_id);
+    data.add(reinterpret_cast<void*>(&subnet_id_data));
 
     // Get the data
     Lease4Ptr result;
     getLease(GET_LEASE4_CLIENTID_SUBID, data, result);
 
-    return (result);
+    return result;
 }
 
 Lease6Ptr
 CqlLeaseMgr::getLease6(Lease::Type lease_type,
-                         const isc::asiolink::IOAddress& addr) const {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_GET_ADDR6).arg(addr.toText())
-              .arg(lease_type);
+                       const isc::asiolink::IOAddress& addr) const {
+    std::string addr_data = addr.toText();
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_GET_ADDR6)
+        .arg(addr_data)
+        .arg(lease_type);
 
     // Set up the WHERE clause value
     CqlDataArray data;
 
-    std::string text_buffer = addr.toText();
-    uint32_t addr6_length = text_buffer.size();
-    char addr6_buffer[ADDRESS6_TEXT_MAX_LEN + 1];
-    if (addr6_length >= sizeof(addr6_buffer)) {
-        isc_throw(BadValue, "address value is too large: " << text_buffer);
-    }
-    if (addr6_length) {
-        memcpy(addr6_buffer, text_buffer.c_str(), addr6_length);
+    if (addr_data.size() > ADDRESS6_TEXT_MAX_LEN) {
+        isc_throw(BadValue, "getLease6(): "
+                            "address "
+                                << addr_data << " of length "
+                                << addr_data.size()
+                                << " exceeds maximum allowed length of "
+                                << ADDRESS6_TEXT_MAX_LEN);
     }
-    addr6_buffer[addr6_length] = '\0';
-    data.add(addr6_buffer);
+    data.add(reinterpret_cast<void*>(&addr_data));
 
-    uint32_t lease_type_data = lease_type;
-    data.add(&lease_type_data);
+    cass_int32_t lease_type_data = static_cast<cass_int32_t>(lease_type);
+    data.add(reinterpret_cast<void*>(&lease_type_data));
 
     Lease6Ptr result;
     getLease(GET_LEASE6_ADDR, data, result);
 
-    return (result);
+    return result;
 }
 
 Lease6Collection
 CqlLeaseMgr::getLeases6(Lease::Type lease_type,
-                          const DUID& duid, uint32_t iaid) const {
+                        const DUID& duid,
+                        uint32_t iaid) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_GET_IAID_DUID).arg(iaid).arg(duid.toText())
-              .arg(lease_type);
+              DHCPSRV_CQL_GET_IAID_DUID)
+        .arg(iaid)
+        .arg(duid.toText())
+        .arg(lease_type);
 
     // Set up the WHERE clause value
     CqlDataArray data;
 
-    std::vector<uint8_t> duid_data = duid.getDuid();
-    data.add(&duid_data);
+    std::vector<cass_byte_t> duid_data = duid.getDuid();
+    data.add(reinterpret_cast<void*>(&duid_data));
 
-    uint32_t iaid_data = iaid;
-    data.add(&iaid_data);
+    cass_int32_t iaid_data = static_cast<cass_int32_t>(iaid);
+    data.add(reinterpret_cast<void*>(&iaid_data));
 
-    uint32_t lease_type_data = lease_type;
-    data.add(&lease_type_data);
+    cass_int32_t lease_type_data = static_cast<cass_int32_t>(lease_type);
+    data.add(reinterpret_cast<void*>(&lease_type_data));
 
     // ... and get the data
     Lease6Collection result;
     getLeaseCollection(GET_LEASE6_DUID_IAID, data, result);
 
-    return (result);
+    return result;
 }
 
 Lease6Collection
 CqlLeaseMgr::getLeases6(Lease::Type lease_type,
-                          const DUID& duid, uint32_t iaid,
-                          SubnetID subnet_id) const {
+                        const DUID& duid,
+                        uint32_t iaid,
+                        SubnetID subnet_id) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_CQL_GET_IAID_SUBID_DUID)
-              .arg(iaid).arg(subnet_id).arg(duid.toText())
-              .arg(lease_type);
+        .arg(iaid)
+        .arg(subnet_id)
+        .arg(duid.toText())
+        .arg(lease_type);
 
     // Set up the WHERE clause value
     CqlDataArray data;
 
-    std::vector<uint8_t> duid_data = duid.getDuid();
-    data.add(&duid_data);
+    std::vector<cass_byte_t> duid_data = duid.getDuid();
+    data.add(reinterpret_cast<void*>(&duid_data));
 
-    uint32_t iaid_data = iaid;
-    data.add(&iaid_data);
+    cass_int32_t iaid_data = static_cast<cass_int32_t>(iaid);
+    data.add(reinterpret_cast<void*>(&iaid_data));
 
-    uint32_t subnet_id_data = subnet_id;
-    data.add(&subnet_id_data);
+    cass_int32_t subnet_id_data = static_cast<cass_int32_t>(subnet_id);
+    data.add(reinterpret_cast<void*>(&subnet_id_data));
 
-    uint32_t lease_type_data = lease_type;
-    data.add(&lease_type_data);
+    cass_int32_t lease_type_data = static_cast<cass_int32_t>(lease_type);
+    data.add(reinterpret_cast<void*>(&lease_type_data));
 
     // ... and get the data
     Lease6Collection result;
     getLeaseCollection(GET_LEASE6_DUID_IAID_SUBID, data, result);
 
-    return (result);
+    return result;
 }
 
 void
 CqlLeaseMgr::getExpiredLeases6(Lease6Collection& expired_leases,
-                                 const size_t max_leases) const {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_GET_EXPIRED6)
+                               const size_t max_leases) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_GET_EXPIRED6)
         .arg(max_leases);
     getExpiredLeasesCommon(expired_leases, max_leases, GET_LEASE6_EXPIRE);
 }
 
 void
 CqlLeaseMgr::getExpiredLeases4(Lease4Collection& expired_leases,
-                                 const size_t max_leases) const {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_GET_EXPIRED4)
+                               const size_t max_leases) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_GET_EXPIRED4)
         .arg(max_leases);
     getExpiredLeasesCommon(expired_leases, max_leases, GET_LEASE4_EXPIRE);
 }
 
-template<typename LeaseCollection>
+template <typename LeaseCollection>
 void
 CqlLeaseMgr::getExpiredLeasesCommon(LeaseCollection& expired_leases,
-                                       const size_t max_leases,
-                                       StatementIndex statement_index) const {
+                                    const size_t max_leases,
+                                    StatementIndex statement_index) const {
     // Set up the WHERE clause value
-    uint32_t keepState = Lease::STATE_EXPIRED_RECLAIMED;
-    uint64_t timestamp = static_cast<int64_t>(time(NULL));
+    cass_int32_t keepState = Lease::STATE_EXPIRED_RECLAIMED;
+    cass_int64_t timestamp = static_cast<cass_int64_t>(time(NULL));
 
     // If the number of leases is 0, we will return all leases. This is
     // achieved by setting the limit to a very high value.
-    uint32_t limit = max_leases > 0 ? static_cast<int32_t>(max_leases) :
-        std::numeric_limits<int32_t>::max();
+    cass_int32_t limit = max_leases > 0 ?
+                             static_cast<cass_int32_t>(max_leases) :
+                             std::numeric_limits<cass_int32_t>::max();
 
-    for (uint32_t state = Lease::STATE_DEFAULT;
-            state <= Lease::STATE_EXPIRED_RECLAIMED; state++) {
+    for (cass_int32_t state = Lease::STATE_DEFAULT;
+         state <= Lease::STATE_EXPIRED_RECLAIMED; state++) {
         if (state == keepState) {
             continue;
         }
         LeaseCollection tempCollection;
         CqlDataArray data;
 
-        data.add(&state);
-        data.add(&timestamp);
-        data.add(&limit);
+        data.add(reinterpret_cast<void*>(&state));
+        data.add(reinterpret_cast<void*>(&timestamp));
+        data.add(reinterpret_cast<void*>(&limit));
 
         // Retrieve leases from the database.
         getLeaseCollection(statement_index, data, tempCollection);
@@ -1628,35 +1506,48 @@ CqlLeaseMgr::getExpiredLeasesCommon(LeaseCollection& expired_leases,
         typedef typename LeaseCollection::iterator LeaseCollectionIt;
 
         for (LeaseCollectionIt it = tempCollection.begin();
-                it != tempCollection.end(); ++it) {
+             it != tempCollection.end(); ++it) {
             expired_leases.push_back((*it));
         }
     }
 }
 
-template <typename LeasePtr>
 void
 CqlLeaseMgr::updateLeaseCommon(StatementIndex stindex,
-                                 CqlDataArray& data,
-                                 const LeasePtr&, CqlLeaseExchange& exchange) {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_ADD_ADDR4).arg(dbconn_.tagged_statements_[stindex].name_);
+                               CqlDataArray& data,
+                               CqlLeaseExchange& exchange) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_ADD_ADDR4)
+        .arg(dbconn_.tagged_statements_[stindex].name_);
 
     CassError rc;
     CassStatement* statement = NULL;
     CassFuture* future = NULL;
 
     statement = cass_prepared_bind(dbconn_.statements_[stindex]);
-    if (NULL == statement) {
+    if (!statement) {
         isc_throw(DbOperationError, "unable to bind statement");
     }
 
-    CqlLeaseMgr::bindData(statement, stindex, data, exchange);
+    if (dbconn_.force_consistency_) {
+        rc = cass_statement_set_consistency(statement, dbconn_.consistency_);
+        if (rc != CASS_OK) {
+            cass_statement_free(statement);
+            isc_throw(
+                DbOperationError,
+                "unable to set statement consistency for statement "
+                    << dbconn_.tagged_statements_[stindex].name_);
+        }
+    }
+
+    CqlCommon::bindData(statement, stindex, data, exchange,
+                        CqlLeaseMgr::tagged_statements_);
 
     future = cass_session_execute(dbconn_.session_, statement);
-    if (NULL == future) {
+    if (!future) {
         cass_statement_free(statement);
-        isc_throw(DbOperationError, "unable to execute statement");
+        isc_throw(DbOperationError,
+                  "unable to execute statement "
+                      << dbconn_.tagged_statements_[stindex].name_);
     }
     cass_future_wait(future);
     std::string error;
@@ -1669,23 +1560,12 @@ CqlLeaseMgr::updateLeaseCommon(StatementIndex stindex,
     }
 
     // Check if statement has been applied.
-    const CassResult* resultCollection = cass_future_get_result(future);
-    CassIterator* rows = cass_iterator_from_result(resultCollection);
-    CqlDataArray appliedData;
-    CqlDataArray appliedSize;
-    bool applied = false;
-    while (cass_iterator_next(rows)) {
-        const CassRow* row = cass_iterator_get_row(rows);
-        // [applied]: bool
-        appliedData.add(reinterpret_cast<void*>(&applied));
-        appliedSize.add(NULL);
-        CqlLeaseMgr::getData(row, exchange.parameters_.size() - 1, appliedData,
-                             appliedSize, 0, exchange);
-    }
+    size_t row_count;
+    size_t column_count;
+    bool applied =
+        exchange.hasStatementBeenApplied(future, &row_count, &column_count);
 
     // Free resources.
-    cass_iterator_free(rows);
-    cass_result_free(resultCollection);
     cass_future_free(future);
     cass_statement_free(statement);
 
@@ -1699,27 +1579,30 @@ CqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
     const StatementIndex stindex = UPDATE_LEASE4;
 
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_UPDATE_ADDR4).arg(lease->addr_.toText());
+              DHCPSRV_CQL_UPDATE_ADDR4)
+        .arg(lease->addr_.toText());
 
     // Create the BIND array for the data being updated
     CqlDataArray data;
     exchange4_->createBindForSend(lease, data);
     data.remove(0);
 
-    // Set up the WHERE clause and append it to the SQL_BIND array
-    uint32_t addr4_data = lease->addr_.toUint32();
-    data.add(&addr4_data);
+    // Set up the WHERE clause and append it to the bind array.
+    cass_int32_t addr4_data =
+        static_cast<cass_int32_t>(lease->addr_.toUint32());
+    data.add(reinterpret_cast<void*>(&addr4_data));
 
     // Drop to common update code
-    updateLeaseCommon(stindex, data, lease, *exchange4_);
+    updateLeaseCommon(stindex, data, *exchange4_);
 }
 
 void
 CqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
     const StatementIndex stindex = UPDATE_LEASE6;
-
+    std::string lease_addr_data = lease->addr_.toText();
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_UPDATE_ADDR6).arg(lease->addr_.toText());
+              DHCPSRV_CQL_UPDATE_ADDR6)
+        .arg(lease_addr_data);
 
     // Create the BIND array for the data being updated
     CqlDataArray data;
@@ -1727,40 +1610,54 @@ CqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
     data.remove(0);
 
     // Set up the WHERE clause and append it to the BIND array
-    std::string text_buffer = lease->addr_.toText();
-    uint32_t addr6_length = text_buffer.size();
-    char addr6_buffer[ADDRESS6_TEXT_MAX_LEN + 1];
-    if (addr6_length >= sizeof(addr6_buffer)) {
-        isc_throw(BadValue, "address value is too large: " << text_buffer);
+    if (lease_addr_data.size() > ADDRESS6_TEXT_MAX_LEN) {
+        isc_throw(BadValue, "updateLease6(): "
+                            "address "
+                                << lease_addr_data << " of length "
+                                << lease_addr_data.size()
+                                << " exceeds maximum allowed length "
+                                   "of "
+                                << ADDRESS6_TEXT_MAX_LEN);
     }
-    if (addr6_length) {
-        memcpy(addr6_buffer, text_buffer.c_str(), addr6_length);
-    }
-    addr6_buffer[addr6_length] = '\0';
-    data.add(addr6_buffer);
+    data.add(reinterpret_cast<void*>(&lease_addr_data));
 
     // Drop to common update code
-    updateLeaseCommon(stindex, data, lease, *exchange6_);
+    updateLeaseCommon(stindex, data, *exchange6_);
 }
 
 bool
 CqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex,
-                                 CqlDataArray& data, CqlLeaseExchange& exchange) {
+                               CqlDataArray& data,
+                               CqlLeaseExchange& exchange) {
     CassError rc;
     CassStatement* statement = NULL;
     CassFuture* future = NULL;
 
     statement = cass_prepared_bind(dbconn_.statements_[stindex]);
-    if (NULL == statement) {
+    if (!statement) {
         isc_throw(DbOperationError, "unable to bind statement");
     }
 
-    CqlLeaseMgr::bindData(statement, stindex, data, exchange);
+    if (dbconn_.force_consistency_) {
+        rc = cass_statement_set_consistency(statement, dbconn_.consistency_);
+        if (rc != CASS_OK) {
+            cass_statement_free(statement);
+            isc_throw(
+                DbOperationError,
+                "unable to set statement consistency for statement "
+                    << dbconn_.tagged_statements_[stindex].name_);
+        }
+    }
+
+    CqlCommon::bindData(statement, stindex, data, exchange,
+                        CqlLeaseMgr::tagged_statements_);
 
     future = cass_session_execute(dbconn_.session_, statement);
-    if (NULL == future) {
+    if (!future) {
         cass_statement_free(statement);
-        isc_throw(DbOperationError, "unable to execute statement");
+        isc_throw(DbOperationError,
+                  "unable to execute statement "
+                      << dbconn_.tagged_statements_[stindex].name_);
     }
     cass_future_wait(future);
     std::string error;
@@ -1773,23 +1670,9 @@ CqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex,
     }
 
     // Check if statement has been applied.
-    const CassResult* resultCollection = cass_future_get_result(future);
-    CassIterator* rows = cass_iterator_from_result(resultCollection);
-    CqlDataArray appliedData;
-    CqlDataArray appliedSize;
-    bool applied = false;
-    while (cass_iterator_next(rows)) {
-        const CassRow* row = cass_iterator_get_row(rows);
-        // [applied]: bool
-        appliedData.add(reinterpret_cast<void*>(&applied));
-        appliedSize.add(NULL);
-        CqlLeaseMgr::getData(row, exchange.parameters_.size() - 1, appliedData,
-                             appliedSize, 0, exchange);
-    }
+    bool applied = exchange.hasStatementBeenApplied(future);
 
     // Free resources.
-    cass_iterator_free(rows);
-    cass_result_free(resultCollection);
     cass_future_free(future);
     cass_statement_free(statement);
 
@@ -1798,29 +1681,29 @@ CqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex,
 
 bool
 CqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_CQL_DELETE_ADDR).arg(addr.toText());
+    std::string addr_data = addr.toText();
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_DELETE_ADDR)
+        .arg(addr_data);
 
     // Set up the WHERE clause value
     CqlDataArray data;
 
     if (addr.isV4()) {
-        uint32_t addr4_data = addr.toUint32();
-        data.add(&addr4_data);
-        return (deleteLeaseCommon(DELETE_LEASE4, data, *exchange4_));
+        cass_int32_t addr4_data = static_cast<cass_int32_t>(addr.toUint32());
+        data.add(reinterpret_cast<void*>(&addr4_data));
+        return deleteLeaseCommon(DELETE_LEASE4, data, *exchange4_);
     } else {
-        std::string text_buffer = addr.toText();
-        uint32_t addr6_length = text_buffer.size();
-        char addr6_buffer[ADDRESS6_TEXT_MAX_LEN + 1];
-        if (addr6_length >= sizeof(addr6_buffer)) {
-            isc_throw(BadValue, "address value is too large: " << text_buffer);
-        }
-        if (addr6_length) {
-            memcpy(addr6_buffer, text_buffer.c_str(), addr6_length);
+        if (addr_data.size() > ADDRESS6_TEXT_MAX_LEN) {
+            isc_throw(BadValue, "deleteLease(): "
+                                "address "
+                                    << addr_data << " of length "
+                                    << addr_data.size()
+                                    << " exceeds maximum allowed length "
+                                       "of "
+                                    << ADDRESS6_TEXT_MAX_LEN);
         }
-        addr6_buffer[addr6_length] = '\0';
-        data.add(addr6_buffer);
-        return (deleteLeaseCommon(DELETE_LEASE6, data, *exchange6_));
+        data.add(reinterpret_cast<void*>(&addr_data));
+        return deleteLeaseCommon(DELETE_LEASE6, data, *exchange6_);
     }
 }
 
@@ -1829,7 +1712,8 @@ CqlLeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t secs) {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_CQL_DELETE_EXPIRED_RECLAIMED4)
         .arg(secs);
-    return (deleteExpiredReclaimedLeasesCommon(secs, DELETE_LEASE4_STATE_EXPIRED));
+    return deleteExpiredReclaimedLeasesCommon(secs,
+                                              DELETE_LEASE4_STATE_EXPIRED);
 }
 
 uint64_t
@@ -1837,25 +1721,27 @@ CqlLeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t secs) {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_CQL_DELETE_EXPIRED_RECLAIMED6)
         .arg(secs);
-    return (deleteExpiredReclaimedLeasesCommon(secs, DELETE_LEASE6_STATE_EXPIRED));
+    return deleteExpiredReclaimedLeasesCommon(secs,
+                                              DELETE_LEASE6_STATE_EXPIRED);
 }
 
 uint64_t
-CqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
-        StatementIndex statement_index) {
+CqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(
+    const uint32_t secs, StatementIndex statement_index) {
     // Set up the WHERE clause value
 
     CqlDataArray data;
-    uint32_t result = 0;
+    uint64_t result = 0;
 
     // State is reclaimed.
-    uint32_t state = Lease::STATE_EXPIRED_RECLAIMED;
-    data.add(&state);
+    cass_int32_t state =
+        static_cast<cass_int32_t>(Lease::STATE_EXPIRED_RECLAIMED);
+    data.add(reinterpret_cast<void*>(&state));
 
     // Expiration timestamp.
-    uint64_t expiration = static_cast<int64_t>(time(NULL) -
-        static_cast<time_t>(secs));
-    data.add(&expiration);
+    cass_int64_t expiration =
+        static_cast<cass_int64_t>(time(NULL) - static_cast<time_t>(secs));
+    data.add(reinterpret_cast<void*>(&expiration));
 
     // Get the data
     Lease4Collection result4Leases;
@@ -1871,18 +1757,18 @@ CqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
         break;
     }
     for (Lease4Collection::iterator it = result4Leases.begin();
-            it != result4Leases.end(); ++it) {
+         it != result4Leases.end(); ++it) {
         if (deleteLease((*it)->addr_)) {
             result++;
         }
     }
     for (Lease6Collection::iterator it = result6Leases.begin();
-            it != result6Leases.end(); ++it) {
+         it != result6Leases.end(); ++it) {
         if (deleteLease((*it)->addr_)) {
             result++;
         }
     }
-    return (result);
+    return result;
 }
 
 std::string
@@ -1893,7 +1779,7 @@ CqlLeaseMgr::getName() const {
     } catch (...) {
         // Return an empty name
     }
-    return (name);
+    return name;
 }
 
 std::string
@@ -1901,29 +1787,42 @@ CqlLeaseMgr::getDescription() const {
     return std::string("Cassandra Database");
 }
 
-pair<uint32_t, uint32_t>
+std::pair<unsigned int, unsigned int>
 CqlLeaseMgr::getVersion() const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_CQL_GET_VERSION);
-    uint32_t version;
-    uint32_t minor;
+    cass_int32_t version;
+    cass_int32_t minor;
     CassError rc;
     CassStatement* statement = NULL;
     CassFuture* future = NULL;
 
     statement = cass_prepared_bind(dbconn_.statements_[GET_VERSION]);
-    if (NULL == statement) {
+    if (!statement) {
         isc_throw(DbOperationError, "unable to bind statement");
     }
 
+    if (dbconn_.force_consistency_) {
+        rc = cass_statement_set_consistency(statement, dbconn_.consistency_);
+        if (rc != CASS_OK) {
+            cass_statement_free(statement);
+            isc_throw(
+                DbOperationError,
+                "unable to set statement consistency for statement "
+                    << dbconn_.tagged_statements_[GET_VERSION].name_);
+        }
+    }
+
     future = cass_session_execute(dbconn_.session_, statement);
-    if (NULL == future) {
+    if (!future) {
         cass_statement_free(statement);
-        isc_throw(DbOperationError, "unable to execute statement");
+        isc_throw(DbOperationError,
+                  "unable to execute statement "
+                      << dbconn_.tagged_statements_[GET_VERSION].name_);
     }
     cass_future_wait(future);
     std::string error;
-    dbconn_.checkStatementError(error, future, "unable to GET");
+    dbconn_.checkStatementError(error, future, "unable to GET version");
     rc = cass_future_error_code(future);
     if (rc != CASS_OK) {
         cass_future_free(future);
@@ -1935,17 +1834,14 @@ CqlLeaseMgr::getVersion() const {
     const CassResult* resultCollection = cass_future_get_result(future);
     CassIterator* rows = cass_iterator_from_result(resultCollection);
     CqlDataArray data;
-    CqlDataArray size;
     while (cass_iterator_next(rows)) {
         const CassRow* row = cass_iterator_get_row(rows);
-        // version: uint32_t
+        // version: int
         data.add(reinterpret_cast<void*>(&version));
-        size.add(NULL);
-        // minor: uint32_t
+        // minor: int
         data.add(reinterpret_cast<void*>(&minor));
-        size.add(NULL);
-        for (size_t i = 0; i < data.size(); i++) {
-            CqlLeaseMgr::getData(row, i, data, size, i, *versionExchange_);
+        for (size_t i = 0U; i < data.size(); i++) {
+            CqlCommon::getData(row, i, i, *versionExchange_, data);
         }
     }
 
@@ -1954,18 +1850,21 @@ CqlLeaseMgr::getVersion() const {
     cass_future_free(future);
     cass_statement_free(statement);
 
-    return (make_pair(version, minor));
+    return std::pair<unsigned int, unsigned int>(
+        static_cast<unsigned int>(version), static_cast<unsigned int>(minor));
 }
 
 void
 CqlLeaseMgr::commit() {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_COMMIT);
+    dbconn_.commit();
 }
 
 void
 CqlLeaseMgr::rollback() {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_ROLLBACK);
+    dbconn_.rollback();
 }
 
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+}  // namespace dhcp
+}  // namespace isc
index 76f96c859e518f46258c38718b6a837e9bc9c8d8..bd5c4b9743c72ccf186545091af420c6ca30cbf6 100644 (file)
 #ifndef CQL_LEASE_MGR_H
 #define CQL_LEASE_MGR_H
 
+#include <cassandra.h>
+
 #include <dhcp/hwaddr.h>
-#include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/cql_connection.h>
+#include <dhcpsrv/cql_exchange.h>
+#include <dhcpsrv/lease_mgr.h>
+
 #include <boost/scoped_ptr.hpp>
 #include <boost/utility.hpp>
-#include <cassandra.h>
+
+#include <string>
+#include <utility>
 #include <vector>
 
 namespace isc {
 namespace dhcp {
 
-/// @brief Structure used to bind C++ input values to dynamic SQL parameters
-/// The structure contains a vector which store the input values,
-/// This vector is passed directly into the
-/// CQL execute call.
-///
-/// Note that the data values are stored as pointers. These pointers need
-/// to be valid for the duration of the CQL statement execution. In other
-/// words populating them with pointers to values that go out of scope
-/// before statement is executed is a bad idea.
-struct CqlDataArray {
-    /// Add void pointer to a vector of pointers to the data values.
-    std::vector<void*> values_;
-    void add(void* value) {
-        values_.push_back(value);
-    }
-    /// Remove void pointer from a vector of pointers to the data values.
-    void remove(int index) {
-        if (values_.size() <= index) {
-            isc_throw(BadValue, "Index " << index << " out of bounds: [0, " <<
-                (values_.size() - 1)  << "]");
-        }
-        values_.erase(values_.begin() + index);
-    }
-    /// Get size.
-    size_t size() {
-        return values_.size();
-    }
-};
-
 class CqlVersionExchange;
 class CqlLeaseExchange;
 class CqlLease4Exchange;
 class CqlLease6Exchange;
 
-/// @brief Cassandra Exchange
-///
-/// This class provides the basic conversion functions between Cassandra CQL and
-/// base types.
-class CqlExchange : public virtual SqlExchange {
-public:
-    // Time conversion methods.
-    static void
-    convertToDatabaseTime(const time_t& cltt,
-                          const uint32_t& valid_lifetime,
-                          uint64_t& expire) {
-        // Calculate expiry time. Store it in the 64-bit value so as we can
-        // detect overflows.
-        int64_t expire_time = static_cast<int64_t>(cltt) +
-            static_cast<int64_t>(valid_lifetime);
-
-        if (expire_time > DatabaseConnection::MAX_DB_TIME) {
-            isc_throw(BadValue, "Time value is too large: " << expire_time);
-        }
-
-        expire = expire_time;
-    }
-
-    static void
-    convertFromDatabaseTime(const uint64_t& expire,
-                            const uint32_t& valid_lifetime,
-                            time_t& cltt) {
-        // Convert to local time
-        cltt = expire - static_cast<int64_t>(valid_lifetime);
-    }
-};
-
 /// @brief Cassandra Lease Manager
 ///
 /// This class provides the \ref isc::dhcp::LeaseMgr interface to the Cassandra
-/// database. Use of this backend presupposes that a CQL database is available
+/// 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 {
 public:
-
     /// @brief Constructor
     ///
     /// Uses the following keywords in the parameters passed to it to
@@ -115,7 +59,7 @@ public:
     /// schema_version table will be checked against hard-coded value in
     /// the implementation file.
     ///
-    /// Finally, all the SQL commands are pre-compiled.
+    /// Finally, all the CQL commands are pre-compiled.
     ///
     /// @param parameters A data structure relating keywords and values
     ///        concerned with the database.
@@ -124,7 +68,7 @@ public:
     /// @throw isc::dhcp::DbOpenError Error opening the database
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    CqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters);
+    explicit CqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters);
 
     /// @brief Destructor (closes database)
     virtual ~CqlLeaseMgr();
@@ -225,9 +169,8 @@ public:
     /// @param subnet_id A subnet identifier.
     ///
     /// @return A pointer to the lease or NULL if the lease is not found.
-    /// @throw isc::NotImplemented On every call as this function is currently
-    /// not implemented for the PostgreSQL backend.
-    virtual Lease4Ptr getLease4(const ClientId& client_id, const HWAddr& hwaddr,
+    virtual Lease4Ptr getLease4(const ClientId& client_id,
+                                const HWAddr& hwaddr,
                                 SubnetID subnet_id) const;
 
     /// @brief Returns existing IPv4 lease for specified client-id
@@ -280,8 +223,8 @@ public:
     ///        lease type field.
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
-                                       uint32_t iaid) const;
+    virtual Lease6Collection
+    getLeases6(Lease::Type type, const DUID& duid, uint32_t iaid) const;
 
     /// @brief Returns existing IPv6 lease for a given DUID+IA combination
     ///
@@ -296,8 +239,10 @@ public:
     ///        lease type field.
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
-                                        uint32_t iaid, SubnetID subnet_id) const;
+    virtual Lease6Collection getLeases6(Lease::Type type,
+                                        const DUID& duid,
+                                        uint32_t iaid,
+                                        SubnetID subnet_id) const;
     /// @brief Returns a collection of expired DHCPv6 leases.
     ///
     /// This method returns at most @c max_leases expired leases. The leases
@@ -308,8 +253,8 @@ public:
     /// by the database backend are added.
     /// @param max_leases A maximum number of leases to be returned. If this
     /// value is set to 0, all expired (but not reclaimed) leases are returned.
-    virtual void getExpiredLeases6(Lease6Collection& ,
-                                   const size_t ) const;
+    virtual void getExpiredLeases6(Lease6Collection& expired_leases,
+                                   const size_t max_leases) const;
 
     /// @brief Returns a collection of expired DHCPv4 leases.
     ///
@@ -321,8 +266,8 @@ public:
     /// by the database backend are added.
     /// @param max_leases A maximum number of leases to be returned. If this
     /// value is set to 0, all expired (but not reclaimed) leases are returned.
-    virtual void getExpiredLeases4(Lease4Collection& ,
-                                   const size_t ) const;
+    virtual void getExpiredLeases4(Lease4Collection& expired_leases,
+                                   const size_t max_leases) const;
 
     /// @brief Updates IPv4 lease.
     ///
@@ -368,7 +313,7 @@ public:
     /// time will not be deleted.
     ///
     /// @return Number of leases deleted.
-    virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t );
+    virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t secs);
 
     /// @brief Deletes all expired and reclaimed DHCPv6 leases.
     ///
@@ -377,7 +322,7 @@ public:
     /// time will not be deleted.
     ///
     /// @return Number of leases deleted.
-    virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t );
+    virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs);
 
     /// @brief Return backend type
     ///
@@ -405,7 +350,7 @@ public:
     ///
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    virtual std::pair<uint32_t, uint32_t> getVersion() const;
+    virtual std::pair<unsigned int, unsigned int> getVersion() const;
 
     /// @brief Commit Transactions
     ///
@@ -419,70 +364,35 @@ public:
 
     /// @brief Statement Tags
     ///
-    /// The contents of the enum are indexes into the list of compiled SQL
+    /// The contents of the enum are indexes into the list of compiled CQL
     /// statements
     enum StatementIndex {
-        DELETE_LEASE4,              // Delete from lease4 by address
-        DELETE_LEASE4_STATE_EXPIRED,// Delete expired lease4s in certain state
-        DELETE_LEASE6,              // Delete from lease6 by address
-        DELETE_LEASE6_STATE_EXPIRED,// Delete expired lease6s in certain state
-        GET_LEASE4_ADDR,            // Get lease4 by address
-        GET_LEASE4_CLIENTID,        // Get lease4 by client ID
-        GET_LEASE4_CLIENTID_SUBID,  // Get lease4 by client ID & subnet ID
-        GET_LEASE4_HWADDR,          // Get lease4 by HW address
-        GET_LEASE4_HWADDR_SUBID,    // Get lease4 by HW address & subnet ID
-        GET_LEASE4_EXPIRE,          // Get expired lease4
-        GET_LEASE6_ADDR,            // Get lease6 by address
-        GET_LEASE6_DUID_IAID,       // Get lease6 by DUID and IAID
-        GET_LEASE6_DUID_IAID_SUBID, // Get lease6 by DUID, IAID and subnet ID
-        GET_LEASE6_EXPIRE,          // Get expired lease6
-        GET_VERSION,                // Obtain version number
-        INSERT_LEASE4,              // Add entry to lease4 table
-        INSERT_LEASE6,              // Add entry to lease6 table
-        UPDATE_LEASE4,              // Update a Lease4 entry
-        UPDATE_LEASE6,              // Update a Lease6 entry
-        NUM_STATEMENTS              // Number of statements
+        DELETE_LEASE4,                // Delete from lease4 by address
+        DELETE_LEASE4_STATE_EXPIRED,  // Delete expired lease4s in certain
+                                      // state
+        DELETE_LEASE6,                // Delete from lease6 by address
+        DELETE_LEASE6_STATE_EXPIRED,  // Delete expired lease6s in certain
+                                      // state
+        GET_LEASE4_ADDR,              // Get lease4 by address
+        GET_LEASE4_CLIENTID,          // Get lease4 by client ID
+        GET_LEASE4_CLIENTID_SUBID,    // Get lease4 by client ID & subnet ID
+        GET_LEASE4_HWADDR,            // Get lease4 by HW address
+        GET_LEASE4_HWADDR_SUBID,      // Get lease4 by HW address & subnet ID
+        GET_LEASE4_EXPIRE,            // Get expired lease4
+        GET_LEASE6_ADDR,              // Get lease6 by address
+        GET_LEASE6_DUID_IAID,         // Get lease6 by DUID and IAID
+        GET_LEASE6_DUID_IAID_SUBID,   // Get lease6 by DUID, IAID and subnet
+                                      // ID
+        GET_LEASE6_EXPIRE,            // Get expired lease6
+        GET_VERSION,                  // Obtain version number
+        INSERT_LEASE4,                // Add entry to lease4 table
+        INSERT_LEASE6,                // Add entry to lease6 table
+        UPDATE_LEASE4,                // Update a Lease4 entry
+        UPDATE_LEASE6,                // Update a Lease6 entry
+        NUM_STATEMENTS                // Number of statements
     };
 
-    /// @brief Binds data specified in data
-    ///
-    /// This method calls one of cass_value_bind_{none,bool,int32,int64,string,bytes}.
-    /// It is used to bind C++ data types used by Kea into formats used by Cassandra.
-    ///
-    /// @param statement Statement object representing the query
-    /// @param stindex Index of statement being executed
-    /// @param data array that has been created for the type of lease in question.
-    /// @param exchange Exchange object to use
-    static void bindData(CassStatement* statement, const StatementIndex stindex,
-        CqlDataArray& data, const SqlExchange& exchange);
-
-    /// @brief Returns type of data for specific parameter.
-    ///
-    /// Returns type of a given parameter of a given statement.
-    ///
-    /// @param stindex Index of statement being executed.
-    /// @param pindex Index of the parameter for a given statement.
-    /// @param exchange Exchange object to use
-    static ExchangeDataType getDataType(const StatementIndex stindex, int pindex,
-        const SqlExchange& exchange);
-
-    /// @brief Retrieves data returned by Cassandra.
-    ///
-    /// This method calls one of cass_value_get_{none,bool,int32,int64,string,bytes}.
-    /// It is used to retrieve data returned by Cassandra into standard C++ types
-    /// used by Kea.
-    ///
-    /// @param row row of data returned by CQL library
-    /// @param pindex Index of statement being executed
-    /// @param data array that has been created for the type of lease in question.
-    /// @param size a structure that holds information about size
-    /// @param dindex data index (specifies which entry in size array is used)
-    /// @param exchange Exchange object to use
-    static void getData(const CassRow* row, const int pindex, CqlDataArray& data,
-        CqlDataArray& size, const int dindex, const SqlExchange& exchange);
-
 private:
-
     /// @brief Add Lease Common Code
     ///
     /// This method performs the common actions for both flavours (V4 and V6)
@@ -499,8 +409,9 @@ private:
     ///
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    bool addLeaseCommon(StatementIndex stindex, CqlDataArray& data,
-        CqlLeaseExchange& exchange);
+    bool addLeaseCommon(StatementIndex stindex,
+                        CqlDataArray& data,
+                        CqlLeaseExchange& exchange);
 
     /// @brief Get Lease Collection Common Code
     ///
@@ -523,8 +434,10 @@ private:
     /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
     ///        from the database where only one was expected.
     template <typename Exchange, typename LeaseCollection>
-    void getLeaseCollection(StatementIndex stindex, CqlDataArray& data_array,
-                            Exchange& exchange, LeaseCollection& result,
+    void getLeaseCollection(StatementIndex stindex,
+                            CqlDataArray& data_array,
+                            Exchange& exchange,
+                            LeaseCollection& result,
                             bool single = false) const;
 
     /// @brief Gets Lease4 Collection
@@ -543,7 +456,8 @@ private:
     ///        failed.
     /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
     ///        from the database where only one was expected.
-    void getLeaseCollection(StatementIndex stindex, CqlDataArray& data,
+    void getLeaseCollection(StatementIndex stindex,
+                            CqlDataArray& data,
                             Lease4Collection& result) const {
         getLeaseCollection(stindex, data, exchange4_, result);
     }
@@ -563,7 +477,8 @@ private:
     ///        failed.
     /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
     ///        from the database where only one was expected.
-    void getLeaseCollection(StatementIndex stindex, CqlDataArray& data,
+    void getLeaseCollection(StatementIndex stindex,
+                            CqlDataArray& data,
                             Lease6Collection& result) const {
         getLeaseCollection(stindex, data, exchange6_, result);
     }
@@ -577,7 +492,8 @@ private:
     /// @param stindex Index of statement being executed
     /// @param data array containing input parameters for the query
     /// @param lease Lease4 object returned
-    void getLease(StatementIndex stindex, CqlDataArray& data,
+    void getLease(StatementIndex stindex,
+                  CqlDataArray& data,
                   Lease4Ptr& result) const;
 
     /// @brief Get Lease6 Common Code
@@ -589,7 +505,8 @@ private:
     /// @param stindex Index of statement being executed
     /// @param data array containing input parameters for the query
     /// @param lease Lease6 object returned
-    void getLease(StatementIndex stindex, CqlDataArray& data,
+    void getLease(StatementIndex stindex,
+                  CqlDataArray& data,
                   Lease6Ptr& result) const;
 
     /// @brief Get expired leases common code.
@@ -606,7 +523,7 @@ private:
     ///        @c GET_LEASE6_EXPIRE.
     ///
     /// @tparam One of the @c Lease4Collection or @c Lease6Collection.
-    template<typename LeaseCollection>
+    template <typename LeaseCollection>
     void getExpiredLeasesCommon(LeaseCollection& expired_leases,
                                 const size_t max_leases,
                                 StatementIndex statement_index) const;
@@ -626,9 +543,9 @@ private:
     ///        the address given.
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    template <typename LeasePtr>
-    void updateLeaseCommon(StatementIndex stindex, CqlDataArray& data,
-                           const LeasePtr& lease, CqlLeaseExchange& exchange);
+    void updateLeaseCommon(StatementIndex stindex,
+                           CqlDataArray& data,
+                           CqlLeaseExchange& exchange);
 
     /// @brief Delete lease common code
     ///
@@ -644,8 +561,9 @@ private:
     ///
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    bool deleteLeaseCommon(StatementIndex stindex, CqlDataArray& data,
-        CqlLeaseExchange& exchange);
+    bool deleteLeaseCommon(StatementIndex stindex,
+                           CqlDataArray& data,
+                           CqlLeaseExchange& exchange);
 
     /// @brief Delete expired-reclaimed leases.
     ///
@@ -659,21 +577,27 @@ private:
     uint64_t deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
                                                 StatementIndex statement_index);
 
-    /// CQL queries used by CQL backend
+    /// @brief CQL queries used by CQL backend
     static CqlTaggedStatement tagged_statements_[];
-    /// Database connection object
-    CqlConnection dbconn_;
 
+    /// @brief Database connection object
+    mutable CqlConnection dbconn_;
+
+    /// @{
     /// The exchange objects are used for transfer of data to/from the database.
     /// They are pointed-to objects as the contents may change in "const" calls,
     /// while the rest of this object does not. (At alternative would be to
     /// declare them as "mutable".)
-    boost::scoped_ptr<CqlLease4Exchange> exchange4_; ///< Exchange object
-    boost::scoped_ptr<CqlLease6Exchange> exchange6_; ///< Exchange object
-    boost::scoped_ptr<CqlVersionExchange> versionExchange_; ///< Exchange object
+    /// @brief Exchange object for IPv4
+    boost::scoped_ptr<CqlLease4Exchange> exchange4_;
+    /// @brief Exchange object for IPv6
+    boost::scoped_ptr<CqlLease6Exchange> exchange6_;
+    /// @brief Exchange object for version
+    boost::scoped_ptr<CqlVersionExchange> versionExchange_;
+    /// @}
 };
 
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+}  // namespace dhcp
+}  // namespace isc
 
-#endif // CQL_LEASE_MGR_H
+#endif  // CQL_LEASE_MGR_H
index 9fa51bd16d2e2925a8d6e0473bd1307c29b15bf4..32d3d43d6ce86ef17f12813371ea3a07298d3f71 100644 (file)
@@ -172,6 +172,9 @@ with the specified address to the Cassandra backend database.
 A debug message issued when the server is about to add an IPv6 lease
 with the specified address to the Cassandra backend database.
 
+% DHCPSRV_CQL_BEGIN_TRANSACTION committing to Cassandra database.
+The server has issued a begin transaction call.
+
 % DHCPSRV_CQL_COMMIT committing to Cassandra database
 A commit call been issued on the server. For Cassandra, this is a no-op.
 
@@ -181,6 +184,13 @@ V6) is about to open a Cassandra lease database.  The parameters of
 the connection including database name and username needed to access it
 (but not the password if any) are logged.
 
+% DHCPSRV_CQL_DEALLOC_ERROR An error occurred while closing the CQL connection: %1
+This is an error message issued when a DHCP server (either V4 or V6) experienced
+and error freeing CQL database resources as part of closing its connection to
+the Cassandra database. The connection is closed as part of normal server
+shutdown. This error is most likely a programmatic issue that is highly
+unlikely to occur or negatively impact server operation.
+
 % DHCPSRV_CQL_DELETE_ADDR deleting lease for address %1
 A debug message issued when the server is attempting to delete a lease from the
 Cassandra database for the specified address.
@@ -266,6 +276,30 @@ lease from the Cassandra database for the specified address.
 A debug message issued when the server is attempting to update IPv6
 lease from the Cassandra database for the specified address.
 
+% DHCPSRV_CQL_HOST_DB Connecting to CQL hosts database: %1
+An informational message logged when the CQL hosts database is about to be
+connected to. The parameters of the connection including database name and
+username needed to access it (but not the password if any) are logged.
+
+% DHCPSRV_CQL_HOST_DB_GET_VERSION obtaining schema version information for the CQL hosts database
+A debug message issued when the server is about to obtain schema version
+information from the CQL hosts database.
+
+% DHCPSRV_CQL_HOST_ADD Adding host information to the database
+An informational message logged when options belonging to any reservation from a
+single host are inserted.
+
+% DHCPSRV_CQL_HOST_GET_ALL Retrieving multiple hosts from a CQL database
+An informational message logged when multiple hosts from a CQL database are retrieved.
+
+% DHCPSRV_CQL_HOST_GET4 Retrieving one DHCPv4 host from a CQL database
+An informational message logged when a DHCP server is about to retrieve one
+host from a CQL database by IPv4 criteria.
+
+% DHCPSRV_CQL_HOST_GET6 Retrieving one DHCPv6 host from a CQL database
+An informational message logged when a DHCP server is about to retrieve one
+host from a CQL database by IPv6 criteria.
+
 % DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET received bad DHCPv4o6 packet: %1
 A bad DHCPv4o6 packet was received.
 
@@ -709,7 +743,7 @@ with the specified address to the PostgreSQL backend database.
 A debug message issued when the server is about to add an IPv6 lease
 with the specified address to the PostgreSQL backend database.
 
-% DHCPSRV_PGSQL_COMMIT committing to MySQL database
+% DHCPSRV_PGSQL_COMMIT committing to PostgreSQL database
 The code has issued a commit call.  All outstanding transactions will be
 committed to the database.  Note that depending on the PostgreSQL settings,
 the committal may not include a write to disk.
index fa080e67ce5d41aaad188b57b7ec2c4376b031e5..40619a81cc5f721d3eb5b9f9c405e004f03f51ca 100644 (file)
@@ -22,7 +22,7 @@
 namespace isc {
 namespace dhcp {
 
-/// @brief HostID (used only when storing in MySQL or Postgres)
+/// @brief HostID (used only when storing in MySQL, PostgreSQL or Cassandra)
 typedef uint64_t HostID;
 
 /// @brief IPv6 reservation for a host.
@@ -531,13 +531,13 @@ public:
     /// @brief Returns information about the host in the textual format.
     std::string toText() const;
 
-    /// @brief Sets Host ID (primary key in MySQL and Postgres backends)
+    /// @brief Sets Host ID (primary key in MySQL, PostgreSQL and Cassandra backends)
     /// @param id HostId value
     void setHostId(HostID id) {
         host_id_ = id;
     }
 
-    /// @brief Returns Host ID (primary key in MySQL and Postgres backends)
+    /// @brief Returns Host ID (primary key in MySQL, PostgreSQL and Cassandra backends)
     /// @return id HostId value (or 0 if not set)
     HostID getHostId() const {
         return (host_id_);
@@ -584,7 +584,7 @@ private:
     std::string boot_file_name_;
 
     /// @brief HostID (a unique identifier assigned when the host is stored in
-    ///                MySQL or Pgsql)
+    ///     MySQL, PostgreSQL or Cassandra)
     uint64_t host_id_;
 
     /// @brief Pointer to the DHCPv4 option data configuration for this host.
index 380d6c8a3b3392cf904213209e6e5dd6b869b1b6..d8e9bf18697e8362fd1c9cb321f714ce4fb654fc 100644 (file)
 #include <dhcpsrv/pgsql_host_data_source.h>
 #endif
 
+#ifdef HAVE_CQL
+#include <dhcpsrv/cql_host_data_source.h>
+#endif
+
 #include <boost/algorithm/string.hpp>
 #include <boost/foreach.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -76,8 +80,10 @@ HostDataSourceFactory::create(const std::string& dbaccess) {
 
 #ifdef HAVE_CQL
     if (db_type == "cql") {
-        isc_throw(NotImplemented, "Sorry, CQL backend for host reservations "
-                  "is not implemented yet.");
+        LOG_INFO(dhcpsrv_logger, DHCPSRV_CQL_HOST_DB)
+            .arg(DatabaseConnection::redactedAccessString(parameters));
+        getHostDataSourcePtr().reset(new CqlHostDataSource(parameters));
+        return;
     }
 #endif
 
index a07cd476488943551f33a082f0343f2b2d8762ac..8361813ad57183b1cad375f1c9c57f3cda58f651 100644 (file)
@@ -15,6 +15,7 @@
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/db_exceptions.h>
+#include <dhcpsrv/sql_common.h>
 
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
 namespace isc {
 namespace dhcp {
 
-/// @brief Used to map server data types with internal backend storage data
-/// types.
-enum ExchangeDataType {
-    EXCHANGE_DATA_TYPE_NONE,
-    EXCHANGE_DATA_TYPE_BOOL,
-    EXCHANGE_DATA_TYPE_INT32,
-    EXCHANGE_DATA_TYPE_INT64,
-    EXCHANGE_DATA_TYPE_TIMESTAMP,
-    EXCHANGE_DATA_TYPE_STRING,
-    EXCHANGE_DATA_TYPE_BYTES
-};
-
-/// @brief Used to specify the direction of the data exchange between the
-/// database and the server.
-enum ExchangeDataTypeIO {
-    EXCHANGE_DATA_TYPE_IO_IN,
-    EXCHANGE_DATA_TYPE_IO_OUT,
-    EXCHANGE_DATA_TYPE_IO_IN_OUT
-};
-
-/// @brief Used to map the column name with internal backend storage data types.
-struct ExchangeColumnInfo {
-    ExchangeColumnInfo () : name_(""), index_(0), type_io_(EXCHANGE_DATA_TYPE_IO_IN),
-                            type_(EXCHANGE_DATA_TYPE_NONE) {};
-    ExchangeColumnInfo (const char* name, const uint32_t index,
-        const ExchangeDataTypeIO type_io, const ExchangeDataType type) :
-        name_(name), index_(index), type_io_(type_io), type_(type) {};
-    std::string name_;
-    uint32_t index_;
-    ExchangeDataTypeIO type_io_;
-    ExchangeDataType type_;
-};
-
-typedef boost::shared_ptr<ExchangeColumnInfo> ExchangeColumnInfoPtr;
-
-typedef boost::multi_index_container<
-    // Container comprises elements of ExchangeColumnInfoPtr type.
-    ExchangeColumnInfoPtr,
-    // Here we start enumerating various indexes.
-    boost::multi_index::indexed_by<
-        // Sequenced index allows accessing elements in the same way as elements
-        // in std::list.
-        // Sequenced is an index #0.
-        boost::multi_index::sequenced<>,
-        // Start definition of index #1.
-        boost::multi_index::hashed_non_unique<
-            boost::multi_index::member<
-                ExchangeColumnInfo,
-                std::string,
-                &ExchangeColumnInfo::name_
-             >
-        >,
-        // Start definition of index #2.
-        boost::multi_index::hashed_non_unique<
-            boost::multi_index::member<
-                ExchangeColumnInfo,
-                uint32_t,
-                &ExchangeColumnInfo::index_
-            >
-        >
-    >
-> ExchangeColumnInfoContainer;
-
-/// Pointer to the ExchangeColumnInfoContainer object.
-typedef boost::shared_ptr<ExchangeColumnInfoContainer> ExchangeColumnInfoContainerPtr;
-/// Type of the index #1 - name.
-typedef ExchangeColumnInfoContainer::nth_index<1>::type ExchangeColumnInfoContainerName;
-/// Pair of iterators to represent the range of ExchangeColumnInfo having the
-/// same name value. The first element in this pair represents
-/// the beginning of the range, the second element represents the end.
-typedef std::pair<ExchangeColumnInfoContainerName::const_iterator,
-                  ExchangeColumnInfoContainerName::const_iterator> ExchangeColumnInfoContainerNameRange;
-/// Type of the index #2 - index.
-typedef ExchangeColumnInfoContainer::nth_index<2>::type ExchangeColumnInfoContainerIndex;
-/// Pair of iterators to represent the range of ExchangeColumnInfo having the
-/// same index value. The first element in this pair represents
-/// the beginning of the range, the second element represents the end.
-typedef std::pair<ExchangeColumnInfoContainerIndex::const_iterator,
-                  ExchangeColumnInfoContainerIndex::const_iterator> ExchangeColumnInfoContainerIndexRange;
-
-class SqlExchange {
-public:
-    SqlExchange () {};
-    virtual ~SqlExchange() {};
-    ExchangeColumnInfoContainer parameters_;   ///< Column names and types
-};
-
 /// @brief Contains a single row of lease statistical data
 ///
 /// The contents of the row consist of a subnet ID, a lease
index b829748c93a63e330bcb94d4069338e9c4b1c479..74b9bcf432350f3ec6964892d7f89bee6373fceb 100644 (file)
@@ -2519,7 +2519,11 @@ MySqlHostDataSource::get4(const SubnetID& subnet_id,
 ConstHostPtr
 MySqlHostDataSource::get4(const SubnetID& subnet_id,
                           const asiolink::IOAddress& address) const {
-    /// @todo: check that address is really v4, not v6.
+    // Check that address is IPv4, not IPv6.
+    if (!address.isV4()) {
+        isc_throw(BadValue, "MySqlHostDataSource::get4(2): wrong address type, "
+                            "address supplied is not an IPv4 address");
+    }
 
     // Set up the WHERE clause value
     MYSQL_BIND inbind[2];
index 6b7fa51eec46bc0f818a72e38c6fb614b9002f9c..2056b94b830c1b686365b7ff5ccefb31f16d9d66 100644 (file)
@@ -53,7 +53,8 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
     // 2. Update the copy with the passed keywords.
     BOOST_FOREACH(ConfigPair param, config_value->mapValue()) {
         try {
-            if ((param.first == "persist") || (param.first == "readonly")) {
+            if ((param.first == "persist") || (param.first == "readonly") ||
+                (param.first == "tcp-nodelay")) {
                 values_copy[param.first] = (param.second->boolValue() ?
                                             "true" : "false");
 
@@ -67,6 +68,21 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
                 values_copy[param.first] =
                     boost::lexical_cast<std::string>(timeout);
 
+            } else if (param.first == "reconnect-wait-time") {
+                timeout = param.second->intValue();
+                values_copy[param.first] =
+                    boost::lexical_cast<std::string>(timeout);
+
+            } else if (param.first == "request-timeout") {
+                timeout = param.second->intValue();
+                values_copy[param.first] =
+                    boost::lexical_cast<std::string>(timeout);
+
+            } else if (param.first == "tcp-keepalive") {
+                timeout = param.second->intValue();
+                values_copy[param.first] =
+                    boost::lexical_cast<std::string>(timeout);
+
             } else {
                 values_copy[param.first] = param.second->stringValue();
             }
diff --git a/src/lib/dhcpsrv/sql_common.h b/src/lib/dhcpsrv/sql_common.h
new file mode 100644 (file)
index 0000000..bc23dbf
--- /dev/null
@@ -0,0 +1,152 @@
+// Copyright (C) 2016 Deutsche Telekom AG.
+//
+// Author: Cristian Secăreanu <cristian.secareanu@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 SQL_COMMON_H
+#define SQL_COMMON_H
+
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Used to map server data types with internal backend storage data
+/// types.
+enum ExchangeDataType {
+    EXCHANGE_DATA_TYPE_NONE,
+    EXCHANGE_DATA_TYPE_BOOL,
+    EXCHANGE_DATA_TYPE_INT32,
+    EXCHANGE_DATA_TYPE_INT64,
+    EXCHANGE_DATA_TYPE_TIMESTAMP,
+    EXCHANGE_DATA_TYPE_STRING,
+    EXCHANGE_DATA_TYPE_BYTES,
+    EXCHANGE_DATA_TYPE_UUID
+};
+
+/// @brief Used to specify the direction of the data exchange between the
+/// database and the server.
+enum ExchangeDataTypeIO {
+    EXCHANGE_DATA_TYPE_IO_IN,
+    EXCHANGE_DATA_TYPE_IO_OUT,
+    EXCHANGE_DATA_TYPE_IO_IN_OUT
+};
+
+/// @brief Used to map the column name with internal backend storage data types.
+struct ExchangeColumnInfo {
+    ExchangeColumnInfo()
+        : name_(""), index_(0), type_io_(EXCHANGE_DATA_TYPE_IO_IN_OUT),
+          type_(EXCHANGE_DATA_TYPE_NONE){};
+    ExchangeColumnInfo(const char* name,
+                       const uint32_t index,
+                       const ExchangeDataTypeIO type_io,
+                       const ExchangeDataType type)
+        : name_(name), index_(index), type_io_(type_io), type_(type){};
+    std::string name_;
+    uint32_t index_;
+    ExchangeDataTypeIO type_io_;
+    ExchangeDataType type_;
+};
+
+/// @brief Smart pointer to an @ref ExchangeColumnInfo
+typedef boost::shared_ptr<ExchangeColumnInfo> ExchangeColumnInfoPtr;
+
+/// @brief Multimap that allows indexing @ref ExchangeColumnInfoPtr
+///     sequentially, by index and by name.
+typedef boost::multi_index_container<
+    // Container comprises elements of ExchangeColumnInfoPtr type.
+    ExchangeColumnInfoPtr,
+    // Here we start enumerating various indexes.
+    boost::multi_index::indexed_by<
+        // Index #0. Sequenced index allows accessing elements in the same way
+        // as in std::list.
+        boost::multi_index::sequenced<>,
+        // Index #1
+        boost::multi_index::hashed_non_unique<
+            boost::multi_index::member<
+                ExchangeColumnInfo,
+                std::string,
+                &ExchangeColumnInfo::name_
+             >
+        >,
+        // Index #2
+        boost::multi_index::hashed_non_unique<
+            boost::multi_index::member<
+                ExchangeColumnInfo,
+                uint32_t,
+                &ExchangeColumnInfo::index_
+            >
+        >
+    >
+> ExchangeColumnInfoContainer;
+
+/// @brief Pointer to the ExchangeColumnInfoContainer object.
+typedef boost::shared_ptr<ExchangeColumnInfoContainer>
+    ExchangeColumnInfoContainerPtr;
+
+/// @brief Type of the index #1 - name.
+typedef ExchangeColumnInfoContainer::nth_index<1>::type
+    ExchangeColumnInfoContainerName;
+
+/// @brief Pair of iterators to represent the range of ExchangeColumnInfo having
+/// the
+/// same name value. The first element in this pair represents
+/// the beginning of the range, the second element represents the end.
+typedef std::pair<ExchangeColumnInfoContainerName::const_iterator,
+                  ExchangeColumnInfoContainerName::const_iterator>
+    ExchangeColumnInfoContainerNameRange;
+
+/// @brief Type of the index #2 - index.
+typedef ExchangeColumnInfoContainer::nth_index<2>::type
+    ExchangeColumnInfoContainerIndex;
+
+/// @brief Pair of iterators to represent the range of ExchangeColumnInfo having
+/// the
+/// same index value. The first element in this pair represents
+/// the beginning of the range, the second element represents the end.
+typedef std::pair<ExchangeColumnInfoContainerIndex::const_iterator,
+                  ExchangeColumnInfoContainerIndex::const_iterator>
+    ExchangeColumnInfoContainerIndexRange;
+
+/// @brief Base class for backend exchanges.
+class SqlExchange {
+public:
+    /// @brief Constructor
+    SqlExchange() {
+    }
+
+    /// @brief Destructor
+    virtual ~SqlExchange() {
+    }
+
+    /// @brief Column names and types
+    ExchangeColumnInfoContainer parameters_;
+};
+
+}  // namespace dhcp
+}  // namespace isc
+
+#endif  // SQL_COMMON_H
index 8729de987d38668a598d8d44766320df6fe232c6..70aca24d02fbae938caefef3236dced37bb749c0 100644 (file)
@@ -119,6 +119,7 @@ libdhcpsrv_unittests_SOURCES += pgsql_lease_mgr_unittest.cc
 endif
 if HAVE_CQL
 libdhcpsrv_unittests_SOURCES += cql_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += cql_host_data_source_unittest.cc
 endif
 libdhcpsrv_unittests_SOURCES += pool_unittest.cc
 libdhcpsrv_unittests_SOURCES += srv_config_unittest.cc
diff --git a/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc
new file mode 100644 (file)
index 0000000..5e7e697
--- /dev/null
@@ -0,0 +1,524 @@
+// Copyright (C) 2015-2016 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/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/cql_connection.h>
+#include <dhcpsrv/cql_host_data_source.h>
+#include <dhcpsrv/cql_lease_mgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/tests/generic_host_data_source_unittest.h>
+#include <dhcpsrv/tests/test_utils.h>
+#include <dhcpsrv/testutils/cql_schema.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace std;
+
+namespace {
+
+class CqlHostDataSourceTest : public GenericHostDataSourceTest {
+public:
+    /// @brief Clears the database and opens connection to it.
+    void initializeTest() {
+        // Ensure schema is the correct one.
+        destroyCqlSchema(false, true);
+        createCqlSchema(false, true);
+
+        // Connect to the database
+        try {
+            HostDataSourceFactory::create(validCqlConnectionString());
+        } catch (...) {
+            std::cerr << "*** ERROR: unable to open database. The test"
+                         "*** environment is broken and must be fixed before"
+                         "*** the CQL tests will run correctly."
+                         "*** The reason for the problem is described in the"
+                         "*** accompanying exception output.";
+            throw;
+        }
+
+        hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
+    }
+
+    /// @brief Destroys the HDS and the schema.
+    void destroyTest() {
+        hdsptr_->rollback();
+        HostDataSourceFactory::destroy();
+        destroyCqlSchema(false, true);
+    }
+
+    /// @brief Constructor
+    ///
+    /// Deletes everything from the database and opens it.
+    CqlHostDataSourceTest(){
+        initializeTest();
+    }
+
+    /// @brief Destructor
+    ///
+    /// Rolls back all pending transactions.  The deletion of myhdsptr_ will
+    /// close the database.  Then reopen it and delete everything created by the
+    /// test.
+    virtual ~CqlHostDataSourceTest() {
+        destroyTest();
+    }
+
+    /// @brief Reopen the database
+    ///
+    /// Closes the database and re-open it.  Anything committed should be
+    /// visible.
+    ///
+    /// Parameter is ignored for CQL backend as the v4 and v6 leases share
+    /// the same database.
+    void reopen(Universe) {
+        HostDataSourceFactory::destroy();
+        HostDataSourceFactory::create(validCqlConnectionString());
+        hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
+    }
+};
+
+/// @brief Check that database can be opened
+///
+/// This test checks if the CqlHostDataSource can be instantiated.  This happens
+/// only if the database can be opened.  Note that this is not part of the
+/// CqlLeaseMgr test fixure set.  This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+
+TEST(CqlHostDataSource, OpenDatabase) {
+    // Schema needs to be created for the test to work.
+    destroyCqlSchema(false, true);
+    createCqlSchema(false, true);
+
+    // Check that lease manager open the database opens correctly and tidy up.
+    //  If it fails, print the error message.
+    try {
+        HostDataSourceFactory::create(validCqlConnectionString());
+        EXPECT_NO_THROW((void)HostDataSourceFactory::getHostDataSourcePtr());
+        HostDataSourceFactory::destroy();
+    } catch (const isc::Exception& ex) {
+        FAIL() << "*** ERROR: unable to open database, reason:\n"
+               << "    " << ex.what() << "\n"
+               << "*** The test environment is broken and must be fixed\n"
+               << "*** before the CQL tests will run correctly.\n";
+    }
+
+    // Check that lease manager open the database opens correctly with a longer
+    // timeout.  If it fails, print the error message.
+    try {
+        std::string connection_string = validCqlConnectionString() +
+                                        std::string(" ") +
+                                        std::string(VALID_TIMEOUT);
+        HostDataSourceFactory::create(connection_string);
+        EXPECT_NO_THROW((void)HostDataSourceFactory::getHostDataSourcePtr());
+        HostDataSourceFactory::destroy();
+    } catch (const isc::Exception& ex) {
+        FAIL() << "*** ERROR: unable to open database, reason:\n"
+               << "    " << ex.what() << "\n"
+               << "*** The test environment is broken and must be fixed\n"
+               << "*** before the CQL tests will run correctly.\n";
+    }
+
+    // Check that attempting to get an instance of the host data source when
+    // none is set throws an exception.
+    EXPECT_FALSE(HostDataSourceFactory::getHostDataSourcePtr());
+
+    // Check that wrong specification of backend throws an exception.
+    // (This is really a check on HostDataSourceFactory, but is convenient to
+    // perform here.)
+    EXPECT_THROW(
+        HostDataSourceFactory::create(connectionString(
+            NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+        InvalidParameter);
+    EXPECT_THROW(
+        HostDataSourceFactory::create(connectionString(
+            INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+        InvalidType);
+
+    // Check that invalid login data does not cause an exception, CQL should use
+    // default values.
+    EXPECT_NO_THROW(HostDataSourceFactory::create(connectionString(
+        CQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)));
+    EXPECT_NO_THROW(HostDataSourceFactory::create(connectionString(
+        CQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)));
+    EXPECT_NO_THROW(HostDataSourceFactory::create(connectionString(
+        CQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)));
+    EXPECT_NO_THROW(HostDataSourceFactory::create(connectionString(
+        CQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)));
+
+    // Check that invalid timeouts throw DbOperationError.
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+                     CQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER,
+                     VALID_PASSWORD, INVALID_TIMEOUT_1)),
+                 DbOperationError);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+                     CQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER,
+                     VALID_PASSWORD, INVALID_TIMEOUT_2)),
+                 DbOperationError);
+
+    // Check that CQL allows the hostname to not be specified.
+    EXPECT_NO_THROW(HostDataSourceFactory::create(connectionString(
+        CQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)));
+
+    // Tidy up after the test
+    destroyCqlSchema(false, true);
+}
+
+/// @brief Check conversion functions
+///
+/// The server works using cltt and valid_filetime.  In the database, the
+/// information is stored as expire_time and valid-lifetime, which are
+/// related by
+///
+/// expire_time = cltt + valid_lifetime
+///
+/// This test checks that the conversion is correct.  It does not check that the
+/// data is entered into the database correctly, only that the CQL_TIME
+/// structure used for the entry is correctly set up.
+TEST(CqlConnection, checkTimeConversion) {
+    const time_t cltt = time(NULL);
+    const uint32_t valid_lft = 86400;  // 1 day
+    cass_int64_t cql_expire;
+
+    // Convert to the database time
+    CqlExchange::convertToDatabaseTime(cltt, valid_lft, cql_expire);
+
+    // Convert back
+    time_t converted_cltt = 0;
+    CqlExchange::convertFromDatabaseTime(cql_expire, valid_lft, converted_cltt);
+    EXPECT_EQ(cltt, converted_cltt);
+}
+// Test verifies if a host reservation can be added and later retrieved by IPv4
+// address. Host uses hw address as identifier.
+TEST_F(CqlHostDataSourceTest, basic4HWAddr) {
+    testBasic4(Host::IDENT_HWADDR);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by IPv4
+// address. Host uses client-id (DUID) as identifier.
+TEST_F(CqlHostDataSourceTest, basic4ClientId) {
+    testBasic4(Host::IDENT_DUID);
+}
+
+// Test verifies that multiple hosts can be added and later retrieved by their
+// reserved IPv4 address. This test uses HW addresses as identifiers.
+TEST_F(CqlHostDataSourceTest, getByIPv4HWaddr) {
+    testGetByIPv4(Host::IDENT_HWADDR);
+}
+
+// Test verifies that multiple hosts can be added and later retrieved by their
+// reserved IPv4 address. This test uses client-id (DUID) as identifiers.
+TEST_F(CqlHostDataSourceTest, getByIPv4ClientId) {
+    testGetByIPv4(Host::IDENT_DUID);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// hardware address.
+TEST_F(CqlHostDataSourceTest, get4ByHWaddr) {
+    testGet4ByIdentifier(Host::IDENT_HWADDR);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// DUID.
+TEST_F(CqlHostDataSourceTest, get4ByDUID) {
+    testGet4ByIdentifier(Host::IDENT_DUID);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// circuit id.
+TEST_F(CqlHostDataSourceTest, get4ByCircuitId) {
+    testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
+}
+
+// Test verifies if hardware address and client identifier are not confused.
+TEST_F(CqlHostDataSourceTest, hwaddrNotClientId1) {
+    testHWAddrNotClientId();
+}
+
+// Test verifies if hardware address and client identifier are not confused.
+TEST_F(CqlHostDataSourceTest, hwaddrNotClientId2) {
+    testClientIdNotHWAddr();
+}
+
+// Test verifies if a host with FQDN hostname can be stored and later retrieved.
+TEST_F(CqlHostDataSourceTest, hostnameFQDN) {
+    testHostname("foo.example.org", 1);
+}
+
+// Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
+// retrieved.
+TEST_F(CqlHostDataSourceTest, hostnameFQDN100) {
+    testHostname("foo.example.org", 100);
+}
+
+// Test verifies if a host without any hostname specified can be stored and
+// later retrieved.
+TEST_F(CqlHostDataSourceTest, noHostname) {
+    testHostname("", 1);
+}
+
+// Test verifies if the hardware or client-id query can match hardware address.
+TEST_F(CqlHostDataSourceTest, DISABLED_hwaddrOrClientId1) {
+    /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+    /// be discussed.
+    ///
+    /// @todo: Add host reservation with hardware address X, try to retrieve
+    /// host for hardware address X or client identifier Y, verify that the
+    /// reservation is returned.
+}
+
+// Test verifies if the hardware or client-id query can match client-id.
+TEST_F(CqlHostDataSourceTest, DISABLED_hwaddrOrClientId2) {
+    /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+    /// be discussed.
+    ///
+    /// @todo: Add host reservation with client identifier Y, try to retrieve
+    /// host for hardware address X or client identifier Y, verify that the
+    /// reservation is returned.
+}
+
+// Test verifies that host with IPv6 address and DUID can be added and
+// later retrieved by IPv6 address.
+TEST_F(CqlHostDataSourceTest, get6AddrWithDuid) {
+    testGetByIPv6(Host::IDENT_DUID, false);
+}
+
+// Test verifies that host with IPv6 address and HWAddr can be added and
+// later retrieved by IPv6 address.
+TEST_F(CqlHostDataSourceTest, get6AddrWithHWAddr) {
+    testGetByIPv6(Host::IDENT_HWADDR, false);
+}
+
+// Test verifies that host with IPv6 prefix and DUID can be added and
+// later retrieved by IPv6 prefix.
+TEST_F(CqlHostDataSourceTest, get6PrefixWithDuid) {
+    testGetByIPv6(Host::IDENT_DUID, true);
+}
+
+// Test verifies that host with IPv6 prefix and HWAddr can be added and
+// later retrieved by IPv6 prefix.
+TEST_F(CqlHostDataSourceTest, get6PrefixWithHWaddr) {
+    testGetByIPv6(Host::IDENT_HWADDR, true);
+}
+
+// Test verifies that host with IPv6 prefix reservation can be retrieved
+// by subnet id and prefix value.
+TEST_F(CqlHostDataSourceTest, get6SubnetPrefix) {
+    testGetBySubnetIPv6();
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// hardware address.
+TEST_F(CqlHostDataSourceTest, get6ByHWaddr) {
+    testGet6ByHWAddr();
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// client identifier.
+TEST_F(CqlHostDataSourceTest, get6ByClientId) {
+    testGet6ByClientId();
+}
+
+// Test verifies if a host reservation can be stored with both IPv6 address and
+// prefix.
+TEST_F(CqlHostDataSourceTest, addr6AndPrefix) {
+    testAddr6AndPrefix();
+}
+
+// Tests if host with multiple IPv6 reservations can be added and then
+// retrieved correctly. Test checks reservations comparing.
+TEST_F(CqlHostDataSourceTest, multipleReservations) {
+    testMultipleReservations();
+}
+
+// Tests if compareIPv6Reservations() method treats same pool of reservations
+// but added in different order as equal.
+TEST_F(CqlHostDataSourceTest, multipleReservationsDifferentOrder) {
+    testMultipleReservationsDifferentOrder();
+}
+
+// Test verifies if multiple client classes for IPv4 can be stored.
+TEST_F(CqlHostDataSourceTest, DISABLED_multipleClientClasses4) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v4 client-classes, retrieve it and
+    /// make sure that all client classes are retrieved properly.
+}
+
+// Test verifies if multiple client classes for IPv6 can be stored.
+TEST_F(CqlHostDataSourceTest, DISABLED_multipleClientClasses6) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v6 client-classes, retrieve it and
+    /// make sure that all client classes are retrieved properly.
+}
+
+// Test verifies if multiple client classes for both IPv4 and IPv6 can be
+// stored.
+TEST_F(CqlHostDataSourceTest, DISABLED_multipleClientClassesBoth) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v4 and v6 client-classes, retrieve
+    /// it and make sure that all client classes are retrieved properly. Also,
+    /// check that the classes are not confused.
+}
+
+// Test if the same host can have reservations in different subnets (with the
+// same hardware address). The test logic is as follows:
+// Insert 10 host reservations for a given physical host (the same
+// hardware address), but for different subnets (different subnet-ids).
+// Make sure that getAll() returns them all correctly.
+TEST_F(CqlHostDataSourceTest, multipleSubnetsHWAddr) {
+    testMultipleSubnets(10, Host::IDENT_HWADDR);
+}
+
+// Test if the same host can have reservations in different subnets (with the
+// same client identifier). The test logic is as follows:
+//
+// Insert 10 host reservations for a given physical host (the same
+// client-identifier), but for different subnets (different subnet-ids).
+// Make sure that getAll() returns them correctly.
+TEST_F(CqlHostDataSourceTest, multipleSubnetsClientId) {
+    testMultipleSubnets(10, Host::IDENT_DUID);
+}
+
+// Test if host reservations made for different IPv6 subnets are handled
+// correctly. The test logic is as follows:
+//
+// Insert 10 host reservations for different subnets. Make sure that
+// get6(subnet-id, ...) calls return correct reservation.
+TEST_F(CqlHostDataSourceTest, subnetId6) {
+    testSubnetId6(10, Host::IDENT_HWADDR);
+}
+
+// Test if the duplicate host instances can't be inserted. The test logic is as
+// follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+// Hosts with same DUID.
+TEST_F(CqlHostDataSourceTest, addDuplicate6WithDUID) {
+    testAddDuplicate6WithSameDUID();
+}
+
+// Test if the duplicate host instances can't be inserted. The test logic is as
+// follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+// Hosts with same HWAddr.
+TEST_F(CqlHostDataSourceTest, addDuplicate6WithHWAddr) {
+    testAddDuplicate6WithSameHWAddr();
+}
+
+// Test if the duplicate IPv4 host instances can't be inserted. The test logic
+// is as follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+TEST_F(CqlHostDataSourceTest, addDuplicate4) {
+    testAddDuplicate4();
+}
+
+// This test verifies that DHCPv4 options can be inserted in a binary format
+/// and retrieved from the CQL host database.
+TEST_F(CqlHostDataSourceTest, optionsReservations4) {
+    testOptionsReservations4(false);
+}
+
+// This test verifies that DHCPv6 options can be inserted in a binary format
+/// and retrieved from the CQL host database.
+TEST_F(CqlHostDataSourceTest, optionsReservations6) {
+    testOptionsReservations6(false);
+}
+
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// binary format and retrieved with a single query to the database.
+TEST_F(CqlHostDataSourceTest, optionsReservations46) {
+    testOptionsReservations46(false);
+}
+
+// This test verifies that DHCPv4 options can be inserted in a textual format
+/// and retrieved from the CQL host database.
+TEST_F(CqlHostDataSourceTest, formattedOptionsReservations4) {
+    testOptionsReservations4(true);
+}
+
+// This test verifies that DHCPv6 options can be inserted in a textual format
+/// and retrieved from the CQL host database.
+TEST_F(CqlHostDataSourceTest, formattedOptionsReservations6) {
+    testOptionsReservations6(true);
+}
+
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+// textual format and retrieved with a single query to the database.
+TEST_F(CqlHostDataSourceTest, formattedOptionsReservations46) {
+    testOptionsReservations46(true);
+}
+
+// This test checks transactional insertion of the host information
+// into the database. The failure to insert host information at
+// any stage should cause the whole transaction to be rolled back.
+TEST_F(CqlHostDataSourceTest, testAddRollback) {
+    // Make sure we have the pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // To test the transaction rollback mechanism we need to cause the
+    // insertion of host information to fail at some stage. The 'hosts'
+    // table should be updated correctly but the failure should occur
+    // when inserting reservations or options. The simplest way to
+    // achieve that is to simply drop one of the tables. To do so, we
+    // connect to the database and issue a DROP query.
+    CqlConnection::ParameterMap params;
+    params["name"] = "keatest";
+    params["user"] = "keatest";
+    params["password"] = "keatest";
+    CqlConnection connection(params);
+    ASSERT_NO_THROW(connection.openDatabase());
+
+    // Drop every table so we make sure host_reservations doesn't exist anymore.
+    destroyCqlSchema(false, true);
+
+    // Create a host with a reservation.
+    HostPtr host = initializeHost6("2001:db8:1::1", Host::IDENT_HWADDR, false);
+    // Let's assign some DHCPv4 subnet to the host, because we will use the
+    // DHCPv4 subnet to try to retrieve the host after failed insertion.
+    host->setIPv4SubnetID(SubnetID(4));
+
+    // There is no ipv6_reservations table, so the insertion should fail.
+    ASSERT_THROW(hdsptr_->add(host), DbOperationError);
+
+    // Even though we have created a DHCPv6 host, we can't use get6()
+    // method to retrieve the host from the database, because the
+    // query would expect that the ipv6_reservations table is present.
+    // Therefore, the query would fail. Instead, we use the get4 method
+    // which uses the same client identifier, but doesn't attempt to
+    // retrieve the data from ipv6_reservations table. The query should
+    // pass but return no host because the (insert) transaction is expected
+    // to be rolled back.
+    ASSERT_THROW(
+        hdsptr_->get4(host->getIPv4SubnetID(), host->getIdentifierType(),
+                      &host->getIdentifier()[0], host->getIdentifier().size()),
+        DbOperationError);
+}
+
+TEST_F(CqlHostDataSourceTest, DISABLED_stressTest) {
+    // Run with 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4092, 8192,
+    // 16384 & 32768 hosts.
+    for (unsigned int i = 0X0001U; i < 0xfffdU; i <<= 1) {
+        initializeTest();
+        stressTest(i);
+        destroyTest();
+    }
+}
+
+}  // namespace
index baa2f7190f65c629c1f066aca0b63fdb1a065e9c..a556b9959bb8aa8018a86cef2d9c445d1709816a 100644 (file)
 #include <config.h>
 
 #include <asiolink/io_address.h>
-#include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/cql_connection.h>
 #include <dhcpsrv/cql_lease_mgr.h>
-#include <dhcpsrv/tests/test_utils.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <dhcpsrv/tests/test_utils.h>
 #include <dhcpsrv/testutils/cql_schema.h>
 #include <exceptions/exceptions.h>
 
@@ -53,7 +53,6 @@ public:
     ///
     /// Deletes everything from the database and opens it.
     CqlLeaseMgrTest() {
-
         // Ensure schema is the correct one.
         destroyCqlSchema(false, true);
         createCqlSchema(false, true);
@@ -95,13 +94,13 @@ public:
         lmptr_ = &(LeaseMgrFactory::instance());
     }
 
-    // This is the CQL implementation for GenericLeaseMgrTest::testGetExpiredLeases4().
+    // This is the CQL implementation for
+    // GenericLeaseMgrTest::testGetExpiredLeases4().
     // The GenericLeaseMgrTest implementation checks for the order of expired
     // leases to be from the most expired to the least expired. Cassandra
     // doesn't support ORDER BY without imposing a EQ / IN restriction on the
     // columns. Because of that, the order check has been excluded.
-    void
-    testCqlGetExpiredLeases4() {
+    void testCqlGetExpiredLeases4() {
         // Get the leases to be used for the test.
         vector<Lease4Ptr> leases = createLeases4();
         // Make sure we have at least 6 leases there.
@@ -118,10 +117,12 @@ public:
                 // valid lifetime to make it expired. The expiration time also
                 // depends on the lease index, so as we can later check that the
                 // leases are ordered by the expiration time.
-                leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 10 - i;
+                leases[i]->cltt_ =
+                    current_time - leases[i]->valid_lft_ - 10 - i;
 
             } else {
-                // Set current time as cltt for remaining leases. These leases are
+                // Set current time as cltt for remaining leases. These leases
+                // are
                 // not expired.
                 leases[i]->cltt_ = current_time;
             }
@@ -132,19 +133,22 @@ public:
         Lease4Collection expired_leases;
         ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 1000));
         // Leases with even indexes should be returned as expired.
-        ASSERT_EQ(static_cast<size_t>(leases.size() / 2U), expired_leases.size());
+        ASSERT_EQ(static_cast<size_t>(leases.size() / 2U),
+                  expired_leases.size());
 
         // Update current time for the next test.
         current_time = time(NULL);
         // Also, remove expired leases collected during the previous test.
         expired_leases.clear();
 
-        // This time let's reverse the expiration time and see if they will be returned
+        // This time let's reverse the expiration time and see if they will be
+        // returned
         // in the correct order.
         for (size_t i = 0U; i < leases.size(); ++i) {
             // Update the time of expired leases with even indexes.
             if (i % 2U == 0U) {
-                leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i;
+                leases[i]->cltt_ =
+                    current_time - leases[i]->valid_lft_ - 1000 + i;
             } else {
                 // Make sure remaining leases remain unexpired.
                 leases[i]->cltt_ = current_time + 100;
@@ -152,11 +156,13 @@ public:
             ASSERT_NO_THROW(lmptr_->updateLease4(leases[i]));
         }
 
-        // Retrieve expired leases again. The limit of 0 means return all expired
+        // Retrieve expired leases again. The limit of 0 means return all
+        // expired
         // leases.
         ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 0));
         // The same leases should be returned.
-        ASSERT_EQ(static_cast<size_t>(leases.size() / 2U), expired_leases.size());
+        ASSERT_EQ(static_cast<size_t>(leases.size() / 2U),
+                  expired_leases.size());
 
         // Remember expired leases returned.
         std::vector<Lease4Ptr> saved_expired_leases = expired_leases;
@@ -173,7 +179,8 @@ public:
         // Mark every other expired lease as reclaimed.
         for (size_t i = 0U; i < saved_expired_leases.size(); ++i) {
             if (i % 2U != 0U) {
-                saved_expired_leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+                saved_expired_leases[i]->state_ =
+                    Lease::STATE_EXPIRED_RECLAIMED;
             }
             ASSERT_NO_THROW(lmptr_->updateLease4(saved_expired_leases[i]));
         }
@@ -190,18 +197,19 @@ public:
         // those that have even index.
         for (Lease4Collection::iterator lease = expired_leases.begin();
              lease != expired_leases.end(); ++lease) {
-            int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+            int index =
+                static_cast<int>(std::distance(expired_leases.begin(), lease));
             EXPECT_EQ(saved_expired_leases[2 * index]->addr_, (*lease)->addr_);
         }
     }
 
-    // This is the CQL implementation for GenericLeaseMgrTest::testGetExpiredLeases4().
+    // This is the CQL implementation for
+    // GenericLeaseMgrTest::testGetExpiredLeases4().
     // The GenericLeaseMgrTest implementation checks for the order of expired
     // leases to be from the most expired to the least expired. Cassandra
     // doesn't support ORDER BY without imposing a EQ / IN restriction on the
     // columns. Because of that, the order check has been excluded.
-    void
-    testCqlGetExpiredLeases6() {
+    void testCqlGetExpiredLeases6() {
         // Get the leases to be used for the test.
         vector<Lease6Ptr> leases = createLeases6();
         // Make sure we have at least 6 leases there.
@@ -218,10 +226,12 @@ public:
                 // valid lifetime to make it expired. The expiration time also
                 // depends on the lease index, so as we can later check that the
                 // leases are ordered by the expiration time.
-                leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 10 - i;
+                leases[i]->cltt_ =
+                    current_time - leases[i]->valid_lft_ - 10 - i;
 
             } else {
-                // Set current time as cltt for remaining leases. These leases are
+                // Set current time as cltt for remaining leases. These leases
+                // are
                 // not expired.
                 leases[i]->cltt_ = current_time;
             }
@@ -232,19 +242,22 @@ public:
         Lease6Collection expired_leases;
         ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 1000));
         // Leases with even indexes should be returned as expired.
-        ASSERT_EQ(static_cast<size_t>(leases.size() / 2U), expired_leases.size());
+        ASSERT_EQ(static_cast<size_t>(leases.size() / 2U),
+                  expired_leases.size());
 
         // Update current time for the next test.
         current_time = time(NULL);
         // Also, remove expired leases collected during the previous test.
         expired_leases.clear();
 
-        // This time let's reverse the expiration time and see if they will be returned
+        // This time let's reverse the expiration time and see if they will be
+        // returned
         // in the correct order.
         for (size_t i = 0U; i < leases.size(); ++i) {
             // Update the time of expired leases with even indexes.
             if (i % 2U == 0U) {
-                leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i;
+                leases[i]->cltt_ =
+                    current_time - leases[i]->valid_lft_ - 1000 + i;
 
             } else {
                 // Make sure remaining leases remain unexpired.
@@ -253,11 +266,13 @@ public:
             ASSERT_NO_THROW(lmptr_->updateLease6(leases[i]));
         }
 
-        // Retrieve expired leases again. The limit of 0 means return all expired
+        // Retrieve expired leases again. The limit of 0 means return all
+        // expired
         // leases.
         ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0));
         // The same leases should be returned.
-        ASSERT_EQ(static_cast<size_t>(leases.size() / 2U), expired_leases.size());
+        ASSERT_EQ(static_cast<size_t>(leases.size() / 2U),
+                  expired_leases.size());
 
         // Remember expired leases returned.
         std::vector<Lease6Ptr> saved_expired_leases = expired_leases;
@@ -274,7 +289,8 @@ public:
         // Mark every other expired lease as reclaimed.
         for (size_t i = 0U; i < saved_expired_leases.size(); ++i) {
             if (i % 2U != 0U) {
-                saved_expired_leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+                saved_expired_leases[i]->state_ =
+                    Lease::STATE_EXPIRED_RECLAIMED;
             }
             ASSERT_NO_THROW(lmptr_->updateLease6(saved_expired_leases[i]));
         }
@@ -289,7 +305,8 @@ public:
         // those that have even index.
         for (Lease6Collection::iterator lease = expired_leases.begin();
              lease != expired_leases.end(); ++lease) {
-            int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+            int index =
+                static_cast<int>(std::distance(expired_leases.begin(), lease));
             EXPECT_EQ(saved_expired_leases[2 * index]->addr_, (*lease)->addr_);
         }
     }
@@ -304,7 +321,6 @@ public:
 /// Unlike other backend implementations, this one doesn't check for lacking
 /// parameters. In that scenario, Cassandra defaults to configured parameters.
 TEST(CqlOpenTest, OpenDatabase) {
-
     // Schema needs to be created for the test to work.
     destroyCqlSchema(false, true);
     createCqlSchema(false, true);
@@ -313,7 +329,7 @@ TEST(CqlOpenTest, OpenDatabase) {
     // If it fails, print the error message.
     try {
         LeaseMgrFactory::create(validCqlConnectionString());
-        EXPECT_NO_THROW((void) LeaseMgrFactory::instance());
+        EXPECT_NO_THROW((void)LeaseMgrFactory::instance());
         LeaseMgrFactory::destroy();
     } catch (const isc::Exception& ex) {
         FAIL() << "*** ERROR: unable to open database, reason:\n"
@@ -325,10 +341,11 @@ TEST(CqlOpenTest, OpenDatabase) {
     // Check that lease manager open the database opens correctly with a longer
     // timeout. If it fails, print the error message.
     try {
-        string connection_string = validCqlConnectionString() + string(" ") +
-                                   string(VALID_TIMEOUT);
+        std::string connection_string = validCqlConnectionString() +
+                                        std::string(" ") +
+                                        std::string(VALID_TIMEOUT);
         LeaseMgrFactory::create(connection_string);
-        EXPECT_NO_THROW((void) LeaseMgrFactory::instance());
+        EXPECT_NO_THROW((void)LeaseMgrFactory::instance());
         LeaseMgrFactory::destroy();
     } catch (const isc::Exception& ex) {
         FAIL() << "*** ERROR: unable to open database, reason:\n"
@@ -344,13 +361,39 @@ TEST(CqlOpenTest, OpenDatabase) {
     // Check that wrong specification of backend throws an exception.
     // (This is really a check on LeaseMgrFactory, but is convenient to
     // perform here.)
-    EXPECT_THROW(LeaseMgrFactory::create(connectionString(
-        NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+    EXPECT_THROW(
+        LeaseMgrFactory::create(connectionString(NULL, VALID_NAME, VALID_HOST,
+                                                 INVALID_USER, VALID_PASSWORD)),
         InvalidParameter);
+    EXPECT_THROW(
+        LeaseMgrFactory::create(connectionString(
+            INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+        InvalidType);
 
+    // Check that invalid login data does not cause an exception, CQL should use
+    // default values.
+    EXPECT_NO_THROW(LeaseMgrFactory::create(connectionString(
+        CQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)));
+    EXPECT_NO_THROW(LeaseMgrFactory::create(connectionString(
+        CQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)));
+    EXPECT_NO_THROW(LeaseMgrFactory::create(connectionString(
+        CQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)));
+    EXPECT_NO_THROW(LeaseMgrFactory::create(connectionString(
+        CQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)));
+
+    // Check that invalid timeouts throw DbOperationError.
     EXPECT_THROW(LeaseMgrFactory::create(connectionString(
-        INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
-        InvalidType);
+                     CQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER,
+                     VALID_PASSWORD, INVALID_TIMEOUT_1)),
+                 DbOperationError);
+    EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+                     CQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER,
+                     VALID_PASSWORD, INVALID_TIMEOUT_2)),
+                 DbOperationError);
+
+    // Check that CQL allows the hostname to not be specified.
+    EXPECT_NO_THROW(LeaseMgrFactory::create(connectionString(
+        CQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)));
 
     // Tidy up after the test
     destroyCqlSchema(false, true);
@@ -375,8 +418,8 @@ TEST_F(CqlLeaseMgrTest, getType) {
 /// This test checks that the conversion is correct.
 TEST_F(CqlLeaseMgrTest, checkTimeConversion) {
     const time_t cltt = time(NULL);
-    const uint32_t valid_lft = 86400;       // 1 day
-    uint64_t cql_expire;
+    const cass_int64_t valid_lft = 86400; // 1 day
+    cass_int64_t cql_expire;
 
     // Convert to the database time
     CqlExchange::convertToDatabaseTime(cltt, valid_lft, cql_expire);
@@ -489,7 +532,8 @@ TEST_F(CqlLeaseMgrTest, getLease4ClientIdSubnetId) {
 
 /// @brief Basic Lease4 Checks
 ///
-/// Checks that the addLease, getLease4(by address), getLease4(hwaddr, subnet_id),
+/// Checks that the addLease, getLease4(by address), getLease4(hwaddr,
+/// subnet_id),
 /// updateLease4() and deleteLease (IPv4 address) can handle NULL client-id.
 /// (client-id is optional and may not be present)
 TEST_F(CqlLeaseMgrTest, lease4NullClientId) {
@@ -643,4 +687,4 @@ TEST_F(CqlLeaseMgrTest, deleteExpiredReclaimedLeases4) {
     testDeleteExpiredReclaimedLeases4();
 }
 
-}; // Of anonymous namespace
+}  // namespace
index 43a87fb860f2e210ff961787c9df6484a1950ebb..ec361708d09e536c5bd697831860bd0a915c3c10 100644 (file)
@@ -4,6 +4,7 @@
 // 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/.
 
+#include <config.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option4_addrlst.h>
@@ -148,6 +149,17 @@ HostPtr GenericHostDataSourceTest::initializeHost6(std::string address,
     return (host);
 }
 
+bool
+GenericHostDataSourceTest::reservationExists(const IPv6Resrv& resrv,
+                                             const IPv6ResrvRange& range) {
+    for (IPv6ResrvIterator it = range.first; it != range.second; ++it) {
+        if (resrv == it->second) {
+            return true;
+        }
+    }
+    return false;
+}
+
 void
 GenericHostDataSourceTest::compareHwaddrs(const ConstHostPtr& host1,
                                           const ConstHostPtr& host2,
@@ -261,6 +273,24 @@ void GenericHostDataSourceTest::compareHosts(const ConstHostPtr& host1,
     compareOptions(host1->getCfgOption6(), host2->getCfgOption6());
 }
 
+bool GenericHostDataSourceTest::compareHostsForSort4(
+    const ConstHostPtr& host1,
+    const ConstHostPtr& host2) {
+    if (host1->getIPv4SubnetID() < host2->getIPv4SubnetID()) {
+        return true;
+    }
+    return false;
+}
+
+bool GenericHostDataSourceTest::compareHostsForSort6(
+    const ConstHostPtr& host1,
+    const ConstHostPtr& host2) {
+    if (host1->getIPv6SubnetID() < host2->getIPv6SubnetID()) {
+        return true;
+    }
+    return false;
+}
+
 DuidPtr
 GenericHostDataSourceTest::HWAddrToDuid(const HWAddrPtr& hwaddr) {
     if (!hwaddr) {
@@ -817,6 +847,12 @@ GenericHostDataSourceTest::testMultipleSubnets(int subnets,
 
     // Verify that the values returned are proper.
     int i = 0;
+    if (hdsptr_->getType() == "cql") {
+        // There is no ORDER BY in Cassandra. Order here. Remove this if entries
+        // are eventually implemented as ordered in the Cassandra host data
+        // source.
+        std::sort(all_by_addr.begin(), all_by_addr.end(), compareHostsForSort4);
+    }
     for (ConstHostCollection::const_iterator it = all_by_addr.begin();
          it != all_by_addr.end(); ++it) {
         EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
@@ -831,6 +867,12 @@ GenericHostDataSourceTest::testMultipleSubnets(int subnets,
 
     // Check that the returned values are as expected.
     i = 0;
+    if (hdsptr_->getType() == "cql") {
+        // There is no ORDER BY in Cassandra. Order here. Remove this if entries
+        // are eventually implemented as ordered in the Cassandra host data
+        // source.
+        std::sort(all_by_id.begin(), all_by_id.end(), compareHostsForSort4);
+    }
     for (ConstHostCollection::const_iterator it = all_by_id.begin();
          it != all_by_id.end(); ++it) {
         EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
@@ -952,6 +994,11 @@ GenericHostDataSourceTest::testSubnetId6(int subnets, Host::IdentifierType id) {
 
     // Check that the returned values are as expected.
     int i = 0;
+    if (hdsptr_->getType() == "cql") {
+        // There is no ORDER BY in Cassandra. Order here. Remove this if entries
+        // are implemented as ordered in the Cassandra host data source.
+        std::sort(all_by_id.begin(), all_by_id.end(), compareHostsForSort6);
+    }
     for (ConstHostCollection::const_iterator it = all_by_id.begin();
          it != all_by_id.end(); ++it) {
         EXPECT_EQ(IOAddress("0.0.0.0"), (*it)->getIPv4Reservation());
@@ -1463,6 +1510,76 @@ GenericHostDataSourceTest::testMessageFields4() {
     ASSERT_NO_FATAL_FAILURE(compareHosts(host, from_hds));
 }
 
-}; // namespace test
-}; // namespace dhcp
-}; // namespace isc
+void
+GenericHostDataSourceTest::stressTest(unsigned int nOfHosts /* = 0xfffdU */) {
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Make sure the variable part of the generated address fits in a 16-bit
+    // field.
+    ASSERT_LE(nOfHosts, 0xfffdU);
+
+    // Create hosts.
+    std::vector<HostPtr> hosts;
+    hosts.reserve(nOfHosts);
+    for (unsigned int i = 0x0001U; i < 0x0001U + nOfHosts; ++i) {
+        /// @todo: Check if this is written in hexadecimal format.
+        std::stringstream ss;
+        std::string n_host;
+        ss << std::hex << i;
+        ss >> n_host;
+
+        const std::string prefix = std::string("2001:db8::") + n_host;
+        hosts.push_back(initializeHost6(prefix, Host::IDENT_HWADDR, false));
+        IPv6ResrvRange range = hosts.back()->getIPv6Reservations();
+        ASSERT_EQ(1, std::distance(range.first, range.second));
+        EXPECT_TRUE(reservationExists(
+            IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress(prefix)), range));
+    }
+    const size_t hosts_size = hosts.size();
+
+    std::cout << "Starting to add hosts..." << std::endl;
+    struct timespec start, end;
+    start = (struct timespec){0, 0};
+    end = (struct timespec){0, 0};
+    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+    for (std::vector<HostPtr>::const_iterator it = hosts.begin();
+         it != hosts.end(); it++) {
+        ASSERT_NO_THROW(hdsptr_->add(*it));
+    }
+    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+    double s = static_cast<double>(end.tv_sec - start.tv_sec) +
+               static_cast<double>(end.tv_nsec - start.tv_nsec) / 1e9;
+    std::cout << "Adding " << hosts_size
+              << (hosts_size == 1 ? " host" : " hosts") << " took "
+              << std::fixed << std::setprecision(2) << s << " seconds."
+              << std::endl;
+
+    // And then try to retrieve them back.
+    std::cout << "Starting to retrieve hosts..." << std::endl;
+    start = (struct timespec){0, 0};
+    end = (struct timespec){0, 0};
+    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+    for (std::vector<HostPtr>::const_iterator it = hosts.begin();
+         it != hosts.end(); it++) {
+        IPv6ResrvRange range = (*it)->getIPv6Reservations();
+        // This get6() call is particularly useful to test because it involves a
+        // subquery for MySQL and PostgreSQL and two separate queries for
+        // Cassandra.
+        ConstHostPtr from_hds =
+            hdsptr_->get6(range.first->second.getPrefix(), 128);
+        ASSERT_TRUE(from_hds);
+        compareHosts(*it, from_hds);
+    }
+    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+    s = static_cast<double>(end.tv_sec - start.tv_sec) +
+        static_cast<double>(end.tv_nsec - start.tv_nsec) / 1e9;
+    std::cout << "Retrieving " << hosts_size
+              << (hosts_size == 1 ? " host" : " hosts") << " took "
+              << std::fixed << std::setprecision(2) << s << " seconds."
+              << std::endl;
+}
+
+}  // namespace test
+}  // namespace dhcp
+}  // namespace isc
index f84638bb83f9f1a59479907aeed9a3dd5fb50db7..fda5191ae70685347fdb4c2c952a1420d0c9978d 100644 (file)
@@ -86,6 +86,13 @@ public:
     /// @return Identifier in textual form acceptable by Host constructor
     std::vector<uint8_t> generateIdentifier(const bool new_identifier = true);
 
+    /// @brief Checks if the reservation is in the range of reservations.
+    ///
+    /// @param resrv Reservation to be searched for.
+    /// @param range Range of reservations returned by the @c Host object
+    /// in which the reservation will be searched
+    bool reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range);
+
     /// @brief Compares hardware addresses of the two hosts.
     ///
     /// This method compares two hwardware address and uses gtest
@@ -122,6 +129,14 @@ public:
     /// @param host2 second host to compare
     void compareHosts(const ConstHostPtr& host1, const ConstHostPtr& host2);
 
+    /// @ brief Used to sort a host collection by IPv4 subnet id.
+    static bool compareHostsForSort4(const ConstHostPtr& host1,
+                                     const ConstHostPtr& host2);
+
+    /// @ brief Used to sort a host collection by IPv6 subnet id.
+    static bool compareHostsForSort6(const ConstHostPtr& host1,
+                                     const ConstHostPtr& host2);
+
     /// @brief Compares two IPv6 reservation lists.
     ///
     /// This method uses gtest macros to signal errors.
@@ -525,6 +540,15 @@ public:
     ///
     void testMessageFields4();
 
+    /// @brief Stress test on adding and retrieving hosts
+    ///
+    /// Rather than checking for correctness, this test gives interpretable
+    /// performance results.
+    ///
+    /// @param n_of_hosts number of hosts to insert into and retrieve from the
+    ///     database; defaults to the maximum number of allowed hosts.
+    void stressTest(uint32_t n_of_hosts /* = 0xfffdU */);
+
     /// @brief Returns DUID with identical content as specified HW address
     ///
     /// This method does not have any sense in real life and is only useful
@@ -547,8 +571,8 @@ public:
 
 };
 
-}; // namespace test
-}; // namespace dhcp
-}; // namespace isc
+}  // namespace test
+}  // namespace dhcp
+}  // namespace isc
 
 #endif
index b03c8a4814f2b3176565443c419a6e2818c4b229..8a4468e39eb08f822fc97476f7ae6716bb67c119 100644 (file)
@@ -5,15 +5,19 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
-#include <string>
+
 #include <cassandra.h>
+
 #include <dhcpsrv/cql_connection.h>
 #include <dhcpsrv/testutils/cql_schema.h>
+
+#include <stdlib.h>
+
 #include <gtest/gtest.h>
 
 #include <fstream>
 #include <sstream>
-#include <stdlib.h>
+#include <string>
 
 using namespace std;
 
@@ -25,11 +29,12 @@ const char* CQL_VALID_TYPE = "type=cql";
 
 string
 validCqlConnectionString() {
-    return (connectionString(CQL_VALID_TYPE, VALID_NAME, VALID_HOST,
-                             VALID_USER, VALID_PASSWORD));
+    return (connectionString(CQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER,
+                             VALID_PASSWORD));
 }
 
-bool softWipeEnabled() {
+bool
+softWipeEnabled() {
     const char* const env = getenv("KEA_TEST_CASSANDRA_WIPE");
     if (env && (string(env) == string("soft"))) {
         return (true);
@@ -38,26 +43,28 @@ bool softWipeEnabled() {
     return (false);
 }
 
-void destroyCqlSchema(bool force_wipe, bool show_err) {
+void
+destroyCqlSchema(bool force_wipe, bool show_err) {
     if (force_wipe || !softWipeEnabled()) {
         // Do full wipe
         runCqlScript(DATABASE_SCRIPTS_DIR, "cql/dhcpdb_drop.cql", show_err);
     } else {
-
         // do soft wipe (just remove the data, not the structures)
         runCqlScript(DATABASE_SCRIPTS_DIR, "cql/soft_wipe.cql", show_err);
     }
 }
 
-void createCqlSchema(bool force_wipe, bool show_err) {
+void
+createCqlSchema(bool force_wipe, bool show_err) {
     if (force_wipe || !softWipeEnabled()) {
-        runCqlScript(DATABASE_SCRIPTS_DIR, "cql/dhcpdb_create.cql",
-                        show_err);
+        runCqlScript(DATABASE_SCRIPTS_DIR, "cql/dhcpdb_create.cql", show_err);
     }
 }
 
-void runCqlScript(const std::string& path, const std::string& script_name,
-                    bool show_err) {
+void
+runCqlScript(const std::string& path,
+             const std::string& script_name,
+             bool show_err) {
     std::ostringstream cmd;
     cmd << "cqlsh -u keatest -p keatest -k keatest";
     if (!show_err) {
@@ -76,7 +83,6 @@ void runCqlScript(const std::string& path, const std::string& script_name,
     ASSERT_EQ(0, retval) << "runCqlSchema failed:" << cmd.str();
 }
 
-
-};
-};
-};
+}  // namespace test
+}  // namespace dhcp
+}  // namespace isc
index 9b92ea15282150f50b79a782b75452486ec54ded..91377f96dba82b7c0433899710cd2f09de0e6965 100644 (file)
@@ -54,7 +54,7 @@ void createCqlSchema(bool force_wipe, bool show_err = false);
 
 /// @brief Run a CQL script against the CQL unit test database
 ///
-/// Submits the given SQL script to CQL via cqlsh CLI. The output of
+/// Submits the given CQL script to CQL via cqlsh CLI. The output of
 /// stderr is suppressed unless the parameter, show_err is true. The is done
 /// to suppress warnings that might otherwise make test output needlessly
 /// noisy. A gtest assertion occurs if the script fails to execute.
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d184e0fe314e46b5a85e5caf3a7761a0f4baa24a 100644 (file)
@@ -0,0 +1,2 @@
+upgrade_1.0_to_2.0.sh
+
index 8a32f195b14c12b087b71e098da1460d7c82d701..369b371b53b5f052490b14a1c20df696ec315d62 100644 (file)
@@ -3,6 +3,7 @@ SUBDIRS = .
 sqlscriptsdir = ${datarootdir}/${PACKAGE_NAME}/scripts/cql
 sqlscripts_DATA = dhcpdb_create.cql
 sqlscripts_DATA += dhcpdb_drop.cql
+sqlscripts_DATA += upgrade_1.0_to_2.0.sh
 
 EXTRA_DIST = ${sqlscripts_DATA}
 
index eda5fe2fc9d482bc03fd2282257f4b66ac0369e0..1a1d35bed1c671e1f8b60899ac898c3fb0f0b0bb 100644 (file)
 -- This line starts database initialization to 1.0.
 
 -- Holds the IPv4 leases.
-CREATE TABLE lease4 (
+-- -----------------------------------------------------
+-- Table `lease4`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS lease4 (
     address int,
     hwaddr blob,
     client_id blob,
@@ -56,16 +59,19 @@ CREATE TABLE lease4 (
 );
 
 -- Create search indexes for lease4 table
-CREATE INDEX lease4index1 ON lease4 (client_id);
-CREATE INDEX lease4index2 ON lease4 (subnet_id);
-CREATE INDEX lease4index3 ON lease4 (hwaddr);
-CREATE INDEX lease4index4 ON lease4 (expire);
-CREATE INDEX lease4index5 ON lease4 (state);
+CREATE INDEX IF NOT EXISTS lease4index1 ON lease4 (client_id);
+CREATE INDEX IF NOT EXISTS lease4index2 ON lease4 (subnet_id);
+CREATE INDEX IF NOT EXISTS lease4index3 ON lease4 (hwaddr);
+CREATE INDEX IF NOT EXISTS lease4index4 ON lease4 (expire);
+CREATE INDEX IF NOT EXISTS lease4index5 ON lease4 (state);
 
 -- Holds the IPv6 leases.
 -- N.B. The use of a VARCHAR for the address is temporary for development:
 -- it will eventually be replaced by BINARY(16).
-CREATE TABLE lease6 (
+-- -----------------------------------------------------
+-- Table `lease6`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS lease6 (
     address varchar,
     duid blob,
     valid_lifetime bigint,
@@ -86,19 +92,22 @@ CREATE TABLE lease6 (
 );
 
 -- Create search indexes for lease6 table
-CREATE INDEX lease6index1 ON lease6 (lease_type);
-CREATE INDEX lease6index2 ON lease6 (duid);
-CREATE INDEX lease6index3 ON lease6 (iaid);
-CREATE INDEX lease6index4 ON lease6 (subnet_id);
-CREATE INDEX lease6index5 ON lease6 (expire);
-CREATE INDEX lease6index6 ON lease6 (state);
+CREATE INDEX IF NOT EXISTS lease6index1 ON lease6 (lease_type);
+CREATE INDEX IF NOT EXISTS lease6index2 ON lease6 (duid);
+CREATE INDEX IF NOT EXISTS lease6index3 ON lease6 (iaid);
+CREATE INDEX IF NOT EXISTS lease6index4 ON lease6 (subnet_id);
+CREATE INDEX IF NOT EXISTS lease6index5 ON lease6 (expire);
+CREATE INDEX IF NOT EXISTS lease6index6 ON lease6 (state);
 
 -- ... and a definition of lease6 types.  This table is a convenience for
 -- users of the database - if they want to view the lease table and use the
 -- type names, they can join this table with the lease6 table.
 -- Make sure those values match Lease6::LeaseType enum (see src/bin/dhcpsrv/
 -- lease_mgr.h)
-CREATE TABLE lease6_types (
+-- -----------------------------------------------------
+-- Table `lease6_types`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS lease6_types (
     lease_type int,                             -- Lease type code.
     name varchar,                               -- Name of the lease type
     PRIMARY KEY (lease_type)
@@ -113,7 +122,10 @@ INSERT INTO lease6_types (lease_type, name) VALUES (2, 'IA_PD');   -- Prefix del
 -- users of the database - if they want to view the lease table and use the
 -- type names, they can join this table with the lease6 table. For details,
 -- see constants defined in src/lib/dhcp/dhcp/pkt.h for detailed explanation.
-CREATE TABLE lease_hwaddr_source (
+-- -----------------------------------------------------
+-- Table `lease_hwaddr_source`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS lease_hwaddr_source (
     hwaddr_source int,
     name varchar,
     PRIMARY KEY (hwaddr_source)
@@ -140,48 +152,13 @@ INSERT INTO lease_hwaddr_source (hwaddr_source, name) VALUES (32, 'HWADDR_SOURCE
 -- Hardware address extracted from docsis options
 INSERT INTO lease_hwaddr_source (hwaddr_source, name) VALUES (64, 'HWADDR_SOURCE_DOCSIS_CMTS');
 
--- -----------------------------------------------------
--- Table `dhcp4_options`
--- -----------------------------------------------------
-CREATE TABLE dhcp4_options (
-    option_id int,
-    code int,
-    value blob,
-    formatted_value varchar,
-    space varchar,
-    persistent int,
-    dhcp_client_class varchar,
-    dhcp4_subnet_id int,
-    host_id int,
-    PRIMARY KEY (option_id)
-);
-
--- Create search indexes for dhcp4_options table
-CREATE INDEX dhcp4_optionsindex1 ON dhcp4_options (host_id);
-
--- -----------------------------------------------------
--- Table `dhcp6_options`
--- -----------------------------------------------------
-CREATE TABLE dhcp6_options (
-    option_id int,
-    code int,
-    value blob,
-    formatted_value varchar,
-    space varchar,
-    persistent int,
-    dhcp_client_class varchar,
-    dhcp6_subnet_id int,
-    host_id int,
-    PRIMARY KEY (option_id)
-);
-
--- Create search indexes for dhcp6_options table
-CREATE INDEX dhcp6_optionsindex1 ON dhcp6_options (host_id);
-
 -- Create table holding mapping of the lease states to their names.
 -- This is not used in queries from the DHCP server but rather in
 -- direct queries from the lease database management tools.
-CREATE TABLE lease_state (
+-- -----------------------------------------------------
+-- Table `lease_state`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS lease_state (
     state int,
     name varchar,
     PRIMARY KEY (state)
@@ -196,9 +173,57 @@ INSERT INTO lease_state (state, name) VALUES (2, 'expired-reclaimed');
 -- This table is only modified during schema upgrades.  For historical reasons
 -- (related to the names of the columns in the BIND 10 DNS database file), the
 -- first column is called "version" and not "major".
-CREATE TABLE schema_version (
+-- -----------------------------------------------------
+-- Table `schema_version`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS schema_version (
     version int,
     minor int,
     PRIMARY KEY (version)
 );
+
 INSERT INTO schema_version (version, minor) VALUES (1, 0);
+
+-- This line concludes database initalization to version 1.0.
+
+-- This line starts database upgrade to version 2.0
+
+-- -----------------------------------------------------
+-- Table `host_reservations`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS host_reservations (
+    id bigint,
+    host_identifier blob,
+    host_identifier_type int,
+    host_ipv4_subnet_id int,
+    host_ipv6_subnet_id int,
+    host_ipv4_address int,
+    hostname text,
+    host_ipv4_client_classes text,
+    host_ipv6_client_classes text,
+    reserved_ipv6_prefix_address text,
+    reserved_ipv6_prefix_length int,
+    reserved_ipv6_prefix_address_type int,
+    iaid int,
+    option_universe int,
+    option_code int,
+    option_value blob,
+    option_formatted_value text,
+    option_space text,
+    option_is_persistent boolean,
+    option_client_class text,
+    option_subnet_id int,
+    PRIMARY KEY (id)
+);
+CREATE INDEX IF NOT EXISTS host_reservationsindex1 ON host_reservations (host_identifier);
+CREATE INDEX IF NOT EXISTS host_reservationsindex2 ON host_reservations (host_identifier_type);
+CREATE INDEX IF NOT EXISTS host_reservationsindex3 ON host_reservations (host_ipv4_subnet_id);
+CREATE INDEX IF NOT EXISTS host_reservationsindex4 ON host_reservations (host_ipv6_subnet_id);
+CREATE INDEX IF NOT EXISTS host_reservationsindex5 ON host_reservations (host_ipv4_address);
+CREATE INDEX IF NOT EXISTS host_reservationsindex6 ON host_reservations (reserved_ipv6_prefix_address);
+CREATE INDEX IF NOT EXISTS host_reservationsindex7 ON host_reservations (reserved_ipv6_prefix_length);
+
+DELETE FROM schema_version WHERE version=1;
+INSERT INTO schema_version (version, minor) VALUES(2, 0);
+
+-- This line concludes database upgrade to version 2.0
index 7ec86fadc33e0254edd19ef7e3d61a2decbf390b..7a93e3df17db7ab0be87626c7ddb1e9acdc55ebb 100644 (file)
@@ -18,10 +18,27 @@ DROP TABLE IF EXISTS lease4;
 DROP TABLE IF EXISTS lease6;
 DROP TABLE IF EXISTS lease6_types;
 DROP TABLE IF EXISTS lease_hwaddr_source;
-DROP TABLE IF EXISTS schema_version;
-DROP TABLE IF EXISTS ipv6_reservations;
-DROP TABLE IF EXISTS hosts;
-DROP TABLE IF EXISTS dhcp4_options;
-DROP TABLE IF EXISTS dhcp6_options;
-DROP TABLE IF EXISTS host_identifier_type;
 DROP TABLE IF EXISTS lease_state;
+DROP TABLE IF EXISTS schema_version;
+DROP TABLE IF EXISTS host_reservations;
+
+DROP INDEX IF EXISTS lease4index1;
+DROP INDEX IF EXISTS lease4index2;
+DROP INDEX IF EXISTS lease4index3;
+DROP INDEX IF EXISTS lease4index4;
+DROP INDEX IF EXISTS lease4index5;
+
+DROP INDEX IF EXISTS lease6index1;
+DROP INDEX IF EXISTS lease6index2;
+DROP INDEX IF EXISTS lease6index3;
+DROP INDEX IF EXISTS lease6index4;
+DROP INDEX IF EXISTS lease6index5;
+DROP INDEX IF EXISTS lease6index6;
+
+DROP INDEX IF EXISTS host_reservationsindex1;
+DROP INDEX IF EXISTS host_reservationsindex2;
+DROP INDEX IF EXISTS host_reservationsindex3;
+DROP INDEX IF EXISTS host_reservationsindex4;
+DROP INDEX IF EXISTS host_reservationsindex5;
+DROP INDEX IF EXISTS host_reservationsindex6;
+DROP INDEX IF EXISTS host_reservationsindex7;
index e3dd6c402e48f5199edaf0230eeae2ca05365f49..5c9580843c2f70533554e64bdb85e4388ca6c062 100644 (file)
@@ -19,6 +19,8 @@
 
 TRUNCATE TABLE lease4;
 TRUNCATE TABLE lease6;
-TRUNCATE TABLE hosts;
-TRUNCATE TABLE dhcp4_options;
-TRUNCATE TABLE dhcp6_options;
+TRUNCATE TABLE lease6_types;
+TRUNCATE TABLE lease_hwaddr_source;
+TRUNCATE TABLE lease_state;
+TRUNCATE TABLE schema_version;
+TRUNCATE TABLE host_reservations;
diff --git a/src/share/database/scripts/cql/upgrade_1.0_to_2.0.sh.in b/src/share/database/scripts/cql/upgrade_1.0_to_2.0.sh.in
new file mode 100644 (file)
index 0000000..ac30776
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+prefix=@prefix@
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+    . @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh
+else
+    . @abs_top_builddir@/src/bin/admin/admin-utils.sh
+fi
+
+version=$(cql_version "$@")
+
+if [ "${version}" != "1.0" ]; then
+    printf "This script upgrades 1.0 to 2.0. Reported version is %s. Skipping upgrade.\n" "${version}"
+    exit 0
+fi
+
+cqlsh "$@" <<EOF
+-- This line starts database upgrade to version 2.0
+
+-- -----------------------------------------------------
+-- Table \`host_reservations\`
+-- -----------------------------------------------------
+CREATE TABLE host_reservations (
+    id bigint,
+    host_identifier blob,
+    host_identifier_type int,
+    host_ipv4_subnet_id int,
+    host_ipv6_subnet_id int,
+    host_ipv4_address int,
+    hostname text,
+    host_ipv4_client_classes text,
+    host_ipv6_client_classes text,
+    reserved_ipv6_prefix_address text,
+    reserved_ipv6_prefix_length int,
+    reserved_ipv6_prefix_address_type int,
+    iaid int,
+    option_universe int,
+    option_code int,
+    option_value blob,
+    option_formatted_value text,
+    option_space text,
+    option_is_persistent boolean,
+    option_client_class text,
+    option_subnet_id int,
+    PRIMARY KEY (id)
+);
+CREATE INDEX IF NOT EXISTS host_reservationsindex1 ON host_reservations (host_identifier);
+CREATE INDEX IF NOT EXISTS host_reservationsindex2 ON host_reservations (host_identifier_type);
+CREATE INDEX IF NOT EXISTS host_reservationsindex3 ON host_reservations (host_ipv4_subnet_id);
+CREATE INDEX IF NOT EXISTS host_reservationsindex4 ON host_reservations (host_ipv6_subnet_id);
+CREATE INDEX IF NOT EXISTS host_reservationsindex5 ON host_reservations (host_ipv4_address);
+CREATE INDEX IF NOT EXISTS host_reservationsindex6 ON host_reservations (reserved_ipv6_prefix_address);
+CREATE INDEX IF NOT EXISTS host_reservationsindex7 ON host_reservations (reserved_ipv6_prefix_length);
+
+DELETE FROM schema_version WHERE version=1;
+INSERT INTO schema_version (version, minor) VALUES(2, 0);
+
+-- This line concludes database upgrade to version 2.0
+EOF
+
+exit $?
index 4bde2fcf3ac4a9f37cebee30119a630ba0b0b6e2..8e8ac1d200dd92b2bdb3d6dc3fc78ce86443308f 100644 (file)
@@ -9,5 +9,4 @@ sqlscripts_DATA += upgrade_3.0_to_4.0.sh
 sqlscripts_DATA += upgrade_4.0_to_4.1.sh
 sqlscripts_DATA += upgrade_4.1_to_5.0.sh
 
-
 EXTRA_DIST = ${sqlscripts_DATA}