2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

[master] Addes support for Host Reservations to PostgreSQL backend

Merge branch 'trac4277'
This commit is contained in:
Thomas Markwalder
2016-07-26 08:03:11 -04:00
17 changed files with 4556 additions and 193 deletions

View File

@@ -886,7 +886,7 @@ EXAMPLE_RECURSIVE = NO
# directories that contain image that are included in the documentation (see
# the \image command).
IMAGE_PATH = ../doc/images ../src/lib/hooks/images ../src/bin/d2/images
IMAGE_PATH = ../doc/images ../src/lib/hooks/images ../src/bin/d2/images ../src/lib/dhcpsrv/images
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program

View File

@@ -44,6 +44,9 @@ EXTRA_DIST += parsers/host_reservations_list_parser.h
EXTRA_DIST += parsers/ifaces_config_parser.cc
EXTRA_DIST += parsers/ifaces_config_parser.h
# Devel guide diagrams
EXTRA_DIST += images/pgsql_host_data_source.svg
# Define rule to build logging source files from message file
alloc_engine_messages.h alloc_engine_messages.cc dhcpsrv_messages.h \
dhcpsrv_messages.cc hosts_messages.h hosts_messages.cc: s-messages
@@ -133,6 +136,7 @@ libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
if HAVE_PGSQL
libkea_dhcpsrv_la_SOURCES += pgsql_connection.cc pgsql_connection.h
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

View File

@@ -634,6 +634,10 @@ An error message indicating that communication with the MySQL database server
has been lost. When this occurs the server exits immediately with a non-zero
exit code. This is most likely due to a network issue.
% DHCPSRV_PGSQL_HOST_DB_GET_VERSION obtaining schema version information for the PostgreSQL hosts database
A debug message issued when the server is about to obtain schema version
information from the PostgreSQL hosts database.
% DHCPSRV_PGSQL_GET_ADDR4 obtaining IPv4 lease for address %1
A debug message issued when the server is attempting to obtain an IPv4
lease from the PostgreSQL database for the specified address.
@@ -696,6 +700,15 @@ connection including database name and username needed to access it
The code has issued a rollback call. All outstanding transaction will
be rolled back and not committed to the database.
% DHCPSRV_PGSQL_START_TRANSACTION starting a new PostgreSQL transaction
A debug message issued when a new PostgreSQL transaction is being started.
This message is typically not issued when inserting data into a
single table because the server doesn't explicitly start
transactions in this case. This message is issued when data is
inserted into multiple tables with multiple INSERT statements
and there may be a need to rollback the whole transaction if
any of these INSERT statements fail.
% DHCPSRV_PGSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
A debug message issued when the server is attempting to update IPv4
lease from the PostgreSQL database for the specified address.

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
// 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
@@ -14,6 +14,10 @@
#include <dhcpsrv/mysql_host_data_source.h>
#endif
#ifdef HAVE_PGSQL
#include <dhcpsrv/pgsql_host_data_source.h>
#endif
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
@@ -63,8 +67,10 @@ HostDataSourceFactory::create(const std::string& dbaccess) {
#ifdef HAVE_PGSQL
if (db_type == "postgresql") {
isc_throw(NotImplemented, "Sorry, PostgreSQL backend for host reservations "
"is not implemented yet.");
LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB)
.arg(DatabaseConnection::redactedAccessString(parameters));
getHostDataSourcePtr().reset(new PgSqlHostDataSource(parameters));
return;
}
#endif

View File

