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:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
||||
|
419
src/lib/dhcpsrv/images/pgsql_host_data_source.svg
Normal file
419
src/lib/dhcpsrv/images/pgsql_host_data_source.svg
Normal 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"><<:shared_ptr>></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"><<:shared_ptr>></text>
|
||||
</g>
|
||||
<g>
|
||||
<text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="145" y="774"><<:shared_ptr>></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"><<typedef>></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"><<:shared_ptr>></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"><<:shared_ptr>></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 |
@@ -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
|
||||
|
@@ -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];
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
1898
src/lib/dhcpsrv/pgsql_host_data_source.cc
Normal file
1898
src/lib/dhcpsrv/pgsql_host_data_source.cc
Normal file
File diff suppressed because it is too large
Load Diff
285
src/lib/dhcpsrv/pgsql_host_data_source.h
Normal file
285
src/lib/dhcpsrv/pgsql_host_data_source.h
Normal 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
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
474
src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
Normal file
474
src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
Normal 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
|
Reference in New Issue
Block a user