@@ -0,0 +1,419 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Bouml (http://bouml.free.fr/) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="1211" height="918" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="144" y="237" width="2" height="212" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="16" y="447" width="130" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="13" y="234" width="131" height="213" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="79" y="246">PgSqlHostDataSource</text>
<line stroke="black" stroke-opacity="1" x1="13" y1="247" x2="144" y2="247" />
<line stroke="black" stroke-opacity="1" x1="13" y1="253" x2="144" y2="253" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="265">PgSqlHostDataSource()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="277">~PgSqlHostDataSource()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="289">getAll()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="301">getAll()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="313">getAll4()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="325">get4()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="337">get4()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="349">get4()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="361">get6()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="373">get6()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="385">get6()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="397">add()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="409">getType()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="421">getName()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="433">getDescription()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="445">getVersion()</text>
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="704" y="24" width="2" height="208" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="560" y="230" width="146" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="557" y="21" width="147" height="209" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="631" y="33">PgSqlExchange</text>
<line stroke="black" stroke-opacity="1" x1="557" y1="34" x2="704" y2="34" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="560" y="46">columns_</text>
<line stroke="black" stroke-opacity="1" x1="557" y1="47" x2="704" y2="47" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="560" y="59">PgSqlExchange()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="560" y="71">~PgSqlExchange()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="83">convertToDatabaseTime()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="95">convertToDatabaseTime()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="107">convertFromDatabaseTime()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="119">getRawColumnValue()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="131">getColumnLabel()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="143">getColumnValue()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="155">getColumnValue()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="167">getColumnValue()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="179">getIPv6Value()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="191">isColumnNull()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="203">getColumnValue()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="215">convertFromBytea()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="227">dumpRow()</text>
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="698" y="643" width="2" height="172" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="578" y="813" width="122" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="575" y="640" width="123" height="173" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="637" y="652">OptionProcessor</text>
<line stroke="black" stroke-opacity="1" x1="575" y1="653" x2="698" y2="653" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="665">universe_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="677">start_column_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="689">option_id_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="701">code_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="713">value_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="725">formatted_value_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="737">space_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="749">persistent_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="761">most_recent_option_id_</text>
<line stroke="black" stroke-opacity="1" x1="575" y1="762" x2="698" y2="762" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="774">OptionProcessor()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="786">clear()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="798">retrieveOption()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="810">setColumnNames()</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="90" y="475">impl_</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="87" y="638">conn_</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="76" y="831">conn_</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="230" y="581">host_exchange_</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="215" y="714">host_ipv6_exchange_</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="210" y="755">host_ipv46_exchange_</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="282" y="155">host_ipv6_reservation_exchange_</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="391" y="425">host_option_exchange_</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="513" y="563">opt_proc4_</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="513" y="601">opt_proc6_</text>
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="155" y="486" width="2" height="128" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="7" y="612" width="150" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="4" y="483" width="151" height="129" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="80" y="495">PgSqlHostDataSourceImpl</text>
<line stroke="black" stroke-opacity="1" x1="4" y1="496" x2="155" y2="496" />
<line stroke="black" stroke-opacity="1" x1="4" y1="502" x2="155" y2="502" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="514">PgSqlHostDataSourceImpl()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="526">~PgSqlHostDataSourceImpl()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="538">addStatement()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="550">addResv()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="562">addOption()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="574">addOptions()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="586">getHostCollection()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="598">getHost()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="610">getVersion()</text>
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="80" y1="482" x2="86" y2="476" />
<line stroke="black" stroke-opacity="1" x1="80" y1="482" x2="74" y2="476" />
<line stroke="black" stroke-opacity="1" x1="80" y1="450" x2="80" y2="482" />
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="126" y="650" width="2" height="160" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="12" y="808" width="116" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="9" y="647" width="117" height="161" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="68" y="659">PgSqlConnection</text>
<line stroke="black" stroke-opacity="1" x1="9" y1="660" x2="126" y2="660" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="12" y="672">DUPLICATE_KEY</text>
<line stroke="black" stroke-opacity="1" x1="9" y1="673" x2="126" y2="673" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="685">PgSqlConnection()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="697">~PgSqlConnection()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="709">prepareStatement()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="721">openDatabase()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="733">startTransaction()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="745">commit()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="757">rollback()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="769">compareError()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="781">checkStatementError()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="793">operator PGconn*()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="805">operator bool()</text>
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="497" y="548" width="2" height="76" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="321" y="622" width="178" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="318" y="545" width="179" height="77" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="408" y="557">PgSqlHostWithOptionsExchange</text>
<line stroke="black" stroke-opacity="1" x1="318" y1="558" x2="497" y2="558" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="321" y="570">OPTION_COLUMNS</text>
<line stroke="black" stroke-opacity="1" x1="318" y1="571" x2="497" y2="571" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="321" y="583">PgSqlHostWithOptionsExchange()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="321" y="595">clear()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="321" y="607">processRowData()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="321" y="619">getRequiredColumnsNum()</text>
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="78" y1="646" x2="83" y2="639" />
<line stroke="black" stroke-opacity="1" x1="78" y1="646" x2="71" y2="640" />
<line stroke="black" stroke-opacity="1" x1="77" y1="615" x2="78" y2="646" />
<polygon fill="#000000" stroke="black" stroke-opacity="1" points="77,615 83,620 77,626 71,621" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="317" y1="583" x2="311" y2="576" />
<line stroke="black" stroke-opacity="1" x1="317" y1="583" x2="310" y2="588" />
<line stroke="black" stroke-opacity="1" x1="158" y1="582" x2="317" y2="583" />
<polygon fill="#000000" stroke="black" stroke-opacity="1" points="158,582 164,576 169,582 163,588" />
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="198" y="596">&lt;&lt;:shared_ptr&gt;&gt;</text>
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="118" y="841" width="2" height="64" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="14" y="903" width="106" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="11" y="838" width="107" height="65" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="65" y="850">PgSqlTransaction</text>
<line stroke="black" stroke-opacity="1" x1="11" y1="851" x2="118" y2="851" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="14" y="863">committed_</text>
<line stroke="black" stroke-opacity="1" x1="11" y1="864" x2="118" y2="864" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="14" y="876">PgSqlTransaction()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="14" y="888">~PgSqlTransaction()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="14" y="900">commit()</text>
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="478" y="679" width="2" height="160" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="332" y="837" width="148" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="329" y="676" width="149" height="161" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="404" y="688">PgSqlHostIPv6Exchange</text>
<line stroke="black" stroke-opacity="1" x1="329" y1="689" x2="478" y2="689" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="332" y="701">RESERVATION_COLUMNS</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="713">reservation_id_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="725">address_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="737">prefix_len_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="749">type_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="761">iaid_index_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="773">most_recent_reservation_id_</text>
<line stroke="black" stroke-opacity="1" x1="329" y1="774" x2="478" y2="774" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="786">PgSqlHostIPv6Exchange()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="798">clear()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="810">getReservationId()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="822">retrieveReservation()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="834">processRowData()</text>
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="724" y="272" width="2" height="232" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="552" y="502" width="174" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="549" y="269" width="175" height="233" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="637" y="281">PgSqlHostExchange</text>
<line stroke="black" stroke-opacity="1" x1="549" y1="282" x2="724" y2="282" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="294">HOST_ID_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="306">DHCP_IDENTIFIER_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="318">DHCP_IDENTIFIER_TYPE_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="330">DHCP4_SUBNET_ID_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="342">DHCP6_SUBNET_ID_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="354">IPV4_ADDRESS_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="366">HOSTNAME_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="378">DHCP4_CLIENT_CLASSES_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="390">DHCP6_CLIENT_CLASSES_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="402">HOST_COLUMNS</text>
<line stroke="black" stroke-opacity="1" x1="549" y1="403" x2="724" y2="403" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="415">PgSqlHostExchange()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="427">~PgSqlHostExchange()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="439">clear()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="451">findAvailColumn()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="463">getHostId()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="475">createBindForSend()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="487">processRowData()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="499">retrieveHost()</text>
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="66" y1="811" x2="60" y2="817" />
<line stroke="black" stroke-opacity="1" x1="66" y1="811" x2="72" y2="817" />
<line stroke="black" stroke-opacity="1" x1="66" y1="837" x2="66" y2="811" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="406" y1="675" x2="406" y2="630" />
<line stroke="black" stroke-opacity="1" x1="407" y1="625" x2="400" y2="630" />
<line stroke="black" stroke-opacity="1" x1="407" y1="625" x2="412" y2="631" />
<line stroke="black" stroke-opacity="1" x1="400" y1="630" x2="412" y2="631" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="148" y1="615" x2="148" y2="720" />
<polygon fill="#000000" stroke="black" stroke-opacity="1" points="148,615 154,621 148,627 142,621" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="328" y1="720" x2="322" y2="714" />
<line stroke="black" stroke-opacity="1" x1="328" y1="720" x2="322" y2="726" />
<line stroke="black" stroke-opacity="1" x1="148" y1="720" x2="328" y2="720" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="136" y1="615" x2="136" y2="758" />
<polygon fill="#000000" stroke="black" stroke-opacity="1" points="136,615 142,621 136,627 130,621" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="328" y1="758" x2="322" y2="752" />
<line stroke="black" stroke-opacity="1" x1="328" y1="758" x2="322" y2="764" />
<line stroke="black" stroke-opacity="1" x1="136" y1="758" x2="328" y2="758" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="631" y1="268" x2="631" y2="239" />
<line stroke="black" stroke-opacity="1" x1="631" y1="233" x2="625" y2="239" />
<line stroke="black" stroke-opacity="1" x1="631" y1="233" x2="637" y2="239" />
<line stroke="black" stroke-opacity="1" x1="625" y1="239" x2="637" y2="239" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="409" y1="544" x2="409" y2="487" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="409" y1="487" x2="542" y2="487" />
<line stroke="black" stroke-opacity="1" x1="548" y1="487" x2="542" y2="481" />
<line stroke="black" stroke-opacity="1" x1="548" y1="487" x2="542" y2="493" />
<line stroke="black" stroke-opacity="1" x1="542" y1="481" x2="542" y2="493" />
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="155" y="731">&lt;&lt;:shared_ptr&gt;&gt;</text>
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="145" y="774">&lt;&lt;:shared_ptr&gt;&gt;</text>
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="689" y="562" width="2" height="49" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="580" y="609" width="111" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="577" y="559" width="112" height="50" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="633" y="571">&lt;&lt;typedef&gt;&gt;</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="633" y="584">OptionProcessorPtr</text>
<line stroke="black" stroke-opacity="1" x1="577" y1="585" x2="689" y2="585" />
<line stroke="black" stroke-opacity="1" x1="577" y1="591" x2="689" y2="591" />
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="359" y="83" width="2" height="52" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="187" y="133" width="174" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="184" y="80" width="175" height="53" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="272" y="92">PgSqlIPv6ReservationExchange</text>
<line stroke="black" stroke-opacity="1" x1="184" y1="93" x2="359" y2="93" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="187" y="105">RESRV_COLUMNS</text>
<line stroke="black" stroke-opacity="1" x1="184" y1="106" x2="359" y2="106" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="187" y="118">PgSqlIPv6ReservationExchange()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="187" y="130">createBindForSend()</text>
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="634" y1="639" x2="640" y2="633" />
<line stroke="black" stroke-opacity="1" x1="634" y1="639" x2="628" y2="633" />
<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="634" y1="612" x2="634" y2="639" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="576" y1="582" x2="512" y2="582" />
<ellipse fill="none" stroke="black" stroke-width="1" stroke-opacity="1" cx="506" cy="582" rx="5" ry="5" />
<line stroke="black" stroke-opacity="1" x1="501" y1="582" x2="511" y2="582" />
<line stroke="black" stroke-opacity="1" x1="506" y1="577" x2="506" y2="587" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="576" y1="567" x2="569" y2="561" />
<line stroke="black" stroke-opacity="1" x1="576" y1="567" x2="570" y2="573" />
<line stroke="black" stroke-opacity="1" x1="500" y1="568" x2="576" y2="567" />
<polygon fill="#000000" stroke="black" stroke-opacity="1" points="500,568 505,561 511,567 506,573" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="576" y1="605" x2="570" y2="598" />
<line stroke="black" stroke-opacity="1" x1="576" y1="605" x2="569" y2="610" />
<line stroke="black" stroke-opacity="1" x1="500" y1="603" x2="576" y2="605" />
<polygon fill="#000000" stroke="black" stroke-opacity="1" points="500,603 506,597 511,603 505,609" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="273" y1="79" x2="273" y2="55" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="273" y1="55" x2="550" y2="55" />
<line stroke="black" stroke-opacity="1" x1="556" y1="55" x2="550" y2="49" />
<line stroke="black" stroke-opacity="1" x1="556" y1="55" x2="550" y2="61" />
<line stroke="black" stroke-opacity="1" x1="550" y1="49" x2="550" y2="61" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="158" y1="493" x2="273" y2="493" />
<polygon fill="#000000" stroke="black" stroke-opacity="1" points="158,493 164,487 170,493 164,499" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="273" y1="136" x2="267" y2="142" />
<line stroke="black" stroke-opacity="1" x1="273" y1="136" x2="279" y2="142" />
<line stroke="black" stroke-opacity="1" x1="273" y1="493" x2="273" y2="136" />
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="181" y="489">&lt;&lt;:shared_ptr&gt;&gt;</text>
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="461" y="194" width="2" height="208" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="311" y="400" width="152" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="308" y="191" width="153" height="209" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="385" y="203">PgSqlOptionExchange</text>
<line stroke="black" stroke-opacity="1" x1="308" y1="204" x2="461" y2="204" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="216">OPTION_ID_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="228">CODE_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="240">VALUE_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="252">FORMATTED_VALUE_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="264">SPACE_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="276">PERSISTENT_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="288">DHCP_CLIENT_CLASS_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="300">DHCP_SUBNET_ID_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="312">HOST_ID_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="324">SCOPE_ID_COL</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="336">OPTION_COLUMNS</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="311" y="348">value_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="311" y="360">value_len_</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="311" y="372">option_</text>
<line stroke="black" stroke-opacity="1" x1="308" y1="373" x2="461" y2="373" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="311" y="385">PgSqlOptionExchange()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="311" y="397">createBindForSend()</text>
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="386" y1="190" x2="386" y2="168" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="386" y1="168" x2="550" y2="168" />
<line stroke="black" stroke-opacity="1" x1="556" y1="168" x2="550" y2="162" />
<line stroke="black" stroke-opacity="1" x1="556" y1="168" x2="550" y2="174" />
<line stroke="black" stroke-opacity="1" x1="550" y1="162" x2="550" y2="174" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="158" y1="509" x2="383" y2="509" />
<polygon fill="#000000" stroke="black" stroke-opacity="1" points="158,509 164,503 170,509 164,515" />
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="383" y1="403" x2="377" y2="409" />
<line stroke="black" stroke-opacity="1" x1="383" y1="403" x2="389" y2="409" />
<line stroke="black" stroke-opacity="1" x1="383" y1="509" x2="383" y2="403" />
</g>
<g>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="250" y="522">&lt;&lt;:shared_ptr&gt;&gt;</text>
</g>
<g>
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="137" y="22" width="2" height="188" />
<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="15" y="208" width="124" height="2" />
<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="12" y="19" width="125" height="189" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="75" y="31">BaseHostDataSource</text>
<line stroke="black" stroke-opacity="1" x1="12" y1="32" x2="137" y2="32" />
<line stroke="black" stroke-opacity="1" x1="12" y1="38" x2="137" y2="38" />
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="15" y="50">~BaseHostDataSource()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="62">getAll()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="74">getAll()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="86">getAll4()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="98">get4()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="110">get4()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="122">get4()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="134">get6()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="146">get6()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="158">get6()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="170">add()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="182">getType()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="15" y="194">commit()</text>
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="15" y="206">rollback()</text>
</g>
<g>
<line stroke="black" stroke-opacity="1" x1="74" y1="233" x2="74" y2="217" />
<line stroke="black" stroke-opacity="1" x1="74" y1="211" x2="68" y2="217" />
<line stroke="black" stroke-opacity="1" x1="74" y1="211" x2="80" y2="217" />
<line stroke="black" stroke-opacity="1" x1="68" y1="217" x2="80" y2="217" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -112,6 +112,15 @@ data source (if present) and concatenate results.
For more information about the \ref isc::dhcp::HostMgr please refer to its
documentation.
@subsection postgreSQLHostMgr PostgreSQL Host Reservation Management
Storing and retrieving host reservations within a PostgreSQL schema is
provided by the class, \ref isc::dhcp::PgSqlHostDataSource, a derivation of
\ref isc::dhcp::BaseHostDataSource and is depicted in the following
class diagram:
@image html pgsql_host_data_source.svg "PgSqlHostDataSource Class Diagram"
@section optionsConfig Options Configuration Information
The \ref isc::dhcp::CfgOption object holds a collection of options being

View File

@@ -1779,6 +1779,7 @@ public:
};
namespace {
/// @brief Prepared MySQL statements used by the backend to insert and
/// retrieve hosts from the database.
TaggedStatement tagged_statements[] = {
@@ -1934,6 +1935,8 @@ TaggedStatement tagged_statements[] = {
{MySqlHostDataSourceImpl::NUM_STATEMENTS, NULL}
};
}; // end anonymouse namespace
MySqlHostDataSourceImpl::
MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters)
: host_exchange_(new MySqlHostWithOptionsExchange(MySqlHostWithOptionsExchange::DHCP4_ONLY)),
@@ -2318,6 +2321,7 @@ 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.
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
@@ -2384,6 +2388,7 @@ MySqlHostDataSource::get6(const SubnetID& subnet_id,
ConstHostPtr
MySqlHostDataSource::get6(const asiolink::IOAddress& prefix,
const uint8_t prefix_len) const {
/// @todo: Check that prefix is v6 address, not v4.
// Set up the WHERE clause value
MYSQL_BIND inbind[2];

View File

@@ -35,6 +35,77 @@ const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
PgSqlResult::PgSqlResult(PGresult *result)
: result_(result), rows_(0), cols_(0) {
if (!result) {
isc_throw (BadValue, "PgSqlResult result pointer cannot be null");
}
rows_ = PQntuples(result);
cols_ = PQnfields(result);
}
void
PgSqlResult::rowCheck(int row) const {
if (row < 0 || row >= rows_) {
isc_throw (DbOperationError, "row: " << row
<< ", out of range: 0.." << rows_);
}
}
PgSqlResult::~PgSqlResult() {
if (result_) {
PQclear(result_);
}
}
void
PgSqlResult::colCheck(int col) const {
if (col < 0 || col >= cols_) {
isc_throw (DbOperationError, "col: " << col
<< ", out of range: 0.." << cols_);
}
}
void
PgSqlResult::rowColCheck(int row, int col) const {
rowCheck(row);
colCheck(col);
}
std::string
PgSqlResult::getColumnLabel(const int col) const {
const char* label = NULL;
try {
colCheck(col);
label = PQfname(result_, col);
} catch (...) {
std::ostringstream os;
os << "Unknown column:" << col;
return (os.str());
}
return (label);
}
PgSqlTransaction::PgSqlTransaction(PgSqlConnection& conn)
: conn_(conn), committed_(false) {
conn_.startTransaction();
}
PgSqlTransaction::~PgSqlTransaction() {
// If commit() wasn't explicitly called, rollback.
if (!committed_) {
conn_.rollback();
}
}
void
PgSqlTransaction::commit() {
conn_.commit();
committed_ = true;
}
PgSqlConnection::~PgSqlConnection() {
if (conn_) {
// Deallocate the prepared queries.
@@ -192,6 +263,18 @@ PgSqlConnection::checkStatementError(const PgSqlResult& r,
}
}
void
PgSqlConnection::startTransaction() {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_PGSQL_START_TRANSACTION);
PgSqlResult r(PQexec(conn_, "START TRANSACTION"));
if (PQresultStatus(r) != PGRES_COMMAND_OK) {
const char* error_message = PQerrorMessage(conn_);
isc_throw(DbOperationError, "unable to start transaction"
<< error_message);
}
}
void
PgSqlConnection::commit() {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_COMMIT);

View File

@@ -28,7 +28,6 @@ const size_t PGSQL_MAX_PARAMETERS_IN_QUERY = 32;
/// Each statement is associated with an index, which is used to reference the
/// associated prepared statement.
struct PgSqlTaggedStatement {
/// Number of parameters for a given query
int nbparams;
@@ -48,14 +47,16 @@ struct PgSqlTaggedStatement {
/// @brief Constants for PostgreSQL data types
/// This are defined by PostreSQL in <catalog/pg_type.h>, but including
/// this file is extrordinarily convoluted, so we'll use these to fill-in.
/// this file is extraordinarily convoluted, so we'll use these to fill-in.
const size_t OID_NONE = 0; // PostgreSQL infers proper type
const size_t OID_BOOL = 16;
const size_t OID_BYTEA = 17;
const size_t OID_INT8 = 20; // 8 byte int
const size_t OID_INT2 = 21; // 2 byte int
const size_t OID_TIMESTAMP = 1114;
const size_t OID_INT4 = 23; // 4 byte int
const size_t OID_TEXT = 25;
const size_t OID_VARCHAR = 1043;
const size_t OID_TIMESTAMP = 1114;
//@}
@@ -80,20 +81,60 @@ class PgSqlResult : public boost::noncopyable {
public:
/// @brief Constructor
///
/// Store the pointer to the result set to being fetched.
/// Store the pointer to the result set to being fetched. Set row
/// and column counts for convenience.
///
PgSqlResult(PGresult *result) : result_(result)
{}
PgSqlResult(PGresult *result);
/// @brief Destructor
///
/// Frees the result set
~PgSqlResult() {
if (result_) {
PQclear(result_);
}
~PgSqlResult();
/// @brief Returns the number of rows in the result set.
int getRows() const {
return (rows_);
}
/// @brief Returns the number of columns in the result set.
int getCols() const {
return (cols_);
}
/// @brief Determines if a row index is valid
///
/// @param row index to range check
///
/// @throw DbOperationError if the row index is out of range
void rowCheck(int row) const;
/// @brief Determines if a column index is valid
///
/// @param col index to range check
///
/// @throw DbOperationError if the column index is out of range
void colCheck(int col) const;
/// @brief Determines if both a row and column index are valid
///
/// @param row index to range check
/// @param col index to range check
///
/// @throw DbOperationError if either the row or column index
/// is out of range
void rowColCheck(int row, int col) const;
/// @brief Fetches the name of the column in a result set
///
/// Returns the column name of the column from the result set.
/// If the column index is out of range it will return the
/// string "Unknown column:<index>"
///
/// @param col index of the column name to fetch
/// @return string containing the name of the column
/// This method is exception safe.
std::string getColumnLabel(const int col) const;
/// @brief Conversion Operator
///
/// Allows the PgSqlResult object to be passed as the result set argument to
@@ -111,6 +152,8 @@ public:
private:
PGresult* result_; ///< Result set to be freed
int rows_; ///< Number of rows in the result set
int cols_; ///< Number of columns in the result set
};
@@ -176,6 +219,67 @@ private:
PGconn* pgconn_; ///< Postgresql connection
};
/// @brief Forward declaration to @ref PgSqlConnection.
class PgSqlConnection;
/// @brief RAII object representing a PostgreSQL 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 PgSqlTransaction::commit is
/// explicitly called or when the instance of this class is destroyed.
/// The @ref PgSqlTransaction::commit commits changes to the database.
/// If the class instance is destroyed before @ref PgSqlTransaction::commit
/// has been called, the transaction is rolled back. The rollback on
/// destruction guarantees that partial data is not stored in the database
/// when an error occurs during any of the operations within a transaction.
///
/// By default PostgreSQL performs a commit following each statement which
/// alters the database (i.e. "autocommit"). Starting a transaction
/// stops autocommit for the connection until the transaction is ended by
/// either commit or rollback. Other connections are unaffected.
class PgSqlTransaction : public boost::noncopyable {
public:
/// @brief Constructor.
///
/// Starts transaction by executing the SQL statement: "START TRANSACTION"
///
/// @param conn PostgreSQL connection to use for the transaction. This
/// connection will be later used to commit or rollback changes.
///
/// @throw DbOperationError if statement execution fails
PgSqlTransaction(PgSqlConnection& conn);
/// @brief Destructor.
///
/// If the transaction has not been committed, it is rolled back
/// by executing the SQL statement: "ROLLBACK"
///
/// @throw DbOperationError if statement execution fails
~PgSqlTransaction();
/// @brief Commits transaction.
///
/// Commits all changes made during the transaction by executing the
/// SQL statement: "COMMIT"
///
/// @throw DbOperationError if statement execution fails
void commit();
private:
/// @brief Holds reference to the PostgreSQL database connection.
PgSqlConnection& 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_;
};
/// @brief Common PgSql Connector Pool
///
/// This class provides common operations for PgSql database connection
@@ -218,18 +322,23 @@ public:
/// @throw DbOpenError Error opening the database
void openDatabase();
/// @brief Start a transaction
///
/// Starts a transaction.
///
/// @throw DbOperationError If the transaction start failed.
void startTransaction();
/// @brief Commit Transactions
///
/// Commits all pending database operations. On databases that don't
/// support transactions, this is a no-op.
/// Commits all pending database operations.
///
/// @throw DbOperationError If the commit failed.
void commit();
/// @brief Rollback Transactions
///
/// Rolls back all pending database operations. On databases that don't
/// support transactions, this is a no-op.
/// Rolls back all pending database operations.
///
/// @throw DbOperationError If the rollback failed.
void rollback();
@@ -289,8 +398,6 @@ public:
};
}; // end of isc::dhcp namespace
}; // end of isc namespace

View File

@@ -21,6 +21,10 @@ const char* PsqlBindArray::TRUE_STR = "TRUE";
const char* PsqlBindArray::FALSE_STR = "FALSE";
void PsqlBindArray::add(const char* value) {
if (!value) {
isc_throw(BadValue, "PsqlBindArray::add - char* value cannot be NULL");
}
values_.push_back(value);
lengths_.push_back(strlen(value));
formats_.push_back(TEXT_FMT);
@@ -38,10 +42,52 @@ void PsqlBindArray::add(const std::vector<uint8_t>& data) {
formats_.push_back(BINARY_FMT);
}
void PsqlBindArray::add(const uint8_t* data, const size_t len) {
if (!data) {
isc_throw(BadValue, "PsqlBindArray::add - uint8_t data cannot be NULL");
}
values_.push_back(reinterpret_cast<const char*>(&(data[0])));
lengths_.push_back(len);
formats_.push_back(BINARY_FMT);
}
void PsqlBindArray::add(const bool& value) {
add(value ? TRUE_STR : FALSE_STR);
}
void PsqlBindArray::add(const uint8_t& byte) {
// We static_cast to an unsigned int, otherwise lexcial_cast may to
// treat byte as a character, which yields "" for unprintable values
addTempString(boost::lexical_cast<std::string>
(static_cast<unsigned int>(byte)));
}
void PsqlBindArray::add(const isc::asiolink::IOAddress& addr) {
if (addr.isV4()) {
addTempString(boost::lexical_cast<std::string>
(static_cast<uint32_t>(addr)));
} else {
addTempString(addr.toText());
}
}
void PsqlBindArray::addNull(const int format) {
values_.push_back(NULL);
lengths_.push_back(0);
formats_.push_back(format);
}
/// @todo Eventually this could replace add(std::string&)? This would mean
/// all bound strings would be internally copies rather than perhaps belonging
/// to the originating object such as Host::hostname_. One the one hand it
/// would make all strings handled one-way only, on the other hand it would
/// mean duplicating strings where it isn't strictly necessary.
void PsqlBindArray::addTempString(const std::string& str) {
bound_strs_.push_back(ConstStringPtr(new std::string(str)));
PsqlBindArray::add((bound_strs_.back())->c_str());
}
std::string PsqlBindArray::toText() const {
std::ostringstream stream;
for (int i = 0; i < values_.size(); ++i) {
@@ -54,12 +100,13 @@ std::string PsqlBindArray::toText() const {
stream << "empty" << std::endl;
} else {
stream << "0x";
for (int i = 0; i < lengths_[i]; ++i) {
for (int x = 0; x < lengths_[i]; ++x) {
stream << std::setfill('0') << std::setw(2)
<< std::setbase(16)
<< static_cast<unsigned int>(data[i]);
<< static_cast<unsigned int>(data[x]);
}
stream << std::endl;
stream << std::setbase(10);
}
}
}
@@ -111,18 +158,32 @@ PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val) {
const char*
PgSqlExchange::getRawColumnValue(const PgSqlResult& r, const int row,
const size_t col) const {
const size_t col) {
r.rowColCheck(row,col);
const char* value = PQgetvalue(r, row, col);
if (!value) {
isc_throw(DbOperationError, "getRawColumnValue no data for :"
<< getColumnLabel(col) << " row:" << row);
<< getColumnLabel(r, col) << " row:" << row);
}
return (value);
}
bool
PgSqlExchange::isColumnNull(const PgSqlResult& r, const int row,
const size_t col) {
r.rowColCheck(row,col);
return (PQgetisnull(r, row, col));
}
void
PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
const size_t col, bool &value) const {
const size_t col, std::string& value) {
value = getRawColumnValue(r, row, col);
}
void
PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
const size_t col, bool &value) {
const char* data = getRawColumnValue(r, row, col);
if (!strlen(data) || *data == 'f') {
value = false;
@@ -130,40 +191,14 @@ PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
value = true;
} else {
isc_throw(DbOperationError, "Invalid boolean data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " for: " << getColumnLabel(r, col) << " row:" << row
<< " : must be 't' or 'f'");
}
}
void
PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
const size_t col, uint32_t &value) const {
const char* data = getRawColumnValue(r, row, col);
try {
value = boost::lexical_cast<uint32_t>(data);
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Invalid uint32_t data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : " << ex.what());
}
}
void
PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
const size_t col, int32_t &value) const {
const char* data = getRawColumnValue(r, row, col);
try {
value = boost::lexical_cast<int32_t>(data);
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Invalid int32_t data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : " << ex.what());
}
}
void
PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
const size_t col, uint8_t &value) const {
const size_t col, uint8_t &value) {
const char* data = getRawColumnValue(r, row, col);
try {
// lexically casting as uint8_t doesn't convert from char
@@ -171,7 +206,20 @@ PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
value = boost::lexical_cast<uint16_t>(data);
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Invalid uint8_t data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " for: " << getColumnLabel(r, col) << " row:" << row
<< " : " << ex.what());
}
}
isc::asiolink::IOAddress
PgSqlExchange::getIPv6Value(const PgSqlResult& r, const int row,
const size_t col) {
const char* data = getRawColumnValue(r, row, col);
try {
return (isc::asiolink::IOAddress(data));
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Cannot convert data: " << data
<< " for: " << getColumnLabel(r, col) << " row:" << row
<< " : " << ex.what());
}
}
@@ -180,7 +228,7 @@ void
PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
const size_t col, uint8_t* buffer,
const size_t buffer_size,
size_t &bytes_converted) const {
size_t &bytes_converted) {
// Returns converted bytes in a dynamically allocated buffer, and
// sets bytes_converted.
unsigned char* bytes = PQunescapeBytea((const unsigned char*)
@@ -190,7 +238,7 @@ PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
// Unlikely it couldn't allocate it but you never know.
if (!bytes) {
isc_throw (DbOperationError, "PQunescapeBytea failed for:"
<< getColumnLabel(col) << " row:" << row);
<< getColumnLabel(r, col) << " row:" << row);
}
// Make sure it's not larger than expected.
@@ -199,7 +247,7 @@ PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
PQfreemem(bytes);
isc_throw (DbOperationError, "Converted data size: "
<< bytes_converted << " is too large for: "
<< getColumnLabel(col) << " row:" << row);
<< getColumnLabel(r, col) << " row:" << row);
}
// Copy from the allocated buffer to caller's buffer the free up
@@ -209,14 +257,41 @@ PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
}
std::string
PgSqlExchange::getColumnLabel(const size_t column) const {
if (column > column_labels_.size()) {
std::ostringstream os;
os << "Unknown column:" << column;
return (os.str());
PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
return (r.getColumnLabel(column));
}
std::string
PgSqlExchange::dumpRow(const PgSqlResult& r, int row) {
r.rowCheck(row);
std::ostringstream stream;
int columns = r.getCols();
for (int col = 0; col < columns; ++col) {
const char* val = getRawColumnValue(r, row, col);
std::string name = r.getColumnLabel(col);
int format = PQfformat(r, col);
stream << col << " " << name << " : " ;
if (format == PsqlBindArray::TEXT_FMT) {
stream << "\"" << val << "\"" << std::endl;
} else {
const char *data = val;
int length = PQfsize(r, col);
if (length == 0) {
stream << "empty" << std::endl;
} else {
stream << "0x";
for (int i = 0; i < length; ++i) {
stream << std::setfill('0') << std::setw(2)
<< std::setbase(16)
<< static_cast<unsigned int>(data[i]);
}
stream << std::endl;
}
}
}
return (column_labels_[column]);
return (stream.str());
}
}; // end of isc::dhcp namespace

View File

@@ -7,10 +7,16 @@
#ifndef PGSQL_EXCHANGE_H
#define PGSQL_EXCHANGE_H
#include <asiolink/io_address.h>
#include <dhcpsrv/pgsql_connection.h>
#include <boost/lexical_cast.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <stdint.h>
#include <vector>
#include <iostream>
namespace isc {
namespace dhcp {
@@ -24,6 +30,17 @@ namespace dhcp {
/// be valid for the duration of the PostgreSQL statement execution. In other
/// words populating them with pointers to values that go out of scope before
/// statement is executed is a bad idea.
///
/// Other than vectors or buffers of binary data, all other values are currently
/// converted to their string representation prior to sending them to PostgreSQL.
/// All of the add() method variants which accept a non-string value internally
/// create the conversion string which is then retained in the bind array to ensure
/// scope.
///
/// @brief smart pointer to const std::strings used by PsqlBindArray to ensure scope
/// of strings supplying exchange values
typedef boost::shared_ptr<const std::string> ConstStringPtr;
struct PsqlBindArray {
/// @brief Vector of pointers to the data values.
std::vector<const char *> values_;
@@ -60,41 +77,121 @@ struct PsqlBindArray {
/// @brief Adds a char array to bind array based
///
/// Adds a TEXT_FMT value to the end of the bind array, using the given
/// char* as the data source. Note that value is expected to be NULL
/// terminated.
/// char* as the data source. The value is expected to be NULL
/// terminated. The caller is responsible for ensuring that value
/// remains in scope until the bind array has been discarded.
///
/// @param value char array containing the null-terminated text to add.
/// @throw DbOperationError if value is NULL.
void add(const char* value);
/// @brief Adds an string value to the bind array
///
/// Adds a TEXT formatted value to the end of the bind array using the
/// given string as the data source.
/// given string as the data source. The caller is responsible for
/// ensuring that string parameter remains in scope until the bind
/// array has been discarded.
///
/// @param value std::string containing the value to add.
void add(const std::string& value);
/// @brief Adds a binary value to the bind array.
/// @brief Adds a vector of binary data to the bind array.
///
/// Adds a BINARY_FMT value to the end of the bind array using the
/// given vector as the data source.
/// given vector as the data source. NOTE this does not replicate
/// the vector, so it must remain in scope until the bind array
/// is destroyed.
///
/// @param data vector of binary bytes.
void add(const std::vector<uint8_t>& data);
/// @brief Adds a buffer of binary data to the bind array.
///
/// Adds a BINARY_FMT value to the end of the bind array using the
/// given vector as the data source. NOTE this does not replicate
/// the buffer, so it must remain in scope until the bind array
/// is destroyed.
///
/// @param data buffer of binary data.
/// @param len number of bytes of data in buffer
/// @throw DbOperationError if data is NULL.
void add(const uint8_t* data, const size_t len);
/// @brief Adds a boolean value to the bind array.
///
/// Converts the given boolean value to its corresponding to PostgreSQL
/// string value and adds it as a TEXT_FMT value to the bind array.
/// This creates an internally scoped string.
///
/// @param value bool value to add.
/// @param value the boolean value to add.
void add(const bool& value);
/// @brief Adds a uint8_t value to the bind array.
///
/// Converts the given uint8_t value to its corresponding numeric string
/// literal and adds it as a TEXT_FMT value to the bind array.
/// This creates an internally scoped string.
///
/// @param byte the one byte value to add.
void add(const uint8_t& byte);
/// @brief Adds a the given IOAddress value to the bind array.
///
/// Converts the IOAddress, based on its protocol family, to the
/// corresponding string literal and adds it as a TEXT_FMT value to
/// the bind array.
/// This creates an internally scoped string.
///
/// @param addr IP address value to add.
void add(const isc::asiolink::IOAddress& addr);
/// @brief Adds a the given value to the bind array.
///
/// Converts the given value to its corresponding string literal
/// boost::lexical_cast and adds it as a TEXT_FMT value to the bind array.
/// This is intended primarily for numeric types.
/// This creates an internally scoped string.
///
/// @param value data value to add.
template<typename T>
void add(const T& value) {
addTempString(boost::lexical_cast<std::string>(value));
}
/// @brief Binds a the given string to the bind array.
///
/// Prior to added the The given string the vector of exchange values,
/// it duplicated as a ConstStringPtr and saved internally. This guarantees
/// the string remains in scope until the PsqlBindArray is destroyed,
/// without the caller maintaining the string values.
///
/// @param str string value to add.
void addTempString(const std::string& str);
/// @brief Adds a NULL value to the bind array
///
/// This should be used whenever a the value for a parameter specified
/// in the SQL statement should be NULL.
void addNull(const int format = PsqlBindArray::TEXT_FMT);
//std::vector<const std::string> getBoundStrs() {
std::vector<ConstStringPtr> getBoundStrs() {
return (bound_strs_);
}
/// @brief Dumps the contents of the array to a string.
/// @return std::string containing the dump
std::string toText() const;
private:
/// @brief vector of strings which supplied the values
std::vector<ConstStringPtr> bound_strs_;
};
/// @brief Defines a smart pointer to PsqlBindArray
typedef boost::shared_ptr<PsqlBindArray> PsqlBindArrayPtr;
/// @brief Base class for marshalling data to and from PostgreSQL.
///
/// Provides the common functionality to set up binding information between
@@ -103,7 +200,7 @@ struct PsqlBindArray {
class PgSqlExchange {
public:
/// @brief Constructor
PgSqlExchange(){}
PgSqlExchange(const size_t num_columns = 0) : columns_(num_columns) {}
/// @brief Destructor
virtual ~PgSqlExchange(){}
@@ -164,8 +261,33 @@ public:
///
/// @return a const char* pointer to the column's raw data
/// @throw DbOperationError if the value cannot be fetched.
const char* getRawColumnValue(const PgSqlResult& r, const int row,
const size_t col) const;
static const char* getRawColumnValue(const PgSqlResult& r, const int row,
const size_t col);
/// @brief Fetches the name of the column in a result set
///
/// Returns the column name of the column from the result set.
/// If the column index is out of range it will return the
/// string "Unknown column:<index>". Note this is NOT from the
/// list of columns defined in the exchange.
///
/// @param r the result set containing the query results
/// @param col index of the column name to fetch
///
/// @return string containing the name of the column
static std::string getColumnLabel(const PgSqlResult& r, const size_t col);
/// @brief Fetches text column value as a string
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the string value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
static void getColumnValue(const PgSqlResult& r, const int row,
const size_t col, std::string& value);
/// @brief Fetches boolean text ('t' or 'f') as a bool.
///
@@ -176,32 +298,8 @@ public:
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(const PgSqlResult& r, const int row, const size_t col,
bool &value) const;
/// @brief Fetches an integer text column as a uint32_t.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(const PgSqlResult& r, const int row, const size_t col,
uint32_t &value) const;
/// @brief Fetches an integer text column as a int32_t.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(const PgSqlResult& r, const int row, const size_t col,
int32_t &value) const;
static void getColumnValue(const PgSqlResult& r, const int row,
const size_t col, bool &value);
/// @brief Fetches an integer text column as a uint8_t.
///
@@ -212,8 +310,56 @@ public:
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void getColumnValue(const PgSqlResult& r, const int row, const size_t col,
uint8_t &value) const;
static void getColumnValue(const PgSqlResult& r, const int row,
const size_t col, uint8_t &value);
/// @brief Converts a column in a row in a result set into IPv6 address.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
///
/// @return isc::asiolink::IOAddress containing the IPv6 address.
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
static isc::asiolink::IOAddress getIPv6Value(const PgSqlResult& r,
const int row,
const size_t col);
/// @brief Returns true if a column within a row is null
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
///
/// @return True if the column values in the row is NULL, false otherwise.
static bool isColumnNull(const PgSqlResult& r, const int row,
const size_t col);
/// @brief Fetches a text column as the given value type
///
/// Uses boost::lexicalcast to convert the text column value into
/// a value of type T.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
template<typename T>
static void getColumnValue(const PgSqlResult& r, const int row,
const size_t col, T& value) {
const char* data = getRawColumnValue(r, row, col);
try {
value = boost::lexical_cast<T>(data);
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Invalid data:[" << data
<< "] for row: " << row << " col: " << col << ","
<< getColumnLabel(r, col) << " : " << ex.what());
}
}
/// @brief Converts a column in a row in a result set to a binary bytes
///
@@ -231,17 +377,23 @@ public:
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
void convertFromBytea(const PgSqlResult& r, const int row, const size_t col,
uint8_t* buffer, const size_t buffer_size,
size_t &bytes_converted) const;
static void convertFromBytea(const PgSqlResult& r, const int row,
const size_t col, uint8_t* buffer,
const size_t buffer_size,
size_t &bytes_converted);
/// @brief Returns column label given a column number
std::string getColumnLabel(const size_t column) const;
/// @brief Diagnostic tool which dumps the Result row contents as a string
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
///
/// @return A string depiction of the row contents.
static std::string dumpRow(const PgSqlResult& r, int row);
protected:
/// @brief Stores text labels for columns, currently only used for
/// logging and errors.
std::vector<std::string>column_labels_;
std::vector<std::string>columns_;
};
}; // end of isc::dhcp namespace

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,285 @@
// Copyright (C) 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/.
#ifndef PGSQL_HOST_DATA_SOURCE_H
#define PGSQL_HOST_DATA_SOURCE_H
#include <dhcpsrv/base_host_data_source.h>
#include <dhcpsrv/pgsql_connection.h>
#include <dhcpsrv/pgsql_exchange.h>
namespace isc {
namespace dhcp {
/// Forward declaration to the implementation of the @ref PgSqlHostDataSource.
class PgSqlHostDataSourceImpl;
/// @brief PostgreSQL Host Data Source
///
/// This class implements the @ref isc::dhcp::BaseHostDataSource interface to
/// the PostgreSQL database. Use of this backend presupposes that a PostgreSQL
/// database is available and that the Kea schema has been created within it.
///
/// Reservations are uniquely identified by identifier type and value.
/// The currently supported values are defined in @ref Host::IdentifierType
/// as well as in host_identifier_table:
///
/// - IDENT_HWADDR
/// - IDENT_DUID
/// - IDENT_CIRCUIT_ID
/// - IDENT_CLIENT_ID
///
class PgSqlHostDataSource: 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 SQL 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.
PgSqlHostDataSource(const DatabaseConnection::ParameterMap& parameters);
/// @brief Virtual destructor.
/// Frees database resources and closes the database connection through
/// the destruction of member impl_.
virtual ~PgSqlHostDataSource();
/// @brief Return all hosts for the specified HW address or DUID.
///
/// This method returns all @c 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 hardware address and DUID is allowed for this method
/// and results in returning all objects that are associated with hardware
/// address OR duid. For example: if one host is associated with the
/// specified hardware address and another host is associated with the
/// specified 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 @c 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 @c 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 begining of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
/// @return Collection of const @c 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 @c Host objects if they are connected
/// to different subnets.
///
/// @param address IPv4 address for which the @c Host object is searched.
///
/// @return Collection of const @c Host objects.
virtual ConstHostCollection
getAll4(const asiolink::IOAddress& address) const;
/// @brief Returns a host connected to the IPv4 subnet.
///
/// Implementations of this method should guard against the case when
/// mutliple instances of the @c Host are present, e.g. when two
/// @c Host objects are found, one for the DUID, another one for the
/// HW address. In such case, an implementation of this method
/// should throw an MultipleRecords exception.
///
/// @param subnet_id Subnet identifier.
/// @param hwaddr HW address of the client or NULL if no HW address
/// available.
/// @param duid client id or NULL if not available.
///
/// @return Const @c Host object using a specified HW address or DUID.
virtual ConstHostPtr
get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
const DuidPtr& duid = DuidPtr()) const;
/// @brief Returns a host connected to the IPv4 subnet.
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
/// @param identifier_begin Pointer to a begining of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
/// @return Const @c Host object for which 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 Returns a host connected to the IPv4 subnet and having
/// a reservation for a specified IPv4 address.
///
/// One of the use cases for this method is to detect collisions between
/// dynamically allocated addresses and reserved addresses. When the new
/// address is assigned to a client, the allocation mechanism should check
/// if this address is not reserved for some other host and do not allocate
/// this address if reservation is present.
///
/// @param subnet_id Subnet identifier.
/// @param address reserved IPv4 address.
///
/// @return Const @c Host object using a specified IPv4 address.
/// @throw BadValue is given an IPv6 address
virtual ConstHostPtr
get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
/// @brief Returns a host connected to the IPv6 subnet.
///
/// Implementations of this method should guard against the case when
/// mutliple instances of the @c Host are present, e.g. when two
/// @c Host objects are found, one for the DUID, another one for the
/// HW address. In such case, an implementation of this method
/// should throw an MultipleRecords exception.
///
/// @param subnet_id Subnet identifier.
/// @param hwaddr HW address of the client or NULL if no HW address
/// available.
/// @param duid DUID or NULL if not available.
///
/// @return Const @c Host object using a specified HW address or DUID.
virtual ConstHostPtr
get6(const SubnetID& subnet_id, const DuidPtr& duid,
const HWAddrPtr& hwaddr = HWAddrPtr()) const;
/// @brief Returns a host connected to the IPv6 subnet.
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
/// @param identifier_begin Pointer to a begining of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
/// @return Const @c 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 host using the specified IPv6 prefix.
///
/// @param prefix IPv6 prefix for which the @c Host object is searched.
/// @param prefix_len IPv6 prefix length.
///
/// @return Const @c Host object using a specified HW address or DUID.
virtual ConstHostPtr
get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
/// @brief Adds a new host to the collection.
///
/// The method will insert the given host and all of its children (v4
/// options, v6 options, and v6 reservations) into the database. It
/// relies on constraints defined as part of the PostgreSQL schema to
/// defend against duplicate entries and to ensure referential
/// integrity.
///
/// Violation of any of these constraints for a host will result in a
/// DuplicateEntry exception:
///
/// -# IPV4_ADDRESS and DHCP4_SUBNET_ID combination must be unique
/// -# IPV6 ADDRESS and PREFIX_LEN combination must be unique
/// -# DHCP ID, DHCP ID TYPE, and DHCP4_SUBNET_ID combination must be unique
/// -# DHCP ID, DHCP ID TYPE, and DHCP6_SUBNET_ID combination must be unique
///
/// In addition, violating the following referential contraints will
/// a DbOperationError exception:
///
/// -# DHCP ID TYPE must be defined in the HOST_IDENTIFIER_TYPE table
/// -# For DHCP4 Options:
/// -# HOST_ID must exist with HOSTS
/// -# SCOPE_ID must be defined in DHCP_OPTION_SCOPE
/// -# For DHCP6 Options:
/// -# HOST_ID must exist with HOSTS
/// -# SCOPE_ID must be defined in DHCP_OPTION_SCOPE
/// -# For IPV6 Reservations:
/// -# HOST_ID must exist with HOSTS
/// -# Address and Prefix Length must be unique (DuplicateEntry)
///
/// @param host Pointer to the new @c Host object being added.
/// @throw DuplicateEntry or DbOperationError dependent on the constraint
/// violation
virtual void add(const HostPtr& host);
/// @brief Return backend type
///
/// Returns the type of database as the string "postgresql". This is
/// same value as used for configuration purposes.
///
/// @return Type of the backend.
virtual std::string getType() const {
return (std::string("postgresql"));
}
/// @brief Returns the name of the open database
///
/// @return String containing the name of the database
virtual std::string getName() 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 backend version.
///
/// @return Version number stored in the database, as a pair of unsigned
/// integers. "first" is the major version number, "second" the
/// minor number.
///
/// @throw isc::dhcp::DbOperationError An operation on the open database
/// has failed.
virtual std::pair<uint32_t, uint32_t> getVersion() const;
private:
/// @brief Pointer to the implementation of the @ref PgSqlHostDataSource.
PgSqlHostDataSourceImpl* impl_;
};
}
}
#endif // PGSQL_HOST_DATA_SOURCE_H

View File

@@ -26,6 +26,9 @@ using namespace std;
namespace {
/// @todo TKM lease6 needs to accomodate hwaddr,hwtype, and hwaddr source
/// columns. This is coverd by tickets #3557, #4530, and PR#9.
/// @brief Catalog of all the SQL statements currently supported. Note
/// that the order columns appear in statement body must match the order they
/// that the occur in the table. This does not apply to the where clause.
@@ -278,16 +281,16 @@ public:
memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
// Set the column names (for error messages)
column_labels_.push_back("address");
column_labels_.push_back("hwaddr");
column_labels_.push_back("client_id");
column_labels_.push_back("valid_lifetime");
column_labels_.push_back("expire");
column_labels_.push_back("subnet_id");
column_labels_.push_back("fqdn_fwd");
column_labels_.push_back("fqdn_rev");
column_labels_.push_back("hostname");
column_labels_.push_back("state");
columns_.push_back("address");
columns_.push_back("hwaddr");
columns_.push_back("client_id");
columns_.push_back("valid_lifetime");
columns_.push_back("expire");
columns_.push_back("subnet_id");
columns_.push_back("fqdn_fwd");
columns_.push_back("fqdn_rev");
columns_.push_back("hostname");
columns_.push_back("state");
}
/// @brief Creates the bind array for sending Lease4 data to the database.
@@ -466,19 +469,19 @@ public:
memset(duid_buffer_, 0, sizeof(duid_buffer_));
// Set the column names (for error messages)
column_labels_.push_back("address");
column_labels_.push_back("duid");
column_labels_.push_back("valid_lifetime");
column_labels_.push_back("expire");
column_labels_.push_back("subnet_id");
column_labels_.push_back("pref_lifetime");
column_labels_.push_back("lease_type");
column_labels_.push_back("iaid");
column_labels_.push_back("prefix_len");
column_labels_.push_back("fqdn_fwd");
column_labels_.push_back("fqdn_rev");
column_labels_.push_back("hostname");
column_labels_.push_back("state");
columns_.push_back("address");
columns_.push_back("duid");
columns_.push_back("valid_lifetime");
columns_.push_back("expire");
columns_.push_back("subnet_id");
columns_.push_back("pref_lifetime");
columns_.push_back("lease_type");
columns_.push_back("iaid");
columns_.push_back("prefix_len");
columns_.push_back("fqdn_fwd");
columns_.push_back("fqdn_rev");
columns_.push_back("hostname");
columns_.push_back("state");
}
/// @brief Creates the bind array for sending Lease6 data to the database.
@@ -639,28 +642,7 @@ public:
default:
isc_throw(DbOperationError, "Invalid lease type: " << raw_value
<< " for: " << getColumnLabel(col) << " row:" << row);
}
}
/// @brief Converts a column in a row in a result set into IPv6 address.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
///
/// @return isc::asiolink::IOAddress containing the IPv6 address.
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
isc::asiolink::IOAddress getIPv6Value(const PgSqlResult& r, const int row,
const size_t col) const {
const char* data = getRawColumnValue(r, row, col);
try {
return (isc::asiolink::IOAddress(data));
} catch (const std::exception& ex) {
isc_throw(DbOperationError, "Cannot convert data: " << data
<< " for: " << getColumnLabel(col) << " row:" << row
<< " : " << ex.what());
<< " for: " << getColumnLabel(r, col) << " row:" << row);
}
}

View File

@@ -114,6 +114,7 @@ libdhcpsrv_unittests_SOURCES += ncr_generator_unittest.cc
if HAVE_PGSQL
libdhcpsrv_unittests_SOURCES += pgsql_exchange_unittest.cc
libdhcpsrv_unittests_SOURCES += pgsql_host_data_source_unittest.cc
libdhcpsrv_unittests_SOURCES += pgsql_lease_mgr_unittest.cc
endif
if HAVE_CQL

View File

@@ -6,68 +6,918 @@
#include <config.h>
#include <dhcpsrv/pgsql_connection.h>
#include <dhcpsrv/pgsql_exchange.h>
#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
#include <sstream>
#include <vector>
using namespace isc;
using namespace isc::dhcp;
namespace {
/// @brief Converts a time_t into a string matching our Postgres input format
///
/// @param time_val Time value to convert
/// @retrun A string containing the converted time
std::string timeToDbString(const time_t time_val) {
struct tm tinfo;
char buffer[20];
/// @brief Verifies the ability to add various data types to
/// the bind array.
TEST(PsqlBindArray, addDataTest) {
localtime_r(&time_val, &tinfo);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
return(std::string(buffer));
PsqlBindArray b;
// Declare a vector to add. Vectors are not currently duplicated
// So they will go out of scope, unless caller ensures it.
std::vector<uint8_t> bytes;
for (int i = 0; i < 10; i++) {
bytes.push_back(i+1);
}
// Declare a string
std::string not_temp_str("just a string");
// Now add all the items within a different scope. Everything should
// still be valid once we exit this scope.
{
// Add a const char*
b.add("booya!");
// Add the non temporary string
b.add(not_temp_str);
// Add a temporary string
b.addTempString("walah walah washington");
// Add a one byte int
uint8_t small_int = 25;
b.add(small_int);
// Add a four byte int
int reg_int = 376;
b.add(reg_int);
// Add a eight byte unsigned int
uint64_t big_int = 48786749032;
b.add(big_int);
// Add boolean true and false
b.add((bool)(1));
b.add((bool)(0));
// Add IP addresses
b.add(isc::asiolink::IOAddress("192.2.15.34"));
b.add(isc::asiolink::IOAddress("3001::1"));
// Add the vector
b.add(bytes);
}
// We've left bind scope, everything should be intact.
std::string expected =
"0 : \"booya!\"\n"
"1 : \"just a string\"\n"
"2 : \"walah walah washington\"\n"
"3 : \"25\"\n"
"4 : \"376\"\n"
"5 : \"48786749032\"\n"
"6 : \"TRUE\"\n"
"7 : \"FALSE\"\n"
"8 : \"3221360418\"\n"
"9 : \"3001::1\"\n"
"10 : 0x0102030405060708090a\n";
EXPECT_EQ(expected, b.toText());
}
/// @brief Basic checks on time conversion functions in PgSqlExchange
/// We input timestamps as date/time strings and we output them as
/// an integer string of seconds since the epoch. There is no meangingful
/// way to test them round-trip without Postgres involved.
TEST(PgSqlExchangeTest, convertTimeTest) {
// Get a reference time and time string
time_t ref_time;
time(&ref_time);
/// @brief Defines a pointer to a PgSqlConnection
typedef boost::shared_ptr<PgSqlConnection> PgSqlConnectionPtr;
/// @brief Defines a pointer to a PgSqlResult
typedef boost::shared_ptr<PgSqlResult> PgSqlResultPtr;
std::string ref_time_str(timeToDbString(ref_time));
/// @brief Fixture for exercising basic PostgreSQL operations and data types
///
/// This class is intended to be used to verify basic operations and to
/// verify that each PostgreSQL data type currently used by Kea, can be
/// correctly written to and read from PostgreSQL. Rather than use tables
/// that belong to Kea the schema proper, it creates its own. Currently it
/// consists of a single table, called "basics" which contains one column for
/// each of the supported data types.
///
/// It creates the schema during construction, deletes it upon destruction, and
/// provides functions for executing SQL statements, executing prepared
/// statements, fetching all rows in the table, and deleting all the rows in
/// the table.
class PgSqlBasicsTest : public ::testing::Test {
public:
/// @brief Column index for each column
enum BasicColIndex {
ID_COL,
BOOL_COL,
BYTEA_COL,
BIGINT_COL,
SMALLINT_COL,
INT_COL,
TEXT_COL,
TIMESTAMP_COL,
VARCHAR_COL,
NUM_BASIC_COLS
};
// Verify convertToDatabaseTime gives us the expected localtime string
std::string time_str = PgSqlExchange::convertToDatabaseTime(ref_time);
EXPECT_EQ(time_str, ref_time_str);
/// @brief Constructor
///
/// Creates the database connection, opens the database, and destroys
/// the table (if present) and then recreates it.
PgSqlBasicsTest() : expectedColNames_(NUM_BASIC_COLS) {
// Create database connection parameter list
PgSqlConnection::ParameterMap params;
params["name"] = "keatest";
params["user"] = "keatest";
params["password"] = "keatest";
// Verify convertToDatabaseTime with valid_lifetime = 0 gives us the
// expected localtime string
time_str = PgSqlExchange::convertToDatabaseTime(ref_time, 0);
EXPECT_EQ(time_str, ref_time_str);
// Create and open the database connection
conn_.reset(new PgSqlConnection(params));
conn_->openDatabase();
// Verify we can add time by adding a day.
ref_time_str = timeToDbString(ref_time + (24*3600));
ASSERT_NO_THROW(time_str = PgSqlExchange::convertToDatabaseTime(ref_time,
24*3600));
EXPECT_EQ(time_str, ref_time_str);
// Create the list of expected column names
expectedColNames_[ID_COL] = "id";
expectedColNames_[BOOL_COL] = "bool_col";
expectedColNames_[BYTEA_COL] = "bytea_col";
expectedColNames_[BIGINT_COL] = "bigint_col";
expectedColNames_[SMALLINT_COL] = "smallint_col";
expectedColNames_[INT_COL] = "int_col";
expectedColNames_[TEXT_COL] = "text_col";
expectedColNames_[TIMESTAMP_COL] = "timestamp_col";
expectedColNames_[VARCHAR_COL] = "varchar_col";
// Verify too large of a value is detected.
ASSERT_THROW(PgSqlExchange::convertToDatabaseTime(DatabaseConnection::
MAX_DB_TIME - 1,
24*3600),
isc::BadValue);
destroySchema();
createSchema();
}
// Make sure Conversion "from" database time functions
std::string ref_secs_str = boost::lexical_cast<std::string>(ref_time);
time_t from_time = PgSqlExchange::convertFromDatabaseTime(ref_secs_str);
from_time = PgSqlExchange::convertFromDatabaseTime(ref_secs_str);
EXPECT_EQ(ref_time, from_time);
/// @brief Destructor
///
/// Destroys the table. The database resources are freed and the connection
/// closed by the destruction of conn_.
virtual ~PgSqlBasicsTest () {
destroySchema();
}
/// @brief Gets the expected name of the column for a given column index
///
/// Returns the name of column as we expect it to be when the column is
/// fetched from the database.
///
/// @param col index of the desired column
///
/// @return string containing the column name
///
/// @throw BadValue if the index is out of range
const std::string& expectedColumnName(int col) {
if (col < 0 || col >= NUM_BASIC_COLS) {
isc_throw(BadValue,
"definedColunName: invalid column value" << col);
}
return (expectedColNames_[col]);
}
/// @brief Creates the basics table
/// Asserts if the creation step fails
void createSchema() {
// One column for OID type, plus an auto-increment
const char* sql =
"CREATE TABLE basics ( "
" id SERIAL PRIMARY KEY NOT NULL, "
" bool_col BOOLEAN, "
" bytea_col BYTEA, "
" bigint_col BIGINT, "
" smallint_col SMALLINT, "
" int_col INT, "
" text_col TEXT, "
" timestamp_col TIMESTAMP WITH TIME ZONE, "
" varchar_col VARCHAR(255) "
"); ";
PgSqlResult r(PQexec(*conn_, sql));
ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
<< " create basics table failed: " << PQerrorMessage(*conn_);
}
/// @brief Destroys the basics table
/// Asserts if the destruction fails
void destroySchema() {
if (conn_) {
PgSqlResult r(PQexec(*conn_, "DROP TABLE IF EXISTS basics;"));
ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
<< " drop basics table failed: " << PQerrorMessage(*conn_);
}
}
/// @brief Executes a SQL statement and tests for an expected outcome
///
/// @param r pointer which will contain the result set returned by the
/// statment's execution.
/// @param sql string containing the SQL statement text. Note that
/// PostgreSQL supports executing text which contains more than one SQL
/// statement separated by semicolons.
/// @param exp_outcome expected status value returned with within the
/// result set such as PGRES_COMMAND_OK, PGRES_TUPLES_OK.
/// @lineno line number from where the call was invoked
///
/// Asserts if the result set status does not equal the expected outcome.
void runSql(PgSqlResultPtr& r, const std::string sql, int exp_outcome,
int lineno) {
r.reset(new PgSqlResult(PQexec(*conn_, sql.c_str())));
ASSERT_EQ(PQresultStatus(*r), exp_outcome)
<< " runSql at line: " << lineno << " failed, sql:[" << sql
<< "]\n reason: " << PQerrorMessage(*conn_);
}
/// @brief Executes a SQL statement and tests for an expected outcome
///
/// @param r pointer which will contain the result set returned by the
/// statment's execution.
/// @param statement statement descriptor of the prepared statement
/// to execute.
/// @param bind_array bind array containing the input values to submit
/// along with the statement
/// @param exp_outcome expected status value returned with within the
/// result set such as PGRES_COMMAND_OK, PGRES_TUPLES_OK.
/// @lineno line number from where the call was invoked
///
/// Asserts if the result set status does not equal the expected outcome.
void runPreparedStatement(PgSqlResultPtr& r,
PgSqlTaggedStatement& statement,
PsqlBindArrayPtr bind_array, int exp_outcome,
int lineno) {
r.reset(new PgSqlResult(PQexecPrepared(*conn_, statement.name,
statement.nbparams,
&bind_array->values_[0],
&bind_array->lengths_[0],
&bind_array->formats_[0], 0)));
ASSERT_EQ(PQresultStatus(*r), exp_outcome)
<< " runPreparedStatement at line: " << lineno
<< " statement name:[" << statement.name
<< "]\n reason: " << PQerrorMessage(*conn_);
}
/// @brief Fetches all of the rows currently in the table
///
/// Executes a select statement which returns all of the rows in the
/// basics table, in their order of insertion. Each row contains all
/// of the defined columns, in the order they are defined.
///
/// @param r pointer which will contain the result set returned by the
/// statment's execution.
/// @param exp_rows expected number of rows fetched. (This can be 0).
/// @lineno line number from where the call was invoked
///
/// Asserts if the result set status does not equal the expected outcome.
void fetchRows(PgSqlResultPtr& r, int exp_rows, int line) {
std::string sql =
"SELECT"
" id, bool_col, bytea_col, bigint_col, smallint_col, "
" int_col, text_col,"
" extract(epoch from timestamp_col)::bigint as timestamp_col,"
" varchar_col FROM basics";
runSql(r, sql, PGRES_TUPLES_OK, line);
ASSERT_EQ(r->getRows(), exp_rows) << "fetch at line: " << line
<< " wrong row count, expected: " << exp_rows
<< " , have: " << r->getRows();
}
/// @brief Database connection
PgSqlConnectionPtr conn_;
/// @brief List of column names as we expect them to be in fetched rows
std::vector<std::string> expectedColNames_;
};
// Macros defined to ease passing invocation line number for output tracing
// (Yes I could have used scoped tracing but that's so ugly in code...)
#define RUN_SQL(a,b,c) (runSql(a,b,c, __LINE__))
#define RUN_PREP(a,b,c,d) (runPreparedStatement(a,b,c,d, __LINE__))
#define FETCH_ROWS(a,b) (fetchRows(a,b,__LINE__))
#define WIPE_ROWS(a) (RUN_SQL(a, "DELETE FROM BASICS", PGRES_COMMAND_OK))
/// @brief Verifies that PgResultSet row and colum meta-data is correct
TEST_F(PgSqlBasicsTest, rowColumnBasics) {
// We fetch the table contents, which at this point should be no rows.
PgSqlResultPtr r;
FETCH_ROWS(r, 0);
// Column meta-data is deteremined by the select statement and is
// present whether or not any rows were returned.
EXPECT_EQ(r->getCols(), NUM_BASIC_COLS);
// Negative indexes should be out of range. We test negative values
// as PostgreSQL functions accept column values as type int.
EXPECT_THROW(r->colCheck(-1), DbOperationError);
// Iterate over the column indexes verifying:
// 1. the column is valid
// 2. the result set column name matches the expected column name
for (int i = 0; i < NUM_BASIC_COLS; i++) {
EXPECT_NO_THROW(r->colCheck(i));
EXPECT_EQ(r->getColumnLabel(i), expectedColumnName(i));
}
// Verify above range column value is detected.
EXPECT_THROW(r->colCheck(NUM_BASIC_COLS), DbOperationError);
// Verify the fetching a column label for out of range columns
// do NOT throw.
std::string label;
ASSERT_NO_THROW(label = r->getColumnLabel(-1));
EXPECT_EQ(label, "Unknown column:-1");
ASSERT_NO_THROW(label = r->getColumnLabel(NUM_BASIC_COLS));
std::ostringstream os;
os << "Unknown column:" << NUM_BASIC_COLS;
EXPECT_EQ(label, os.str());
// Verify row count and checking. With an empty result set all values of
// row are invalid.
EXPECT_EQ(r->getRows(), 0);
EXPECT_THROW(r->rowCheck(-1), DbOperationError);
EXPECT_THROW(r->rowCheck(0), DbOperationError);
EXPECT_THROW(r->rowCheck(1), DbOperationError);
// Verify Row-column check will always fail with an empty result set.
EXPECT_THROW(r->rowColCheck(-1, 1), DbOperationError);
EXPECT_THROW(r->rowColCheck(0, 1), DbOperationError);
EXPECT_THROW(r->rowColCheck(1, 1), DbOperationError);
// Insert three minimal rows. We don't really care about column content
// for this test.
int num_rows = 3;
for (int i = 0; i < num_rows; i++) {
RUN_SQL(r, "INSERT INTO basics (bool_col) VALUES ('t')",
PGRES_COMMAND_OK);
}
// Fetch the newly created rows.
FETCH_ROWS(r, num_rows);
// Verify we row count and checking
EXPECT_EQ(r->getRows(), num_rows);
EXPECT_THROW(r->rowCheck(-1), DbOperationError);
// Iterate over the row count, verifying that expected rows are valid
for (int i = 0; i < num_rows; i++) {
EXPECT_NO_THROW(r->rowCheck(i));
EXPECT_NO_THROW(r->rowColCheck(i, 0));
}
// Verify an above range row is detected.
EXPECT_THROW(r->rowCheck(num_rows), DbOperationError);
}
/// @brief Verify that we can read and write BOOL columns
TEST_F(PgSqlBasicsTest, boolTest) {
// Create a prepared statement for inserting bool_col
const char* st_name = "bool_insert";
PgSqlTaggedStatement statement[] = {
{1, { OID_BOOL }, st_name,
"INSERT INTO BASICS (bool_col) values ($1)" }
};
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
bool bools[] = { true, false };
PsqlBindArrayPtr bind_array(new PsqlBindArray());
PgSqlResultPtr r;
// Insert bool rows
for (int i = 0; i < 2; ++i) {
bind_array.reset(new PsqlBindArray());
bind_array->add(bools[i]);
RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
}
// Fetch the newly inserted rows.
FETCH_ROWS(r, 2);
// Verify the fetched bool values are what we expect.
bool fetched_bool;
int row = 0;
for ( ; row < 2; ++row ) {
// Verify the column is not null.
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BOOL_COL));
// Fetch and verify the column value
fetched_bool = !bools[row];
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, BOOL_COL,
fetched_bool));
EXPECT_EQ(fetched_bool, bools[row]);
}
// While we here, verify that bad row throws
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, 1, fetched_bool),
DbOperationError);
// Clean out the table
WIPE_ROWS(r);
// Verify we can insert a NULL boolean
bind_array.reset(new PsqlBindArray());
bind_array->addNull();
// Run the insert with the bind array.
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted row.
FETCH_ROWS(r, 1);
// Verify the column is null.
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, 1));
}
/// @brief Verify that we can read and write BYTEA columns
TEST_F(PgSqlBasicsTest, byteaTest) {
const char* st_name = "bytea_insert";
PgSqlTaggedStatement statement[] = {
{1, { OID_BYTEA }, st_name,
"INSERT INTO BASICS (bytea_col) values ($1)" }
};
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
const uint8_t bytes[] = {
0x01, 0x02, 0x03, 0x04
};
std::vector<uint8_t> vbytes(bytes, bytes + sizeof(bytes));
// Verify we can insert bytea from a vector
PsqlBindArrayPtr bind_array(new PsqlBindArray());
PgSqlResultPtr r;
bind_array->add(vbytes);
RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
// Verify we can insert bytea from a buffer.
bind_array.reset(new PsqlBindArray());
bind_array->add(bytes, sizeof(bytes));
RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted rows.
int num_rows = 2;
FETCH_ROWS(r, num_rows);
uint8_t fetched_bytes[sizeof(bytes)];
size_t byte_count;
int row = 0;
for ( ; row < num_rows; ++row) {
// Verify the column is not null.
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BYTEA_COL));
// Extract the data into a correctly sized buffer
memset(fetched_bytes, 0, sizeof(fetched_bytes));
ASSERT_NO_THROW(PgSqlExchange::convertFromBytea(*r, row, BYTEA_COL,
fetched_bytes,
sizeof(fetched_bytes),
byte_count));
// Verify the data is correct
ASSERT_EQ(byte_count, sizeof(bytes));
for (int i = 0; i < sizeof(bytes); i++) {
ASSERT_EQ(bytes[i], fetched_bytes[i]);
}
}
// While we here, verify that bad row throws
ASSERT_THROW(PgSqlExchange::convertFromBytea(*r, row, BYTEA_COL,
fetched_bytes,
sizeof(fetched_bytes),
byte_count), DbOperationError);
// Verify that too small of a buffer throws
ASSERT_THROW(PgSqlExchange::convertFromBytea(*r, 0, BYTEA_COL,
fetched_bytes,
sizeof(fetched_bytes) - 1,
byte_count), DbOperationError);
// Clean out the table
WIPE_ROWS(r);
// Verify we can insert a NULL for a bytea column
bind_array.reset(new PsqlBindArray());
bind_array->addNull(PsqlBindArray::BINARY_FMT);
RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted row.
FETCH_ROWS(r, 1);
// Verify the column is null.
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, BYTEA_COL));
// Verify that fetching a NULL bytea, returns 0 byte count
ASSERT_NO_THROW(PgSqlExchange::convertFromBytea(*r, 0, BYTEA_COL,
fetched_bytes,
sizeof(fetched_bytes),
byte_count));
EXPECT_EQ(byte_count, 0);
}
/// @brief Verify that we can read and write BIGINT columns
TEST_F(PgSqlBasicsTest, bigIntTest) {
// Create a prepared statement for inserting BIGINT
const char* st_name = "bigint_insert";
PgSqlTaggedStatement statement[] = {
{ 1, { OID_INT8 }, st_name,
"INSERT INTO BASICS (bigint_col) values ($1)" }
};
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
// Build our reference list of reference values
std::vector<int64_t> ints;
ints.push_back(-1);
ints.push_back(0);
ints.push_back(0x7fffffffffffffff);
ints.push_back(0xffffffffffffffff);
// Insert a row for each reference value
PsqlBindArrayPtr bind_array;
PgSqlResultPtr r;
for (int i = 0; i < ints.size(); ++i) {
bind_array.reset(new PsqlBindArray());
bind_array->add(ints[i]);
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
}
// Fetch the newly inserted rows.
FETCH_ROWS(r, ints.size());
// Iterate over the rows, verifying each value against its reference
int64_t fetched_int;
int row = 0;
for ( ; row < ints.size(); ++row ) {
// Verify the column is not null.
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BIGINT_COL));
// Fetch and verify the column value
fetched_int = 777;
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, BIGINT_COL,
fetched_int));
EXPECT_EQ(fetched_int, ints[row]);
}
// While we here, verify that bad row throws
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, BIGINT_COL,
fetched_int), DbOperationError);
// Clean out the table
WIPE_ROWS(r);
// Verify we can insert a NULL value.
bind_array.reset(new PsqlBindArray());
bind_array->addNull();
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted row.
FETCH_ROWS(r, 1);
// Verify the column is null.
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, BIGINT_COL));
}
/// @brief Verify that we can read and write SMALLINT columns
TEST_F(PgSqlBasicsTest, smallIntTest) {
// Create a prepared statement for inserting a SMALLINT
const char* st_name = "smallint_insert";
PgSqlTaggedStatement statement[] = {
{ 1, { OID_INT2 }, st_name,
"INSERT INTO BASICS (smallint_col) values ($1)" }
};
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
// Build our reference list of reference values
std::vector<int16_t>ints;
ints.push_back(-1);
ints.push_back(0);
ints.push_back(0x7fff);
ints.push_back(0xffff);
// Insert a row for each reference value
PsqlBindArrayPtr bind_array;
PgSqlResultPtr r;
for (int i = 0; i < ints.size(); ++i) {
bind_array.reset(new PsqlBindArray());
bind_array->add(ints[i]);
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
}
// Fetch the newly inserted rows.
FETCH_ROWS(r, ints.size());
// Iterate over the rows, verifying each value against its reference
int16_t fetched_int;
int row = 0;
for ( ; row < ints.size(); ++row ) {
// Verify the column is not null.
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, SMALLINT_COL));
// Fetch and verify the column value
fetched_int = 777;
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, SMALLINT_COL,
fetched_int));
EXPECT_EQ(fetched_int, ints[row]);
}
// While we here, verify that bad row throws
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, SMALLINT_COL,
fetched_int),
DbOperationError);
// Clean out the table
WIPE_ROWS(r);
// Verify we can insert a NULL value.
bind_array.reset(new PsqlBindArray());
bind_array->addNull();
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted row.
FETCH_ROWS(r, 1);
// Verify the column is null.
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, SMALLINT_COL));
}
/// @brief Verify that we can read and write INT columns
TEST_F(PgSqlBasicsTest, intTest) {
// Create a prepared statement for inserting an INT
const char* st_name = "int_insert";
PgSqlTaggedStatement statement[] = {
{ 1, { OID_INT4 }, st_name,
"INSERT INTO BASICS (int_col) values ($1)" }
};
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
// Build our reference list of reference values
std::vector<int32_t> ints;
ints.push_back(-1);
ints.push_back(0);
ints.push_back(0x7fffffff);
ints.push_back(0xffffffff);
// Insert a row for each reference value
PsqlBindArrayPtr bind_array;
PgSqlResultPtr r;
for (int i = 0; i < ints.size(); ++i) {
bind_array.reset(new PsqlBindArray());
bind_array->add(ints[i]);
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
}
// Fetch the newly inserted rows.
FETCH_ROWS(r, ints.size());
// Iterate over the rows, verifying each value against its reference
int32_t fetched_int;
int row = 0;
for ( ; row < ints.size(); ++row ) {
// Verify the column is not null.
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INT_COL));
// Fetch and verify the column value
fetched_int = 777;
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, INT_COL,
fetched_int));
EXPECT_EQ(fetched_int, ints[row]);
}
// While we here, verify that bad row throws
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, INT_COL, fetched_int),
DbOperationError);
// Clean out the table
WIPE_ROWS(r);
// Verify we can insert a NULL value.
bind_array.reset(new PsqlBindArray());
bind_array->addNull();
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted rows
FETCH_ROWS(r, 1);
// Verify the column is null.
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, INT_COL));
}
/// @brief Verify that we can read and write TEXT columns
TEST_F(PgSqlBasicsTest, textTest) {
// Create a prepared statement for inserting TEXT
PgSqlTaggedStatement statement[] = {
{ 1, { OID_TEXT }, "text_insert",
"INSERT INTO BASICS (text_col) values ($1)" }
};
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
// Our reference string.
std::string ref_string = "This is a text string";
// Insert the reference from std::string
PsqlBindArrayPtr bind_array;
PgSqlResultPtr r;
bind_array.reset(new PsqlBindArray());
bind_array->add(ref_string);
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Insert the reference from a buffer
bind_array.reset(new PsqlBindArray());
bind_array->add(ref_string.c_str());
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted rows.
FETCH_ROWS(r, 2);
// Iterate over the rows, verifying the value against the reference
std::string fetched_str;
int row = 0;
for ( ; row < 2; ++row ) {
// Verify the column is not null.
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, TEXT_COL));
// Fetch and verify the column value
fetched_str = "";
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, TEXT_COL,
fetched_str));
EXPECT_EQ(fetched_str, ref_string);
}
// While we here, verify that bad row throws
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, TEXT_COL, fetched_str),
DbOperationError);
// Clean out the table
WIPE_ROWS(r);
// Verify we can insert a NULL value.
bind_array.reset(new PsqlBindArray());
bind_array->addNull();
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted row.
FETCH_ROWS(r, 1);
// Verify the column is null.
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, TEXT_COL));
}
/// @brief Verify that we can read and write VARCHAR columns
TEST_F(PgSqlBasicsTest, varcharTest) {
// Create a prepared statement for inserting a VARCHAR
PgSqlTaggedStatement statement[] = {
{ 1, { OID_VARCHAR }, "varchar_insert",
"INSERT INTO BASICS (varchar_col) values ($1)" }
};
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
// Our reference string.
std::string ref_string = "This is a varchar string";
// Insert the reference from std::string
PsqlBindArrayPtr bind_array;
PgSqlResultPtr r;
bind_array.reset(new PsqlBindArray());
bind_array->add(ref_string);
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Insert the reference from a buffer
bind_array.reset(new PsqlBindArray());
bind_array->add(ref_string.c_str());
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted rows.
FETCH_ROWS(r, 2);
// Iterate over the rows, verifying the value against the reference
std::string fetched_str;
int row = 0;
for ( ; row < 2; ++row ) {
// Verify the column is not null.
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, VARCHAR_COL));
// Fetch and verify the column value
fetched_str = "";
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, VARCHAR_COL,
fetched_str));
EXPECT_EQ(fetched_str, ref_string);
}
// While we here, verify that bad row throws
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, VARCHAR_COL,
fetched_str), DbOperationError);
// Clean out the table
WIPE_ROWS(r);
// Verify we can insert a NULL value.
bind_array.reset(new PsqlBindArray());
bind_array->addNull();
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted rows
FETCH_ROWS(r, 1);
// Verify the column is null.
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, VARCHAR_COL));
}
/// @brief Verify that we can read and write TIMESTAMP columns
TEST_F(PgSqlBasicsTest, timeStampTest) {
// Create a prepared statement for inserting a TIMESTAMP
PgSqlTaggedStatement statement[] = {
{ 1, { OID_TIMESTAMP }, "timestamp_insert",
"INSERT INTO BASICS (timestamp_col) values ($1)" }
};
ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
// Build our list of reference times
time_t now;
time(&now);
std::vector<time_t> times;
times.push_back(now);
times.push_back(DatabaseConnection::MAX_DB_TIME);
// Note on a 32-bit OS this value is really -1. PosgreSQL will store it
// and return it intact.
times.push_back(0xFFFFFFFF);
// Insert a row for each reference value
PsqlBindArrayPtr bind_array;
PgSqlResultPtr r;
std::string time_str;
for (int i = 0; i < times.size(); ++i) {
// Timestamps are inserted as strings so convert them first
ASSERT_NO_THROW(time_str =
PgSqlExchange::convertToDatabaseTime(times[i]));
// Add it to the bind array and insert it
bind_array.reset(new PsqlBindArray());
bind_array->add(time_str);
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
}
// Insert a row with ref time plus one day
times.push_back(now + 24*3600);
ASSERT_NO_THROW(time_str =
PgSqlExchange::convertToDatabaseTime(times[0], 24*3600));
bind_array.reset(new PsqlBindArray());
bind_array->add(time_str);
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted rows.
FETCH_ROWS(r, times.size());
// Iterate over the rows, verifying the value against its reference
std::string fetched_str;
int row = 0;
for ( ; row < times.size(); ++row ) {
// Verify the column is not null.
ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, TIMESTAMP_COL));
// Fetch and verify the column value
fetched_str = "";
ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, TIMESTAMP_COL,
fetched_str));
time_t fetched_time;
ASSERT_NO_THROW(fetched_time =
PgSqlExchange::convertFromDatabaseTime(fetched_str));
EXPECT_EQ(fetched_time, times[row]) << " row: " << row;
}
// While we here, verify that bad row throws
ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, TIMESTAMP_COL,
fetched_str), DbOperationError);
// Clean out the table
WIPE_ROWS(r);
// Verify we can insert a NULL value.
bind_array.reset(new PsqlBindArray());
bind_array->addNull();
RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
// Fetch the newly inserted rows
FETCH_ROWS(r, 1);
// Verify the column is null.
ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, TIMESTAMP_COL));
// Verify exceeding max time throws
ASSERT_THROW(PgSqlExchange::convertToDatabaseTime(times[0],
DatabaseConnection::
MAX_DB_TIME), BadValue);
}
}; // namespace

View File

@@ -0,0 +1,474 @@
// Copyright (C) 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/tests/test_utils.h>
#include <exceptions/exceptions.h>
#include <dhcpsrv/host.h>
#include <dhcpsrv/pgsql_connection.h>
#include <dhcpsrv/pgsql_host_data_source.h>
#include <dhcpsrv/tests/generic_host_data_source_unittest.h>
#include <dhcpsrv/testutils/pgsql_schema.h>
#include <dhcpsrv/host_data_source_factory.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 PgSqlHostDataSourceTest : public GenericHostDataSourceTest {
public:
/// @brief Constructor
///
/// Deletes everything from the database and opens it.
PgSqlHostDataSourceTest() {
// Ensure schema is the correct one.
destroyPgSQLSchema();
createPgSQLSchema();
// Connect to the database
try {
HostDataSourceFactory::create(validPgSQLConnectionString());
} catch (...) {
std::cerr << "*** ERROR: unable to open database. The test\n"
"*** environment is broken and must be fixed before\n"
"*** the PostgreSQL tests will run correctly.\n"
"*** The reason for the problem is described in the\n"
"*** accompanying exception output.\n";
throw;
}
hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
}
/// @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 ~PgSqlHostDataSourceTest() {
hdsptr_->rollback();
HostDataSourceFactory::destroy();
destroyPgSQLSchema();
}
/// @brief Reopen the database
///
/// Closes the database and re-open it. Anything committed should be
/// visible.
///
/// Parameter is ignored for PostgreSQL backend as the v4 and v6 leases
/// share the same database.
void reopen(Universe) {
HostDataSourceFactory::destroy();
HostDataSourceFactory::create(validPgSQLConnectionString());
hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
}
};
/// @brief Check that database can be opened
///
/// This test checks if the PgSqlHostDataSource can be instantiated. This happens
/// only if the database can be opened. Note that this is not part of the
/// PgSqlLeaseMgr test fixure set. This test checks that the database can be
/// opened: the fixtures assume that and check basic operations.
TEST(PgSqlHostDataSource, OpenDatabase) {
// Schema needs to be created for the test to work.
destroyPgSQLSchema();
createPgSQLSchema();
// Check that lease manager open the database opens correctly and tidy up.
// If it fails, print the error message.
try {
HostDataSourceFactory::create(validPgSQLConnectionString());
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 PostgreSQL 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 {
string connection_string = validPgSQLConnectionString() + string(" ") +
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 PostgreSQL tests will run correctly.\n";
}
// Check that attempting to get an instance of the lease manager 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 LeaseMgrFactory, 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 causes an exception.
EXPECT_THROW(HostDataSourceFactory::create(connectionString(
PGSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
DbOpenError);
EXPECT_THROW(HostDataSourceFactory::create(connectionString(
PGSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
DbOpenError);
EXPECT_THROW(HostDataSourceFactory::create(connectionString(
PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
DbOpenError);
EXPECT_THROW(HostDataSourceFactory::create(connectionString(
PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
DbOpenError);
EXPECT_THROW(HostDataSourceFactory::create(connectionString(
PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_1)),
DbInvalidTimeout);
EXPECT_THROW(HostDataSourceFactory::create(connectionString(
PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)),
DbInvalidTimeout);
// Check for missing parameters
EXPECT_THROW(HostDataSourceFactory::create(connectionString(
PGSQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
NoDatabaseName);
// Tidy up after the test
destroyPgSQLSchema();
}
// Test verifies if a host reservation can be added and later retrieved by IPv4
// address. Host uses hw address as identifier.
TEST_F(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, getByIPv4ClientId) {
testGetByIPv4(Host::IDENT_DUID);
}
// Test verifies if a host reservation can be added and later retrieved by
// hardware address.
TEST_F(PgSqlHostDataSourceTest, get4ByHWaddr) {
testGet4ByIdentifier(Host::IDENT_HWADDR);
}
// Test verifies if a host reservation can be added and later retrieved by
// DUID.
TEST_F(PgSqlHostDataSourceTest, get4ByDUID) {
testGet4ByIdentifier(Host::IDENT_DUID);
}
// Test verifies if a host reservation can be added and later retrieved by
// circuit id.
TEST_F(PgSqlHostDataSourceTest, get4ByCircuitId) {
testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
}
// Test verifies if hardware address and client identifier are not confused.
TEST_F(PgSqlHostDataSourceTest, hwaddrNotClientId1) {
testHWAddrNotClientId();
}
// Test verifies if hardware address and client identifier are not confused.
TEST_F(PgSqlHostDataSourceTest, hwaddrNotClientId2) {
testClientIdNotHWAddr();
}
// Test verifies if a host with FQDN hostname can be stored and later retrieved.
TEST_F(PgSqlHostDataSourceTest, hostnameFQDN) {
testHostname("foo.example.org", 1);
}
// Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
// retrieved.
TEST_F(PgSqlHostDataSourceTest, hostnameFQDN100) {
testHostname("foo.example.org", 100);
}
// Test verifies if a host without any hostname specified can be stored and
// later retrieved.
TEST_F(PgSqlHostDataSourceTest, noHostname) {
testHostname("", 1);
}
// Test verifies if the hardware or client-id query can match hardware address.
TEST_F(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, get6PrefixWithHWaddr) {
testGetByIPv6(Host::IDENT_HWADDR, true);
}
// Test verifies if a host reservation can be added and later retrieved by
// hardware address.
TEST_F(PgSqlHostDataSourceTest, get6ByHWaddr) {
testGet6ByHWAddr();
}
// Test verifies if a host reservation can be added and later retrieved by
// client identifier.
TEST_F(PgSqlHostDataSourceTest, get6ByClientId) {
testGet6ByClientId();
}
// Test verifies if a host reservation can be stored with both IPv6 address and
// prefix.
TEST_F(PgSqlHostDataSourceTest, addr6AndPrefix) {
testAddr6AndPrefix();
}
// Tests if host with multiple IPv6 reservations can be added and then
// retrieved correctly. Test checks reservations comparing.
TEST_F(PgSqlHostDataSourceTest, multipleReservations){
testMultipleReservations();
}
// Tests if compareIPv6Reservations() method treats same pool of reservations
// but added in different order as equal.
TEST_F(PgSqlHostDataSourceTest, multipleReservationsDifferentOrder){
testMultipleReservationsDifferentOrder();
}
// Test verifies if multiple client classes for IPv4 can be stored.
TEST_F(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, addDuplicate4) {
testAddDuplicate4();
}
// This test verifies that DHCPv4 options can be inserted in a binary format
/// and retrieved from the PostgreSQL host database.
TEST_F(PgSqlHostDataSourceTest, optionsReservations4) {
testOptionsReservations4(false);
}
// This test verifies that DHCPv6 options can be inserted in a binary format
/// and retrieved from the PostgreSQL host database.
TEST_F(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, optionsReservations46) {
testOptionsReservations46(false);
}
// This test verifies that DHCPv4 options can be inserted in a textual format
/// and retrieved from the PostgreSQL host database.
TEST_F(PgSqlHostDataSourceTest, formattedOptionsReservations4) {
testOptionsReservations4(true);
}
// This test verifies that DHCPv6 options can be inserted in a textual format
/// and retrieved from the PostgreSQL host database.
TEST_F(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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(PgSqlHostDataSourceTest, 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.
PgSqlConnection::ParameterMap params;
params["name"] = "keatest";
params["user"] = "keatest";
params["password"] = "keatest";
PgSqlConnection conn(params);
ASSERT_NO_THROW(conn.openDatabase());
PgSqlResult r(PQexec(conn, "DROP TABLE IF EXISTS ipv6_reservations"));
ASSERT_TRUE (PQresultStatus(r) == PGRES_COMMAND_OK)
<< " drop command failed :" << PQerrorMessage(conn);
// 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.
ConstHostPtr from_hds = hdsptr_->get4(host->getIPv4SubnetID(),
host->getIdentifierType(),
&host->getIdentifier()[0],
host->getIdentifier().size());
EXPECT_FALSE(from_hds);
}
}; // Of anonymous namespace