summaryrefslogtreecommitdiffstats
path: root/src/lib/pgsql
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/pgsql
parentInitial commit. (diff)
downloadisc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.tar.xz
isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/pgsql')
-rw-r--r--src/lib/pgsql/Makefile.am31
-rw-r--r--src/lib/pgsql/Makefile.in959
-rw-r--r--src/lib/pgsql/pgsql_connection.cc560
-rw-r--r--src/lib/pgsql/pgsql_connection.h597
-rw-r--r--src/lib/pgsql/pgsql_exchange.cc767
-rw-r--r--src/lib/pgsql/pgsql_exchange.h1004
-rw-r--r--src/lib/pgsql/tests/Makefile.am40
-rw-r--r--src/lib/pgsql/tests/Makefile.in1066
-rw-r--r--src/lib/pgsql/tests/pgsql_basics.cc149
-rw-r--r--src/lib/pgsql/tests/pgsql_basics.h161
-rw-r--r--src/lib/pgsql/tests/pgsql_connection_unittest.cc651
-rw-r--r--src/lib/pgsql/tests/pgsql_exchange_unittest.cc1545
-rw-r--r--src/lib/pgsql/tests/run_unittests.cc20
-rw-r--r--src/lib/pgsql/testutils/Makefile.am24
-rw-r--r--src/lib/pgsql/testutils/Makefile.in862
-rw-r--r--src/lib/pgsql/testutils/pgsql_schema.cc103
-rw-r--r--src/lib/pgsql/testutils/pgsql_schema.h105
17 files changed, 8644 insertions, 0 deletions
diff --git a/src/lib/pgsql/Makefile.am b/src/lib/pgsql/Makefile.am
new file mode 100644
index 0000000..fbefd8c
--- /dev/null
+++ b/src/lib/pgsql/Makefile.am
@@ -0,0 +1,31 @@
+SUBDIRS = . testutils tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(PGSQL_CPPFLAGS)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-pgsql.la
+libkea_pgsql_la_SOURCES = pgsql_connection.cc pgsql_connection.h
+libkea_pgsql_la_SOURCES += pgsql_exchange.cc pgsql_exchange.h
+
+
+libkea_pgsql_la_LIBADD = $(top_builddir)/src/lib/database/libkea-database.la
+libkea_pgsql_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_pgsql_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_pgsql_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_pgsql_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_pgsql_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_pgsql_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+
+libkea_pgsql_la_LDFLAGS = -no-undefined -version-info 53:0:0
+
+libkea_pgsql_la_LDFLAGS += $(PGSQL_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_pgsql_includedir = $(pkgincludedir)/pgsql
+libkea_pgsql_include_HEADERS = \
+ pgsql_connection.h \
+ pgsql_exchange.h
diff --git a/src/lib/pgsql/Makefile.in b/src/lib/pgsql/Makefile.in
new file mode 100644
index 0000000..d2aa868
--- /dev/null
+++ b/src/lib/pgsql/Makefile.in
@@ -0,0 +1,959 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/pgsql
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(libkea_pgsql_include_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)" \
+ "$(DESTDIR)$(libkea_pgsql_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_pgsql_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_libkea_pgsql_la_OBJECTS = pgsql_connection.lo pgsql_exchange.lo
+libkea_pgsql_la_OBJECTS = $(am_libkea_pgsql_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libkea_pgsql_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_pgsql_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/pgsql_connection.Plo \
+ ./$(DEPDIR)/pgsql_exchange.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libkea_pgsql_la_SOURCES)
+DIST_SOURCES = $(libkea_pgsql_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libkea_pgsql_include_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = . testutils tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES) $(PGSQL_CPPFLAGS)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-pgsql.la
+libkea_pgsql_la_SOURCES = pgsql_connection.cc pgsql_connection.h \
+ pgsql_exchange.cc pgsql_exchange.h
+libkea_pgsql_la_LIBADD = \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+libkea_pgsql_la_LDFLAGS = -no-undefined -version-info 53:0:0 \
+ $(PGSQL_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_pgsql_includedir = $(pkgincludedir)/pgsql
+libkea_pgsql_include_HEADERS = \
+ pgsql_connection.h \
+ pgsql_exchange.h
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/pgsql/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/pgsql/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libkea-pgsql.la: $(libkea_pgsql_la_OBJECTS) $(libkea_pgsql_la_DEPENDENCIES) $(EXTRA_libkea_pgsql_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_pgsql_la_LINK) -rpath $(libdir) $(libkea_pgsql_la_OBJECTS) $(libkea_pgsql_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pgsql_connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pgsql_exchange.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_pgsql_includeHEADERS: $(libkea_pgsql_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_pgsql_include_HEADERS)'; test -n "$(libkea_pgsql_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_pgsql_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_pgsql_includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libkea_pgsql_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_pgsql_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_pgsql_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_pgsql_include_HEADERS)'; test -n "$(libkea_pgsql_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_pgsql_includedir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_pgsql_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/pgsql_connection.Plo
+ -rm -f ./$(DEPDIR)/pgsql_exchange.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-libkea_pgsql_includeHEADERS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/pgsql_connection.Plo
+ -rm -f ./$(DEPDIR)/pgsql_exchange.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES \
+ uninstall-libkea_pgsql_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libLTLIBRARIES \
+ install-libkea_pgsql_includeHEADERS install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-libLTLIBRARIES uninstall-libkea_pgsql_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/pgsql/pgsql_connection.cc b/src/lib/pgsql/pgsql_connection.cc
new file mode 100644
index 0000000..6ec896d
--- /dev/null
+++ b/src/lib/pgsql/pgsql_connection.cc
@@ -0,0 +1,560 @@
+// Copyright (C) 2016-2023 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 <database/db_exceptions.h>
+#include <database/db_log.h>
+#include <pgsql/pgsql_connection.h>
+
+// PostgreSQL errors should be tested based on the SQL state code. Each state
+// code is 5 decimal, ASCII, digits, the first two define the category of
+// error, the last three are the specific error. PostgreSQL makes the state
+// code as a char[5]. Macros for each code are defined in PostgreSQL's
+// server/utils/errcodes.h, although they require a second macro,
+// MAKE_SQLSTATE for completion. For example, duplicate key error as:
+//
+// #define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3','5','0','5')
+//
+// PostgreSQL deliberately omits the MAKE_SQLSTATE macro so callers can/must
+// supply their own. We'll define it as an initialization list:
+#define MAKE_SQLSTATE(ch1,ch2,ch3,ch4,ch5) {ch1,ch2,ch3,ch4,ch5}
+// So we can use it like this: const char some_error[] = ERRCODE_xxxx;
+#define PGSQL_STATECODE_LEN 5
+#include <utils/errcodes.h>
+
+#include <sstream>
+
+using namespace std;
+
+namespace isc {
+namespace db {
+
+// Default connection timeout
+
+/// @todo: migrate this default timeout to src/bin/dhcpX/simple_parserX.cc
+const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
+
+const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
+const char PgSqlConnection::NULL_KEY[] = ERRCODE_NOT_NULL_VIOLATION;
+
+bool PgSqlConnection::warned_about_tls = false;
+
+PgSqlResult::PgSqlResult(PGresult *result)
+ : result_(result), rows_(0), cols_(0) {
+ if (!result) {
+ // Certain failures, like a loss of connectivity, can return a
+ // null PGresult and we still need to be able to create a PgSqlResult.
+ // We'll set row and col counts to -1 to prevent anyone going off the
+ // rails.
+ rows_ = -1;
+ cols_ = -1;
+ } else {
+ rows_ = PQntuples(result);
+ cols_ = PQnfields(result);
+ }
+}
+
+void
+PgSqlResult::rowCheck(int row) const {
+ if (row < 0 || row >= rows_) {
+ isc_throw (db::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.
+ if (PQstatus(conn_) == CONNECTION_OK) {
+ PgSqlResult r(PQexec(conn_, "DEALLOCATE all"));
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ // Highly unlikely but we'll log it and go on.
+ DB_LOG_ERROR(PGSQL_DEALLOC_ERROR)
+ .arg(PQerrorMessage(conn_));
+ }
+ }
+ }
+}
+
+std::pair<uint32_t, uint32_t>
+PgSqlConnection::getVersion(const ParameterMap& parameters) {
+ // Get a connection.
+ PgSqlConnection conn(parameters);
+
+ // Open the database.
+ conn.openDatabaseInternal(false);
+
+ const char* version_sql = "SELECT version, minor FROM schema_version;";
+ PgSqlResult r(PQexec(conn.conn_, version_sql));
+ if (PQresultStatus(r) != PGRES_TUPLES_OK) {
+ isc_throw(DbOperationError, "unable to execute PostgreSQL statement <"
+ << version_sql << ", reason: " << PQerrorMessage(conn.conn_));
+ }
+
+ uint32_t version;
+ PgSqlExchange::getColumnValue(r, 0, 0, version);
+
+ uint32_t minor;
+ PgSqlExchange::getColumnValue(r, 0, 1, minor);
+
+ return (make_pair(version, minor));
+}
+
+void
+PgSqlConnection::prepareStatement(const PgSqlTaggedStatement& statement) {
+ // Prepare all statements queries with all known fields datatype
+ PgSqlResult r(PQprepare(conn_, statement.name, statement.text,
+ statement.nbparams, statement.types));
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ isc_throw(DbOperationError, "unable to prepare PostgreSQL statement: "
+ << " name: " << statement.name
+ << ", reason: " << PQerrorMessage(conn_)
+ << ", text: " << statement.text);
+ }
+}
+
+void
+PgSqlConnection::prepareStatements(const PgSqlTaggedStatement* start_statement,
+ const PgSqlTaggedStatement* end_statement) {
+ // Created the PostgreSQL prepared statements.
+ for (const PgSqlTaggedStatement* tagged_statement = start_statement;
+ tagged_statement != end_statement; ++tagged_statement) {
+ prepareStatement(*tagged_statement);
+ }
+}
+
+std::string
+PgSqlConnection::getConnParameters() {
+ return (getConnParametersInternal(false));
+}
+
+std::string
+PgSqlConnection::getConnParametersInternal(bool logging) {
+ string dbconnparameters;
+ string shost = "localhost";
+ try {
+ shost = getParameter("host");
+ } catch(...) {
+ // No host. Fine, we'll use "localhost"
+ }
+
+ dbconnparameters += "host = '" + shost + "'" ;
+
+ unsigned int port = 0;
+ try {
+ setIntParameterValue("port", 0, numeric_limits<uint16_t>::max(), port);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbInvalidPort, ex.what());
+ }
+
+ // Add port to connection parameters when not default.
+ if (port > 0) {
+ std::ostringstream oss;
+ oss << port;
+ dbconnparameters += " port = " + oss.str();
+ }
+
+ string suser;
+ try {
+ suser = getParameter("user");
+ dbconnparameters += " user = '" + suser + "'";
+ } catch(...) {
+ // No user. Fine, we'll use NULL
+ }
+
+ string spassword;
+ try {
+ spassword = getParameter("password");
+ dbconnparameters += " password = '" + spassword + "'";
+ } catch(...) {
+ // No password. Fine, we'll use NULL
+ }
+
+ string sname;
+ try {
+ sname = getParameter("name");
+ dbconnparameters += " dbname = '" + sname + "'";
+ } catch(...) {
+ // No database name. Throw a "NoDatabaseName" exception
+ isc_throw(NoDatabaseName, "must specify a name for the database");
+ }
+
+ unsigned int connect_timeout = PGSQL_DEFAULT_CONNECTION_TIMEOUT;
+ unsigned int tcp_user_timeout = 0;
+ try {
+ // The timeout is only valid if greater than zero, as depending on the
+ // database, a zero timeout might signify something like "wait
+ // indefinitely".
+ setIntParameterValue("connect-timeout", 1, numeric_limits<int>::max(), connect_timeout);
+ // This timeout value can be 0, meaning that the database client will
+ // follow a default behavior. Earlier Postgres versions didn't have
+ // this parameter, so we allow 0 to skip setting them for these
+ // earlier versions.
+ setIntParameterValue("tcp-user-timeout", 0, numeric_limits<int>::max(), tcp_user_timeout);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbInvalidTimeout, ex.what());
+ }
+
+ // Append connection timeout.
+ std::ostringstream oss;
+ oss << " connect_timeout = " << connect_timeout;
+
+ if (tcp_user_timeout > 0) {
+// tcp_user_timeout parameter is a PostgreSQL 12+ feature.
+#ifdef HAVE_PGSQL_TCP_USER_TIMEOUT
+ oss << " tcp_user_timeout = " << tcp_user_timeout * 1000;
+ static_cast<void>(logging);
+#else
+ if (logging) {
+ DB_LOG_WARN(PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED).arg();
+ }
+#endif
+ }
+ dbconnparameters += oss.str();
+
+ return (dbconnparameters);
+}
+
+void
+PgSqlConnection::openDatabase() {
+ openDatabaseInternal(true);
+}
+
+void
+PgSqlConnection::openDatabaseInternal(bool logging) {
+ std::string dbconnparameters = getConnParametersInternal(logging);
+ // Connect to Postgres, saving the low level connection pointer
+ // in the holder object
+ PGconn* new_conn = PQconnectdb(dbconnparameters.c_str());
+ if (!new_conn) {
+ isc_throw(DbOpenError, "could not allocate connection object");
+ }
+
+ if (PQstatus(new_conn) != CONNECTION_OK) {
+ // If we have a connection object, we have to call finish
+ // to release it, but grab the error message first.
+ std::string error_message = PQerrorMessage(new_conn);
+ PQfinish(new_conn);
+ isc_throw(DbOpenError, error_message);
+ }
+
+ // We have a valid connection, so let's save it to our holder
+ conn_.setConnection(new_conn);
+}
+
+bool
+PgSqlConnection::compareError(const PgSqlResult& r, const char* error_state) {
+ const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
+ // PostgreSQL guarantees it will always be 5 characters long
+ return ((sqlstate != NULL) &&
+ (memcmp(sqlstate, error_state, PGSQL_STATECODE_LEN) == 0));
+}
+
+void
+PgSqlConnection::checkStatementError(const PgSqlResult& r,
+ PgSqlTaggedStatement& statement) {
+ int s = PQresultStatus(r);
+ if (s != PGRES_COMMAND_OK && s != PGRES_TUPLES_OK) {
+ // We're testing the first two chars of SQLSTATE, as this is the
+ // error class. Note, there is a severity field, but it can be
+ // misleadingly returned as fatal. However, a loss of connectivity
+ // can lead to a NULL sqlstate with a status of PGRES_FATAL_ERROR.
+ const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
+ if ((sqlstate == NULL) ||
+ ((memcmp(sqlstate, "08", 2) == 0) || // Connection Exception
+ (memcmp(sqlstate, "53", 2) == 0) || // Insufficient resources
+ (memcmp(sqlstate, "54", 2) == 0) || // Program Limit exceeded
+ (memcmp(sqlstate, "57", 2) == 0) || // Operator intervention
+ (memcmp(sqlstate, "58", 2) == 0))) { // System error
+ DB_LOG_ERROR(PGSQL_FATAL_ERROR)
+ .arg(statement.name)
+ .arg(PQerrorMessage(conn_))
+ .arg(sqlstate ? sqlstate : "<sqlstate null>");
+
+ // Mark this connection as no longer usable.
+ markUnusable();
+
+ // Start the connection recovery.
+ startRecoverDbConnection();
+
+ // We still need to throw so caller can error out of the current
+ // processing.
+ isc_throw(DbConnectionUnusable,
+ "fatal database error or connectivity lost");
+ }
+
+ // Failure: check for the special case of duplicate entry.
+ if (compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
+ isc_throw(DuplicateEntry, "statement: " << statement.name
+ << ", reason: " << PQerrorMessage(conn_));
+ }
+
+ // Failure: check for the special case of null key violation.
+ if (compareError(r, PgSqlConnection::NULL_KEY)) {
+ isc_throw(NullKeyError, "statement: " << statement.name
+ << ", reason: " << PQerrorMessage(conn_));
+ }
+
+ // Apparently it wasn't fatal, so we throw with a helpful message.
+ const char* error_message = PQerrorMessage(conn_);
+ isc_throw(DbOperationError, "Statement exec failed for: "
+ << statement.name << ", status: " << s
+ << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>")
+ << " ], reason: " << error_message);
+ }
+}
+
+void
+PgSqlConnection::startTransaction() {
+ // If it is nested transaction, do nothing.
+ if (++transaction_ref_count_ > 1) {
+ return;
+ }
+
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_START_TRANSACTION);
+ checkUnusable();
+ 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);
+ }
+}
+
+bool
+PgSqlConnection::isTransactionStarted() const {
+ return (transaction_ref_count_ > 0);
+}
+
+void
+PgSqlConnection::commit() {
+ if (transaction_ref_count_ <= 0) {
+ isc_throw(Unexpected, "commit called for not started transaction - coding error");
+ }
+
+ // When committing nested transaction, do nothing.
+ if (--transaction_ref_count_ > 0) {
+ return;
+ }
+
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_COMMIT);
+ checkUnusable();
+ PgSqlResult r(PQexec(conn_, "COMMIT"));
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ const char* error_message = PQerrorMessage(conn_);
+ isc_throw(DbOperationError, "commit failed: " << error_message);
+ }
+}
+
+void
+PgSqlConnection::rollback() {
+ if (transaction_ref_count_ <= 0) {
+ isc_throw(Unexpected, "rollback called for not started transaction - coding error");
+ }
+
+ // When rolling back nested transaction, do nothing.
+ if (--transaction_ref_count_ > 0) {
+ return;
+ }
+
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_ROLLBACK);
+ checkUnusable();
+ PgSqlResult r(PQexec(conn_, "ROLLBACK"));
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ const char* error_message = PQerrorMessage(conn_);
+ isc_throw(DbOperationError, "rollback failed: " << error_message);
+ }
+}
+
+void
+PgSqlConnection::createSavepoint(const std::string& name) {
+ if (transaction_ref_count_ <= 0) {
+ isc_throw(InvalidOperation, "no transaction, cannot create savepoint: " << name);
+ }
+
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_CREATE_SAVEPOINT).arg(name);
+ std::string sql("SAVEPOINT " + name);
+ executeSQL(sql);
+}
+
+void
+PgSqlConnection::rollbackToSavepoint(const std::string& name) {
+ if (transaction_ref_count_ <= 0) {
+ isc_throw(InvalidOperation, "no transaction, cannot rollback to savepoint: " << name);
+ }
+
+ std::string sql("ROLLBACK TO SAVEPOINT " + name);
+ executeSQL(sql);
+}
+
+void
+PgSqlConnection::executeSQL(const std::string& sql) {
+ // Use a TaggedStatement so we can call checkStatementError and ensure
+ // we detect connectivity issues properly.
+ PgSqlTaggedStatement statement({0, {OID_NONE}, "run-statement", sql.c_str()});
+ checkUnusable();
+ PgSqlResult r(PQexec(conn_, statement.text));
+ checkStatementError(r, statement);
+}
+
+PgSqlResultPtr
+PgSqlConnection::executePreparedStatement(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings) {
+ checkUnusable();
+
+ if (statement.nbparams != in_bindings.size()) {
+ isc_throw (InvalidOperation, "executePreparedStatement:"
+ << " expected: " << statement.nbparams
+ << " parameters, given: " << in_bindings.size()
+ << ", statement: " << statement.name
+ << ", SQL: " << statement.text);
+ }
+
+ const char* const* values = 0;
+ const int* lengths = 0;
+ const int* formats = 0;
+ if (statement.nbparams > 0) {
+ values = static_cast<const char* const*>(&in_bindings.values_[0]);
+ lengths = static_cast<const int *>(&in_bindings.lengths_[0]);
+ formats = static_cast<const int *>(&in_bindings.formats_[0]);
+ }
+
+ PgSqlResultPtr result_set;
+ result_set.reset(new PgSqlResult(PQexecPrepared(conn_, statement.name, statement.nbparams,
+ values, lengths, formats, 0)));
+
+ checkStatementError(*result_set, statement);
+ return (result_set);
+}
+
+void
+PgSqlConnection::selectQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings,
+ ConsumeResultRowFun process_result_row) {
+ // Execute the prepared statement.
+ PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
+
+ // Iterate over the returned rows and invoke the row consumption
+ // function on each one.
+ int rows = result_set->getRows();
+ for (int row = 0; row < rows; ++row) {
+ try {
+ process_result_row(*result_set, row);
+ } catch (const std::exception& ex) {
+ // Rethrow the exception with a bit more data.
+ isc_throw(BadValue, ex.what() << ". Statement is <" <<
+ statement.text << ">");
+ }
+ }
+}
+
+void
+PgSqlConnection::insertQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings) {
+ // Execute the prepared statement.
+ PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
+}
+
+uint64_t
+PgSqlConnection::updateDeleteQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings) {
+ // Execute the prepared statement.
+ PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
+
+ return (boost::lexical_cast<int>(PQcmdTuples(*result_set)));
+}
+
+template<typename T>
+void
+PgSqlConnection::setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value) {
+ string svalue;
+ try {
+ svalue = getParameter(name);
+ } catch (...) {
+ // Do nothing if the parameter is not present.
+ }
+ if (svalue.empty()) {
+ return;
+ }
+ try {
+ // Try to convert the value.
+ auto parsed_value = boost::lexical_cast<T>(svalue);
+ // Check if the value is within the specified range.
+ if ((parsed_value < min) || (parsed_value > max)) {
+ isc_throw(BadValue, "bad " << svalue << " value");
+ }
+ // Everything is fine. Return the parsed value.
+ value = parsed_value;
+
+ } catch (...) {
+ // We may end up here when lexical_cast fails or when the
+ // parsed value is not within the desired range. In both
+ // cases let's throw the same general error.
+ isc_throw(BadValue, name << " parameter (" <<
+ svalue << ") must be an integer between "
+ << min << " and " << max);
+ }
+}
+
+
+} // end of isc::db namespace
+} // end of isc namespace
diff --git a/src/lib/pgsql/pgsql_connection.h b/src/lib/pgsql/pgsql_connection.h
new file mode 100644
index 0000000..4e8c21a
--- /dev/null
+++ b/src/lib/pgsql/pgsql_connection.h
@@ -0,0 +1,597 @@
+// Copyright (C) 2016-2023 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_CONNECTION_H
+#define PGSQL_CONNECTION_H
+
+#include <asiolink/io_service.h>
+#include <pgsql/pgsql_exchange.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace db {
+
+/// @brief Define the PostgreSQL backend version.
+const uint32_t PGSQL_SCHEMA_VERSION_MAJOR = 18;
+const uint32_t PGSQL_SCHEMA_VERSION_MINOR = 0;
+
+// Maximum number of parameters that can be used a statement
+// @todo This allows us to use an initializer list (since we can't
+// require C++11). It's unlikely we'd go past this many a single
+// statement.
+const size_t PGSQL_MAX_PARAMETERS_IN_QUERY = 128;
+
+/// @brief Define a PostgreSQL statement.
+///
+/// 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;
+
+ /// @brief OID types
+ ///
+ /// Specify parameter types. See /usr/include/postgresql/catalog/pg_type.h.
+ /// For some reason that header does not export those parameters.
+ /// Those OIDs must match both input and output parameters.
+ const Oid types[PGSQL_MAX_PARAMETERS_IN_QUERY];
+
+ /// Short name of the query.
+ const char* name;
+
+ /// Text representation of the actual query.
+ const char* text;
+};
+
+/// @{
+/// @brief Constants for PostgreSQL data types
+/// These are defined by PostgreSQL in <catalog/pg_type.h>, but including
+/// 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_INT4 = 23; // 4 byte int
+const size_t OID_TEXT = 25;
+const size_t OID_VARCHAR = 1043;
+const size_t OID_TIMESTAMP = 1114;
+/// @}
+
+/// @brief Postgresql connection handle Holder
+///
+/// Small RAII object for safer initialization, will close the database
+/// connection upon destruction. This means that if an exception is thrown
+/// during database initialization, resources allocated to the database are
+/// guaranteed to be freed.
+///
+/// It makes no sense to copy an object of this class. After the copy, both
+/// objects would contain pointers to the same PgSql context object. The
+/// destruction of one would invalid the context in the remaining object.
+/// For this reason, the class is declared noncopyable.
+class PgSqlHolder : public boost::noncopyable {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Sets the Postgresql API connector handle to NULL.
+ ///
+ PgSqlHolder() : pgconn_(NULL) {
+ }
+
+ /// @brief Destructor
+ ///
+ /// Frees up resources allocated by the connection.
+ ~PgSqlHolder() {
+ if (pgconn_ != NULL) {
+ PQfinish(pgconn_);
+ }
+ }
+
+ /// @brief Sets the connection to the value given
+ ///
+ /// @param connection - pointer to the Postgresql connection instance
+ void setConnection(PGconn* connection) {
+ if (pgconn_ != NULL) {
+ // Already set? Release the current connection first.
+ // Maybe this should be an error instead?
+ PQfinish(pgconn_);
+ }
+
+ pgconn_ = connection;
+ }
+
+ /// @brief Conversion Operator
+ ///
+ /// Allows the PgSqlHolder object to be passed as the context argument to
+ /// PQxxxx functions.
+ operator PGconn*() const {
+ return (pgconn_);
+ }
+
+ /// @brief Boolean Operator
+ ///
+ /// Allows testing the connection for emptiness: "if (holder)"
+ operator bool() const {
+ return (pgconn_);
+ }
+
+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
+/// used by both PgSqlLeaseMgr and PgSqlHostDataSource. It manages connecting
+/// to the database and preparing compiled statements. Its fields are
+/// public, because they are used (both set and retrieved) in classes
+/// that use instances of PgSqlConnection.
+class PgSqlConnection : public db::DatabaseConnection {
+public:
+ /// @brief Define the PgSql error state for a duplicate key error.
+ static const char DUPLICATE_KEY[];
+ /// @brief Define the PgSql error state for a null foreign key error.
+ static const char NULL_KEY[];
+
+ /// @brief Function invoked to process fetched row.
+ typedef std::function<void(PgSqlResult&, int)> ConsumeResultRowFun;
+
+ /// @brief Emit the TLS support warning only once.
+ static bool warned_about_tls;
+
+ /// @brief Constructor
+ ///
+ /// Initialize PgSqlConnection object with parameters needed for connection.
+ ///
+ /// @param parameters Specify the connection details.
+ /// @param io_accessor The IOService accessor function.
+ /// @param callback The connection recovery callback.
+ PgSqlConnection(const ParameterMap& parameters,
+ IOServiceAccessorPtr io_accessor = IOServiceAccessorPtr(),
+ DbCallback callback = DbCallback())
+ : DatabaseConnection(parameters, callback),
+ io_service_accessor_(io_accessor), io_service_(),
+ transaction_ref_count_(0) {
+ }
+
+ /// @brief Destructor
+ virtual ~PgSqlConnection();
+
+ /// @brief Get the schema version.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @return Version number as a pair of unsigned integers. "first" is the
+ /// major version number, "second" the minor number.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ static std::pair<uint32_t, uint32_t>
+ getVersion(const ParameterMap& parameters);
+
+ /// @brief Prepare Single Statement
+ ///
+ /// Creates a prepared statement from the text given and adds it to the
+ /// statements_ vector at the given index.
+ ///
+ /// @param statement SQL statement to be prepared.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ void prepareStatement(const PgSqlTaggedStatement& statement);
+
+ /// @brief Prepare statements
+ ///
+ /// Creates the prepared statements for all of the SQL statements used
+ /// by the PostgreSQL backend.
+ ///
+ /// @param start_statement Pointer to the first statement in range of the
+ /// statements to be compiled.
+ /// @param end_statement Pointer to the statement marking end of the
+ /// range of statements to be compiled. This last statement is not compiled.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ void prepareStatements(const PgSqlTaggedStatement* start_statement,
+ const PgSqlTaggedStatement* end_statement);
+
+ /// @brief Creates connection string from specified parameters.
+ ///
+ /// This function is called from the unit tests.
+ ///
+ /// @return connection string for @c openDatabase.
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbInvalidTimeout when the database timeout is wrong.
+ std::string getConnParameters();
+
+private:
+
+ /// @brief Creates connection string from the specified parameters.
+ ///
+ /// This is an internal implementation of the @c getConnParameters that
+ /// allows for controlling logging. In some cases, a caller can disable
+ /// logging warnings to avoid duplication of the log messages emitted
+ /// when the invocation is a result of calling @c getVersion before
+ /// opening the connection for the normal server operation.
+ ///
+ /// @param logging boolean parameter controlling if logging should be
+ /// enabled (when true) or disabled (when false).
+ ///
+ /// @return connection string for @c openDatabase.
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbInvalidTimeout when the database timeout is wrong.
+ std::string getConnParametersInternal(bool logging);
+
+public:
+
+ /// @brief Open database with logging.
+ ///
+ /// Opens the database using the information supplied in the parameters
+ /// passed to the constructor. It logs warnings resulting from the
+ /// @c getConnParameters.
+ ///
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbOpenError Error opening the database
+ void openDatabase();
+
+private:
+
+ /// @brief Internal implementation of the database opening.
+ ///
+ /// It allows for controlling if the @c getConnParameterInternal function
+ /// should log the warnings.
+ ///
+ /// @param logging boolean parameter controlling if logging should be
+ /// enabled (when true) or disabled (when false).
+ ///
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbOpenError Error opening the database
+ void openDatabaseInternal(bool logging);
+
+public:
+
+ /// @brief Starts new transaction
+ ///
+ /// This function begins a new transaction by sending the START TRANSACTION
+ /// statement to the database. The transaction should be explicitly committed
+ /// by calling @c commit() or rolled back by calling @c rollback().
+ ///
+ /// PostgreSQL does not support nested transactions directly. Issuing a
+ /// START TRANSACTION while already in a transaction will cause a warning to
+ /// be emitted but otherwise does not alter the state of the current transaction.
+ /// In other words, the transaction will still end upon the next COMMIT or
+ /// ROLLBACK statement.
+ ///
+ /// Therefore, this function checks if a transaction has already started and
+ /// does not start a new transaction. However, it increments a transaction
+ /// reference counter which is later decremented when @c commit() or @c
+ /// rollback() is called. When this mechanism is used properly, it
+ /// guarantees that nested transactions are not attempted, thus avoiding
+ /// unexpected commits or rollbacks of the pending transaction.
+ void startTransaction();
+
+ /// @brief Checks if there is a transaction in progress.
+ ///
+ /// @return true if a transaction has been started, false otherwise.
+ bool isTransactionStarted() const;
+
+ /// @brief Commits current transaction
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// When this method is called for a nested transaction it decrements the
+ /// transaction reference counter incremented during the call to
+ /// @c startTransaction.
+ ///
+ /// @throw DbOperationError If the commit failed.
+ void commit();
+
+ /// @brief Rollbacks current transaction
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// When this method is called for a nested transaction it decrements the
+ /// transaction reference counter incremented during the call to
+ /// @c startTransaction.
+ ///
+ /// @throw DbOperationError If the rollback failed.
+ void rollback();
+
+ /// @brief Creates a savepoint within the current transaction
+ ///
+ /// Creates a named savepoint within the current transaction.
+ ///
+ /// @param name name of the savepoint to create.
+ ///
+ /// @throw InvalidOperation if called outside a transaction.
+ /// @throw DbOperationError If the savepoint cannot be created.
+ void createSavepoint(const std::string& name);
+
+ /// @brief Rollbacks to the given savepoint
+ ///
+ /// Rolls back all pending database operations made after the
+ /// named savepoint.
+ ///
+ /// @param name name of the savepoint to which to rollback.
+ ///
+ /// @throw InvalidOperation if called outside a transaction.
+ /// @throw DbOperationError if the rollback failed.
+ void rollbackToSavepoint(const std::string& name);
+
+ /// @brief Executes the an SQL statement.
+ ///
+ /// It executes the given SQL text after first checking the
+ /// connection for usability. After the statement is executed
+ /// @c checkStatementError() is invoked to ensure we detect
+ /// connectivity issues properly.
+ /// It is intended to be used to execute utility statements such
+ /// as commit, rollback et al, which have no parameters, return no
+ /// results, and are not pre-compiled.
+ ///
+ /// @param sql SQL statement to execute.
+ void executeSQL(const std::string& sql);
+
+ /// @brief Checks a result set's SQL state against an error state.
+ ///
+ /// @param r result set to check
+ /// @param error_state error state to compare against
+ ///
+ /// @return True if the result set's SQL state equals the error_state,
+ /// false otherwise.
+ bool compareError(const PgSqlResult& r, const char* error_state);
+
+ /// @brief Checks result of the r object
+ ///
+ /// This function is used to determine whether or not the SQL statement
+ /// execution succeeded, and in the event of failures, decide whether or
+ /// not the failures are recoverable.
+ ///
+ /// If the error is recoverable, the function will throw a DbOperationError.
+ /// If the error is deemed unrecoverable, such as a loss of connectivity
+ /// with the server, the function will call startRecoverDbConnection() which
+ /// will start the connection recovery.
+ ///
+ /// If the invocation returns true, this indicates the calling layer will
+ /// attempt recovery, and the function throws a DbOperationError to allow
+ /// the caller to error handle the failed db access attempt.
+ ///
+ /// @param r result of the last PostgreSQL operation
+ /// @param statement - tagged statement that was executed
+ ///
+ /// @throw isc::db::DbOperationError Detailed PostgreSQL failure
+ void checkStatementError(const PgSqlResult& r,
+ PgSqlTaggedStatement& statement);
+
+ /// @brief The recover connection
+ ///
+ /// This function starts the recover process of the connection.
+ ///
+ /// @note The recover function must be run on the IO Service thread.
+ void startRecoverDbConnection() {
+ if (callback_) {
+ if (!io_service_ && io_service_accessor_) {
+ io_service_ = (*io_service_accessor_)();
+ io_service_accessor_.reset();
+ }
+
+ if (io_service_) {
+ io_service_->post(std::bind(callback_, reconnectCtl()));
+ }
+ }
+ }
+
+ /// @brief Executes a prepared SQL statement.
+ ///
+ /// It executes the given prepared SQL statement, after checking
+ /// for usability and input parameter sanity. After the statement
+ /// is executed @c checkStatementError() is invoked to ensure we detect
+ /// connectivity issues properly. Upon successful execution, the
+ /// the result set is returned. It may be used for any form of
+ /// prepared SQL statement (e.g query, insert, update, delete...),
+ /// with or without input parameters.
+ ///
+ /// @param statement PgSqlTaggedStatement describing the prepared
+ /// statement to execute.
+ /// @param in_bindings array of input parameter bindings. If the SQL
+ /// statement requires no input arguments, this parameter should either
+ /// be omitted or an empty PsqlBindArray should be supplied.
+ /// @throw InvalidOperation if the number of parameters expected
+ /// by the statement does not match the size of the input bind array.
+ PgSqlResultPtr executePreparedStatement(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings
+ = PsqlBindArray());
+
+ /// @brief Executes SELECT query using prepared statement.
+ ///
+ /// The statement parameter refers to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// This method executes prepared statement using provided input bindings and
+ /// calls @c process_result_row function for each returned row. The
+ /// @c process_result_row function is implemented by the caller and should
+ /// gather and store each returned row in an external data structure prior.
+ ///
+ /// @param statement reference to the precompiled tagged statement to execute
+ /// @param in_bindings input bindings holding values to substitue placeholders
+ /// in the query.
+ /// @param process_result_row Pointer to the function to be invoked for each
+ /// retrieved row. This function consumes the retrieved data from the
+ /// result set.
+ void selectQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings,
+ ConsumeResultRowFun process_result_row);
+
+ /// @brief Executes INSERT prepared statement.
+ ///
+ /// The @c statement must refer to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// This method executes prepared statement using provided bindings to
+ /// insert data into the database.
+ ///
+ /// @param statement reference to the precompiled tagged statement to execute
+ /// @param in_bindings input bindings holding values to substitue placeholders
+ /// in the query.
+ void insertQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings);
+
+
+ /// @brief Executes UPDATE or DELETE prepared statement and returns
+ /// the number of affected rows.
+ ///
+ /// The @c statement must refer to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// @param statement reference to the precompiled tagged statement to execute
+ /// @param in_bindings Input bindings holding values to substitute placeholders
+ /// in the query.
+ ///
+ /// @return Number of affected rows.
+ uint64_t updateDeleteQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings);
+
+ /// @brief PgSql connection handle
+ ///
+ /// This field is public, because it is used heavily from PgSqlLeaseMgr
+ /// and from PgSqlHostDataSource.
+ PgSqlHolder conn_;
+
+ /// @brief Conversion Operator
+ ///
+ /// Allows the PgConnection object to be passed as the context argument to
+ /// PQxxxx functions.
+ operator PGconn*() const {
+ return (conn_);
+ }
+
+ /// @brief Boolean Operator
+ ///
+ /// Allows testing the PgConnection for initialized connection
+ operator bool() const {
+ return (conn_);
+ }
+
+private:
+
+ /// @brief Convenience function parsing and setting an integer parameter,
+ /// if it exists.
+ ///
+ /// If the parameter is not present, this function doesn't change the @c value.
+ /// Otherwise, it tries to convert the parameter to the type @c T. Finally,
+ /// it checks if the converted number is within the specified range.
+ ///
+ /// @param name Parameter name.
+ /// @param min Expected minimal value.
+ /// @param max Expected maximal value.
+ /// @param [out] value Reference to a value returning the parsed parameter.
+ /// @tparam T Parameter type.
+ /// @throw BadValue if the parameter is not a valid number or if it is out
+ /// of range.
+ template<typename T>
+ void setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value);
+
+public:
+
+ /// @brief Accessor function which returns the IOService that can be used to
+ /// recover the connection.
+ ///
+ /// This accessor is used to lazy retrieve the IOService when the connection
+ /// is lost. It is useful to retrieve it at a later time to support hook
+ /// libraries which create managers on load and set IOService later on by
+ /// using the dhcp4_srv_configured and dhcp6_srv_configured hooks.
+ IOServiceAccessorPtr io_service_accessor_;
+
+ /// @brief IOService object, used for all ASIO operations.
+ isc::asiolink::IOServicePtr io_service_;
+
+ /// @brief Reference counter for transactions.
+ ///
+ /// It precludes starting and committing nested transactions. PostgreSQL
+ /// logs but ignores START TRANSACTIONs (or BEGINs) issued from within an
+ /// ongoing transaction. We do not want to start new transactions when one
+ /// is already in progress.
+ int transaction_ref_count_;
+};
+
+/// @brief Defines a pointer to a PgSqlConnection
+typedef boost::shared_ptr<PgSqlConnection> PgSqlConnectionPtr;
+
+} // end of isc::db namespace
+} // end of isc namespace
+
+#endif // PGSQL_CONNECTION_H
diff --git a/src/lib/pgsql/pgsql_exchange.cc b/src/lib/pgsql/pgsql_exchange.cc
new file mode 100644
index 0000000..d203714
--- /dev/null
+++ b/src/lib/pgsql/pgsql_exchange.cc
@@ -0,0 +1,767 @@
+// Copyright (C) 2016-2022 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 <pgsql/pgsql_exchange.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <iomanip>
+#include <sstream>
+#include <vector>
+
+using namespace isc::util;
+using namespace isc::data;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace db {
+
+const int PsqlBindArray::TEXT_FMT = 0;
+const int PsqlBindArray::BINARY_FMT = 1;
+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);
+}
+
+void PsqlBindArray::add(const std::string& value) {
+ values_.push_back(value.c_str());
+ lengths_.push_back(value.size());
+ formats_.push_back(TEXT_FMT);
+}
+
+void PsqlBindArray::insert(const char* value, size_t index) {
+ if (index && index >= values_.size()) {
+ isc_throw(OutOfRange, "PsqlBindArray::insert - index: " << index
+ << ", is larger than the array size: " << values_.size());
+ }
+
+ values_.insert(values_.begin() + index, value);
+ lengths_.insert(lengths_.begin() + index, strlen(value));
+ formats_.insert(formats_.begin() + index, TEXT_FMT);
+}
+
+void PsqlBindArray::insert(const std::string& value, size_t index) {
+ if (index && index >= values_.size()) {
+ isc_throw(OutOfRange, "PsqlBindArray::insert - index: " << index
+ << ", is larger than the array size: " << values_.size());
+ }
+
+ bound_strs_.push_back(ConstStringPtr(new std::string(value)));
+
+ values_.insert(values_.begin() + index, bound_strs_.back()->c_str());
+ lengths_.insert(lengths_.begin() + index, value.size());
+ formats_.insert(formats_.begin() + index, TEXT_FMT);
+}
+
+void PsqlBindArray::popBack() {
+ if (values_.size() == 0) {
+ isc_throw(OutOfRange, "PsqlBindArray::pop_back - array empty");
+ }
+
+ values_.erase(values_.end() - 1);
+ lengths_.erase(lengths_.end() - 1);
+ formats_.erase(formats_.end() - 1);
+}
+
+void PsqlBindArray::add(const std::vector<uint8_t>& data) {
+ values_.push_back(reinterpret_cast<const char*>(&(data[0])));
+ lengths_.push_back(data.size());
+ formats_.push_back(BINARY_FMT);
+}
+
+void PsqlBindArray::addTempBinary(const std::vector<uint8_t>& data) {
+ bound_strs_.push_back(ConstStringPtr(new std::string(
+ reinterpret_cast<const char*>(data.data()), data.size())));
+
+ values_.push_back(reinterpret_cast<const char*>(bound_strs_.back()->data()));
+ lengths_.push_back(data.size());
+ 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::addTempBuffer(const uint8_t* data, const size_t len) {
+ if (!data) {
+ isc_throw(BadValue, "PsqlBindArray::addTempBuffer - uint8_t data cannot be NULL");
+ }
+
+ bound_strs_.push_back(ConstStringPtr(new std::string(
+ reinterpret_cast<const char*>(data), len)));
+
+ values_.push_back(bound_strs_.back()->data());
+ 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 lexical_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>
+ (addr.toUint32()));
+ } else {
+ addTempString(addr.toText());
+ }
+}
+
+void PsqlBindArray::addNull(const int format) {
+ values_.push_back(NULL);
+ lengths_.push_back(0);
+ formats_.push_back(format);
+}
+
+void
+PsqlBindArray::add(const Triplet<uint32_t>& triplet) {
+ if (triplet.unspecified()) {
+ addNull();
+ } else {
+ add<uint32_t>(triplet.get());
+ }
+}
+
+void
+PsqlBindArray::addMin(const Triplet<uint32_t>& triplet) {
+ if (triplet.unspecified() || (triplet.getMin() == triplet.get())) {
+ addNull();
+ } else {
+ add<uint32_t>(triplet.getMin());
+ }
+}
+
+void
+PsqlBindArray::addMax(const Triplet<uint32_t>& triplet) {
+ if (triplet.unspecified() || (triplet.getMax() == triplet.get())) {
+ addNull();
+ } else {
+ add<uint32_t>(triplet.getMax());
+ }
+}
+
+/// @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());
+}
+
+void
+PsqlBindArray::addOptional(const util::Optional<std::string>& value) {
+ if (value.unspecified()) {
+ addNull();
+ } else {
+ addTempString(value);
+ }
+}
+
+void
+PsqlBindArray::addInet4(const isc::asiolink::IOAddress& value) {
+ if (!value.isV4()) {
+ isc_throw(BadValue, "unable to add address to PsqlBindAray '"
+ << value.toText() << "' is not an IPv4 address");
+ }
+
+ // inet columns are inserted as string addresses.
+ addTempString(value.toText());
+}
+
+void
+PsqlBindArray::addOptionalInet4(const util::Optional<isc::asiolink::IOAddress>& value) {
+ // If the value is unspecified it doesn't matter what the value is.
+ if (value.unspecified()) {
+ addNull();
+ } else {
+ addInet4(value);
+ }
+}
+
+void
+PsqlBindArray::addInet6(const isc::asiolink::IOAddress& value) {
+ if (!value.isV6()) {
+ isc_throw(BadValue, "unable to add address to PsqlBindAray '"
+ << value.toText() << "' is not an IPv6 address");
+ }
+
+ // inet columns are inserted as string addresses.
+ addTempString(value.toText());
+}
+
+void
+PsqlBindArray::addOptionalInet6(const util::Optional<isc::asiolink::IOAddress>& value) {
+ // If the value is unspecified it doesn't matter what the value is.
+ if (value.unspecified()) {
+ addNull();
+ } else {
+ addInet6(value);
+ }
+}
+
+void
+PsqlBindArray::addTimestamp(const boost::posix_time::ptime& timestamp) {
+ // Convert the ptime to time_t, then use the existing conversion
+ // function to make db time.
+ //
+ // Sadly boost::posix_time::to_time_t() was not added until 1.58,
+ // so do it ourselves.
+ ptime epoch(boost::gregorian::date(1970, 1, 1));
+ if (timestamp < epoch) {
+ isc_throw(isc::BadValue, "Time value is before the epoch");
+ }
+ ptime max_db_time = boost::posix_time::from_time_t(DatabaseConnection::MAX_DB_TIME);
+ time_duration::sec_type since_epoch = (timestamp - epoch).total_seconds();
+ time_t input_time(since_epoch);
+ if (timestamp > max_db_time) {
+ isc_throw(isc::BadValue, "Time value is too large: " <<
+ (input_time < 0 ?
+ static_cast<int64_t>(static_cast<uint32_t>(input_time)) :
+ input_time));
+ }
+
+ // Converts to timestamp to local date/time string.
+ addTempString(PgSqlExchange::convertLocalToDatabaseTime(input_time));
+}
+
+void
+PsqlBindArray::addTimestamp() {
+ time_t now;
+ time(&now);
+ addTempString(PgSqlExchange::convertLocalToDatabaseTime(now));
+}
+
+void
+PsqlBindArray::add(const ElementPtr& value) {
+ if (!value) {
+ addNull();
+ return;
+ }
+
+ std::ostringstream ss;
+ value->toJSON(ss);
+ addTempString(ss.str());
+}
+
+void
+PsqlBindArray::add(const ConstElementPtr& value) {
+ if (!value) {
+ addNull();
+ return;
+ }
+
+ std::ostringstream ss;
+ value->toJSON(ss);
+ addTempString(ss.str());
+}
+
+std::string
+PsqlBindArray::toText() const {
+ std::ostringstream stream;
+
+ if (values_.size() == 0) {
+ return ("bindarray is empty");
+ }
+
+ for (int i = 0; i < values_.size(); ++i) {
+ stream << i << " : ";
+
+ if (lengths_[i] == 0) {
+ stream << "empty" << std::endl;
+ continue;
+ }
+
+ if (formats_[i] == TEXT_FMT) {
+ stream << "\"" << values_[i] << "\"" << std::endl;
+ } else {
+ const char *data = values_[i];
+ stream << "0x";
+ for (int x = 0; x < lengths_[i]; ++x) {
+ stream << std::setfill('0') << std::setw(2)
+ << std::setbase(16)
+ << static_cast<unsigned int>(data[x]);
+ }
+
+ stream << std::endl << std::setbase(10);
+ }
+ }
+
+ return (stream.str());
+}
+
+bool
+PsqlBindArray::amNull(size_t index) const {
+ if (values_.size() < index + 1) {
+ isc_throw(OutOfRange, "The index " << index << " is larger than the "
+ " array size " << values_.size());
+ }
+
+ // We assume lengths_.size() always equals values_.size(). If not, the
+ // at() operator will throw.
+ return ( (values_.at(index) == NULL) && (lengths_.at(index) == 0) );
+}
+
+std::string
+PgSqlExchange::convertToDatabaseTime(const time_t input_time) {
+ struct tm tinfo;
+ char buffer[20];
+
+ localtime_r(&input_time, &tinfo);
+
+ // PostgreSQL will assume the value is already in local time since we
+ // do not specify timezone in the string.
+ strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
+ return (std::string(buffer));
+}
+
+std::string
+PgSqlExchange::convertLocalToDatabaseTime(const time_t input_time) {
+ struct tm tinfo;
+ char buffer[20];
+
+ // We use gmtime_r to avoid adjustment as time_t is already local.
+ gmtime_r(&input_time, &tinfo);
+
+ // PostgreSQL will assume the value is already in local time since we
+ // do not specify timezone in the string.
+ strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
+ return (std::string(buffer));
+}
+
+
+std::string
+PgSqlExchange::convertToDatabaseTime(const time_t cltt,
+ const uint32_t valid_lifetime) {
+ // Calculate expiry time. Store it in the 64-bit value so as we can
+ // detect overflows.
+ int64_t expire_time_64 = static_cast<int64_t>(cltt)
+ + static_cast<int64_t>(valid_lifetime);
+
+ // It has been observed that the PostgreSQL doesn't deal well with the
+ // timestamp values beyond the DataSource::MAX_DB_TIME seconds since the
+ // beginning of the epoch (around year 2038). The value is often
+ // stored in the database but it is invalid when read back (overflow?).
+ // Hence, the maximum timestamp value is restricted here.
+ if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
+ isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
+ }
+
+ return (convertToDatabaseTime(static_cast<time_t>(expire_time_64)));
+}
+
+time_t
+PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val) {
+ // Convert string time value to time_t
+ time_t new_time;
+ try {
+ new_time = (boost::lexical_cast<time_t>(db_time_val));
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "Database time value is invalid: " << db_time_val);
+ }
+
+ return (new_time);
+}
+
+void
+PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val,
+ boost::posix_time::ptime& conv_time) {
+ time_t tmp_time = convertFromDatabaseTime(db_time_val);
+ conv_time = boost::posix_time::from_time_t(tmp_time);
+}
+
+const char*
+PgSqlExchange::getRawColumnValue(const PgSqlResult& r, const int row,
+ 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(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, 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;
+ } else if (*data == 't') {
+ value = true;
+ } else {
+ isc_throw(DbOperationError, "Invalid boolean data: " << data
+ << " for: " << getColumnLabel(r, col) << " row:" << row
+ << " : must be 't' or 'f'");
+ }
+}
+
+void
+PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
+ 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
+ // so we use uint16_t and implicitly convert.
+ value = boost::lexical_cast<uint16_t>(data);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError, "Invalid uint8_t data: " << data
+ << " for: " << getColumnLabel(r, col) << " row:" << row
+ << " : " << ex.what());
+ }
+}
+
+void
+PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, boost::posix_time::ptime& value) {
+ std::string db_time_val;
+ PgSqlExchange::getColumnValue(r, row, col, db_time_val );
+ PgSqlExchange::convertFromDatabaseTime(db_time_val, value);
+}
+
+void
+PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, ElementPtr& value) {
+ const char* data = getRawColumnValue(r, row, col);
+ try {
+ value = Element::fromJSON(data);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError, "Cannot convert data: " << data
+ << " for: " << getColumnLabel(r, col) << " row:" << row
+ << " : " << ex.what());
+ }
+}
+
+isc::asiolink::IOAddress
+PgSqlExchange::getInetValue4(const PgSqlResult& r, const int row,
+ const size_t col) {
+ const char* data = getRawColumnValue(r, row, col);
+ try {
+ asiolink::IOAddress addr(data);
+ if (!addr.isV4()) {
+ isc_throw(BadValue, "not a v4 address");
+ }
+
+ return (addr);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError, "Cannot convert data: " << data
+ << " for: " << getColumnLabel(r, col) << " row:" << row
+ << " : " << ex.what());
+ }
+}
+
+isc::asiolink::IOAddress
+PgSqlExchange::getInetValue6(const PgSqlResult& r, const int row,
+ const size_t col) {
+ const char* data = getRawColumnValue(r, row, col);
+ try {
+ asiolink::IOAddress addr(data);
+ if (!addr.isV6()) {
+ isc_throw(BadValue, "not a v6 address");
+ }
+
+ return (addr);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError, "Cannot convert data: " << data
+ << " 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());
+ }
+}
+
+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) {
+ // Returns converted bytes in a dynamically allocated buffer, and
+ // sets bytes_converted.
+ unsigned char* bytes = PQunescapeBytea((const unsigned char*)
+ (getRawColumnValue(r, row, col)),
+ &bytes_converted);
+
+ // Unlikely it couldn't allocate it but you never know.
+ if (!bytes) {
+ isc_throw (DbOperationError, "PQunescapeBytea failed for:"
+ << getColumnLabel(r, col) << " row:" << row);
+ }
+
+ // Make sure it's not larger than expected.
+ if (bytes_converted > buffer_size) {
+ // Free the allocated buffer first!
+ PQfreemem(bytes);
+ isc_throw (DbOperationError, "Converted data size: "
+ << bytes_converted << " is too large for: "
+ << getColumnLabel(r, col) << " row:" << row);
+ }
+
+ // Copy from the allocated buffer to caller's buffer then free
+ // the allocated buffer.
+ memcpy(buffer, bytes, bytes_converted);
+ PQfreemem(bytes);
+}
+
+void
+PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row, const size_t col,
+ std::vector<uint8_t>& value) {
+ // Returns converted bytes in a dynamically allocated buffer, and
+ // sets bytes_converted.
+ size_t bytes_converted = 0;
+ unsigned char* bytes = PQunescapeBytea((const unsigned char*)
+ (getRawColumnValue(r, row, col)),
+ &bytes_converted);
+
+ // Unlikely it couldn't allocate it but you never know.
+ if (!bytes) {
+ isc_throw (DbOperationError, "PQunescapeBytea failed for:"
+ << getColumnLabel(r, col) << " row:" << row);
+ }
+
+ // Copy from the allocated buffer to caller's buffer then free
+ // the allocated buffer.
+ if (bytes_converted) {
+ value.assign(bytes, bytes + bytes_converted);
+ } else {
+ value.clear();
+ }
+
+ // Free the PostgreSQL buffer.
+ PQfreemem(bytes);
+}
+
+Triplet<uint32_t>
+PgSqlExchange::getTripletValue(const PgSqlResult& r, const int row,
+ const size_t col) {
+ uint32_t col_value;
+ if (isColumnNull(r, row, col)) {
+ return (Triplet<uint32_t>());
+ }
+
+ getColumnValue(r, row, col, col_value);
+ return (Triplet<uint32_t>(col_value));
+}
+
+Triplet<uint32_t>
+PgSqlExchange::getTripletValue(const PgSqlResult& r, const int row,
+ const size_t def_col, const size_t min_col,
+ const size_t max_col) {
+ if (isColumnNull(r, row, def_col)) {
+ return (Triplet<uint32_t>());
+ }
+
+ uint32_t value;
+ getColumnValue(r, row, def_col, value);
+
+ uint32_t min_value = value;
+ if (!isColumnNull(r, row, min_col)) {
+ getColumnValue(r, row, min_col, min_value);
+ }
+
+ uint32_t max_value = value;
+ if (!isColumnNull(r, row, max_col)) {
+ getColumnValue(r, row, max_col, max_value);
+ }
+
+ return (Triplet<uint32_t>(min_value, value, max_value));
+}
+
+std::string
+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 (stream.str());
+}
+
+PgSqlResultRowWorker::PgSqlResultRowWorker(const PgSqlResult& r, const int row)
+ : r_(r), row_(row) {
+ // Validate the desired row.
+ r.rowCheck(row);
+}
+
+bool
+PgSqlResultRowWorker::isColumnNull(const size_t col) {
+ return (PgSqlExchange::isColumnNull(r_, row_, col));
+}
+
+std::string
+PgSqlResultRowWorker::getString(const size_t col) {
+ std::string tmp;
+ PgSqlExchange::getColumnValue(r_, row_, col, tmp);
+ return (tmp);
+}
+
+bool
+PgSqlResultRowWorker::getBool(const size_t col) {
+ bool tmp;
+ PgSqlExchange::getColumnValue(r_, row_, col, tmp);
+ return (tmp);
+}
+
+double
+PgSqlResultRowWorker::getDouble(const size_t col) {
+ double tmp;
+ PgSqlExchange::getColumnValue(r_, row_, col, tmp);
+ return (tmp);
+}
+
+const char*
+PgSqlResultRowWorker::getRawColumnValue(const size_t col) {
+ return (PgSqlExchange::getRawColumnValue(r_, row_, col));
+}
+
+uint64_t
+PgSqlResultRowWorker::getBigInt(const size_t col) {
+ uint64_t value;
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ return (value);
+}
+
+uint32_t
+PgSqlResultRowWorker::getInt(const size_t col) {
+ uint32_t value;
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ return (value);
+}
+
+uint16_t
+PgSqlResultRowWorker::getSmallInt(const size_t col) {
+ uint16_t value;
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ return (value);
+}
+
+void
+PgSqlResultRowWorker::getBytes(const size_t col, std::vector<uint8_t>& value) {
+ PgSqlExchange::convertFromBytea(r_, row_, col, value);
+}
+
+isc::asiolink::IOAddress
+PgSqlResultRowWorker::getInet4(const size_t col) {
+ return (PgSqlExchange::getInetValue4(r_, row_, col));
+}
+
+isc::asiolink::IOAddress
+PgSqlResultRowWorker::getInet6(const size_t col) {
+ return (PgSqlExchange::getInetValue6(r_, row_, col));
+}
+
+boost::posix_time::ptime
+PgSqlResultRowWorker::getTimestamp(const size_t col) {
+ boost::posix_time::ptime value;
+ getColumnValue(col, value);
+ return (value);
+};
+
+data::ElementPtr
+PgSqlResultRowWorker::getJSON(const size_t col) {
+ data::ElementPtr value;
+ getColumnValue(col, value);
+ return (value);
+}
+
+isc::util::Triplet<uint32_t>
+PgSqlResultRowWorker::getTriplet(const size_t col) {
+ return (PgSqlExchange::getTripletValue(r_, row_, col));
+}
+
+isc::util::Triplet<uint32_t>
+PgSqlResultRowWorker::getTriplet(const size_t def_col, const size_t min_col,
+ const size_t max_col) {
+ return (PgSqlExchange::getTripletValue(r_, row_, def_col, min_col, max_col));
+}
+
+std::string
+PgSqlResultRowWorker::dumpRow() {
+ return (PgSqlExchange::dumpRow(r_, row_));
+}
+
+} // end of isc::db namespace
+} // end of isc namespace
diff --git a/src/lib/pgsql/pgsql_exchange.h b/src/lib/pgsql/pgsql_exchange.h
new file mode 100644
index 0000000..22ec7af
--- /dev/null
+++ b/src/lib/pgsql/pgsql_exchange.h
@@ -0,0 +1,1004 @@
+// Copyright (C) 2016-2022 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_EXCHANGE_H
+#define PGSQL_EXCHANGE_H
+
+#include <asiolink/io_address.h>
+#include <database/database_connection.h>
+#include <cc/data.h>
+#include <util/triplet.h>
+#include <util/boost_time_utils.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <libpq-fe.h>
+
+#include <stdint.h>
+#include <vector>
+#include <iostream>
+
+namespace isc {
+namespace db {
+
+/// @brief RAII wrapper for PostgreSQL Result sets
+///
+/// When a Postgresql statement is executed, the results are returned
+/// in a pointer allocated structure, PGresult*. Data and status information
+/// are accessed via calls to functions such as PQgetvalue() which require
+/// the results pointer. In order to ensure this structure is freed, any
+/// invocation of Psql function which returns a PGresult* (e.g. PQexec,
+/// PQExecPrepared, ...) must save the result to an instance of this
+/// class. Example:
+/// {{{
+/// PgSqlResult r(PQexec(conn_, "ROLLBACK"));
+/// }}}
+///
+/// This eliminates the need for an explicit release via, PQclear() and
+/// guarantees that the resources are released even if the an exception is
+/// thrown.
+
+class PgSqlResult : public boost::noncopyable {
+public:
+ /// @brief Constructor
+ ///
+ /// Store the pointer to the fetched result set. Set row and column
+ /// counts for convenience.
+ ///
+ /// @param result pointer to the Postgresql client layer result
+ /// If the value of is NULL, row and col values will be set to -1.
+ /// This allows PgSqlResult to be passed into statement error
+ /// checking.
+ PgSqlResult(PGresult *result);
+
+ /// @brief Destructor
+ ///
+ /// Frees the result set
+ ~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
+ /// PQxxxx functions.
+ operator PGresult*() const {
+ return (result_);
+ }
+
+ /// @brief Boolean Operator
+ ///
+ /// Allows testing the PgSqlResult object for emptiness: "if (result)"
+ operator bool() const {
+ return (result_);
+ }
+
+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
+};
+
+typedef boost::shared_ptr<PgSqlResult> PgSqlResultPtr;
+
+/// @brief Structure used to bind C++ input values to dynamic SQL parameters
+/// The structure contains three vectors which store the input values,
+/// data lengths, and formats. These vectors are passed directly into the
+/// PostgreSQL execute call.
+///
+/// Note that the data values are stored as pointers. These pointers need to
+/// 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 Constructor.
+ PsqlBindArray() : values_(0), lengths_(0), formats_(0) {}
+
+ /// @brief Vector of pointers to the data values.
+ std::vector<const char*> values_;
+
+ /// @brief Vector of data lengths for each value.
+ std::vector<int> lengths_;
+
+ /// @brief Vector of "format" for each value. A value of 0 means the
+ /// value is text, 1 means the value is binary.
+ std::vector<int> formats_;
+
+ /// @brief Format value for text data.
+ static const int TEXT_FMT;
+
+ /// @brief Format value for binary data.
+ static const int BINARY_FMT;
+
+ /// @brief Constant string passed to DB for boolean true values.
+ static const char* TRUE_STR;
+
+ /// @brief Constant string passed to DB for boolean false values.
+ static const char* FALSE_STR;
+
+ /// @brief Fetches the number of entries in the array.
+ /// @return Returns size_t containing the number of entries.
+ size_t size() const {
+ return (values_.size());
+ }
+
+ /// @brief Indicates it the array is empty.
+ /// @return Returns true if there are no entries in the array, false
+ /// otherwise.
+ bool empty() const {
+ return (values_.empty());
+ }
+
+ /// @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. 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 a 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. 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 Inserts a string value to the bind array before the given index
+ ///
+ /// Inserts a TEXT_FMT value into the bind array before the element
+ /// position given by index, using the given 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.
+ /// @param index element position before which the string should be inserted.
+ ///
+ /// @throw DbOperationError if value is NULL.
+ /// @throw OutOfRange if the index is beyond the end of the array.
+ void insert(const char* value, size_t index);
+
+ /// @brief Inserts a string value to the bind array before the given index
+ ///
+ /// Inserts a TEXT_FMT value into the bind array before the element
+ /// position given by index, using the given given string as the data
+ /// source. This creates an internally scoped copy of the string.
+ ///
+ /// @param value char array containing the null-terminated text to add.
+ /// @param index element position before which the string should be
+ /// inserted.
+ ///
+ /// @throw DbOperationError if value is NULL.
+ /// @throw OutOfRange if the index is beyond the end of the array.
+ void insert(const std::string& value, size_t index);
+
+ /// @brief Removes the last entry in the bind array.
+ ///
+ /// @throw OutOfRange if array is empty.
+ void popBack();
+
+ /// @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. 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 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. This creates an internally scoped
+ /// copy of the vector.
+ ///
+ /// @param data vector of binary bytes.
+ void addTempBinary(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 temporary 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.
+ /// Prior to adding the buffer, it is duplicated as a ConstStringPtr
+ /// and saved internally. This guarantees the buffer remains in scope
+ /// until the PsqlBindArray is destroyed, without the caller maintaining
+ /// the buffer values.
+ ///
+ /// @param data buffer of binary data.
+ /// @param len number of bytes of data in buffer
+ /// @throw DbOperationError if data is NULL.
+ void addTempBuffer(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 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 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 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 the given string to the bind array.
+ ///
+ /// Prior to add 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 the value for a parameter specified
+ /// in the SQL statement should be NULL.
+ void addNull(const int format = PsqlBindArray::TEXT_FMT);
+
+ /// @brief Adds an integer Triplet's value to the bind array
+ ///
+ /// Stores the current value of a triplet to the bind array.
+ /// If it is unspecified it stores a NULL.
+ ///
+ /// @param triplet Triplet instance from which to get the value.
+ void add(const isc::util::Triplet<uint32_t>& triplet);
+
+ /// @brief Adds an integer Triplet's minimum value to the bind array
+ ///
+ /// Stores the minimum value of a triplet to the bind array.
+ /// If it is unspecified it stores a NULL.
+ ///
+ /// @param triplet Triplet instance from which to get the value.
+ void addMin(const isc::util::Triplet<uint32_t>& triplet);
+
+ /// @brief Adds an integer Triplet's maximum value to the bind array
+ ///
+ /// Stores the maximum value of a triplet to the bind array.
+ /// If it is unspecified it stores a NULL.
+ ///
+ /// @param triplet Triplet instance from which to get the value.
+ void addMax(const isc::util::Triplet<uint32_t>& triplet);
+
+ /// @brief Adds an @c Optional string to the bind array.
+ ///
+ /// Optional strings require adding a temp string to the
+ /// bind array, unlike other types which implicitly do so.
+ ///
+ /// @param value Optional string value to add
+ void addOptional(const util::Optional<std::string>& value);
+
+ /// @brief Adds an @c Optional<type> value to the bind array.
+ ///
+ /// @tparam T variable type corresponding to the binding type, e.g.
+ /// @c string, bool, uint8_t, @c uint16_t etc.
+ /// @param value Optional of type T.
+ template<typename T>
+ void addOptional(const util::Optional<T>& value) {
+ if (value.unspecified()) {
+ addNull();
+ } else {
+ add(value);
+ }
+ }
+
+ /// @brief Adds an IPv4 address to the bind array.
+ ///
+ /// This is used for inet type columns.
+ ///
+ /// @param value Optional boolean value to add
+ /// @throw BadValue if the address is not a IPv4 address.
+ void addInet4(const isc::asiolink::IOAddress& value);
+
+ /// @brief Adds an IPv6 address to the bind array.
+ ///
+ /// This is used for inet type columns.
+ ///
+ /// @param value Optional boolean value to add
+ /// @throw BadValue if the address is not a IPv6 address.
+ void addInet6(const isc::asiolink::IOAddress& value);
+
+ /// @brief Adds an @c Optional IPv4 address to the bind array.
+ ///
+ /// This is used for inet type columns.
+ ///
+ /// @param value Optional boolean value to add
+ /// @throw BadValue if the address is not a IPv4 address.
+ void addOptionalInet4(const util::Optional<isc::asiolink::IOAddress>& value);
+
+ /// @brief Adds an @c Optional IPv6 address to the bind array.
+ ///
+ /// This is used for inet type columns which expect
+ /// v4 addresses to be inserted in string form:
+ /// '3001::1'
+ ///
+ /// @param value Optional boolean value to add
+ /// @throw BadValue if the address is not a IPv6 address.
+ void addOptionalInet6(const util::Optional<isc::asiolink::IOAddress>& value);
+
+
+ /// @brief Adds a timestamp from a ptime to the bind array.
+ ///
+ /// Precision is seconds.
+ ///
+ /// @param timestamp Timestamp value to be sent to the database.
+ /// @throw BadValue if the timestamp exceeds DatabaseConnection::MAX_DB_TIME.
+ void addTimestamp(const boost::posix_time::ptime& timestamp);
+
+ /// @brief Adds a timestamp of the current time to the bind array.
+ ///
+ /// Precision is seconds.
+ void addTimestamp();
+
+ /// @brief Adds an ElementPtr to the bind array
+ ///
+ /// Adds a TEXT_FMT value to the end of the bind array containing
+ /// the JSON text output by given ElementPtr::toJSON().
+ ///
+ /// @param value ElementPtr containing Element tree to add.
+ /// @throw DbOperationError if value is NULL.
+ void add(const data::ElementPtr& value);
+
+ /// @brief Adds a ConstElementPtr to the bind array
+ ///
+ /// Adds a TEXT_FMT value to the end of the bind array containing
+ /// the JSON text output by given ElementPtr::toJSON().
+ ///
+ /// @param value ElementPtr containing Element tree to add.
+ /// @throw DbOperationError if value is NULL.
+ void add(const data::ConstElementPtr& value);
+
+ /// @brief Dumps the contents of the array to a string.
+ /// @return std::string containing the dump
+ std::string toText() const;
+
+ // --- the following methods are mostly useful for testing -----
+
+ /// @brief Determines if specified value is null
+ /// @param index if array holds more than one value, this index determines
+ /// which column to use
+ /// @return true if the column is defined and is null
+ bool amNull(size_t index = 0) const;
+
+ /// @brief Returns the value as an integer.
+ /// @param index if array holds more than one value, this index determines
+ /// which column to use
+ /// @return value interpreted as specified integer type
+ /// @throw OutOfRange if the offset is too large
+ /// @throw BadValue if the data is null
+ /// @throw boost::bad_lexical_cast if value is not an integer
+ template<typename T>
+ T getInteger(size_t index = 0) {
+ if (values_.size() < index + 1) {
+ isc_throw(OutOfRange, "Invalid index " << index << ", the values array has "
+ << values_.size() << " element(s)");
+ }
+ auto x = values_.at(index);
+ if (!x) {
+ isc_throw(BadValue, "the data in column " << index << " is null");
+ }
+ return (boost::lexical_cast<T>(x));
+ }
+
+ /// @brief Returns the column type
+ /// @param index if array holds more than one value, this index determines
+ /// which column to use
+ /// @return the type of specified column
+ /// @throw BadValue if the offset is too large
+ int getType(size_t index = 0 ) {
+ if (formats_.size() < index + 1) {
+ isc_throw(OutOfRange, "Invalid index " << index << ", the formats_ array has "
+ << formats_.size() << " element(s)");
+ }
+ return (formats_.at(index));
+ }
+
+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
+/// application objects in the program and their representation in the
+/// database, and for retrieving column values from rows of a result set.
+class PgSqlExchange {
+public:
+ /// @brief Constructor
+ PgSqlExchange(const size_t num_columns = 0) : columns_(num_columns) {}
+
+ /// @brief Destructor
+ virtual ~PgSqlExchange() {}
+
+ /// @brief Converts UTC time_t value to a text representation in local time.
+ ///
+ /// @param input_time A time_t value representing time.
+ /// @return std::string containing stringified time.
+ static std::string convertToDatabaseTime(const time_t input_time);
+
+ /// @brief Converts local time_t value to a text representation in local time.
+ ///
+ /// @param input_time A time_t value representing time.
+ /// @return std::string containing stringified time.
+ static std::string convertLocalToDatabaseTime(const time_t input_time);
+
+ /// @brief Converts lease expiration time to a text representation in
+ /// local time.
+ ///
+ /// The expiration time is calculated as a sum of the cltt (client last
+ /// transmit time) and the valid lifetime.
+ ///
+ /// The format of the output string is "%Y-%m-%d %H:%M:%S". Database
+ /// table columns using this value should be typed as TIMESTAMP WITH
+ /// TIME ZONE. For such columns PostgreSQL assumes input strings without
+ /// timezones should be treated as in local time and are converted to UTC
+ /// when stored. Likewise, these columns are automatically adjusted
+ /// upon retrieval unless fetched via "extract(epoch from <column>))".
+ ///
+ /// Unless we start using binary input, timestamp columns must be input as
+ /// date/time strings.
+ ///
+ /// @param cltt Client last transmit time
+ /// @param valid_lifetime Valid lifetime
+ ///
+ /// @return std::string containing the stringified time
+ /// @throw isc::BadValue if the sum of the calculated expiration time is
+ /// greater than the value of @c DataSource::MAX_DB_TIME.
+ static std::string convertToDatabaseTime(const time_t cltt,
+ const uint32_t valid_lifetime);
+
+ /// @brief Converts time stamp from the database to a time_t
+ ///
+ /// We're fetching timestamps as an integer string of seconds since the
+ /// epoch. This method converts such a string to a time_t.
+ ///
+ /// @param db_time_val timestamp to be converted. This value
+ /// is expected to be the number of seconds since the epoch
+ /// expressed as base-10 integer string.
+ /// @return Converted timestamp as time_t value.
+ static time_t convertFromDatabaseTime(const std::string& db_time_val);
+
+ /// @brief Converts time stamp from the database to a boost::posix::ptime
+ ///
+ /// We're fetching timestamps as an integer string of seconds since the
+ /// epoch. This method converts such a string to a boost::posix_time::ptime.
+ ///
+ /// @param db_time_val timestamp to be converted. This value
+ /// is expected to be the number of seconds since the epoch
+ /// expressed as base-10 integer string.
+ /// @param[out] conv_time resulting time as a ptime (UTC)
+ static void convertFromDatabaseTime(const std::string& db_time_val,
+ boost::posix_time::ptime& conv_time);
+
+ /// @brief Gets a pointer to the raw column value in a result set row
+ ///
+ /// Given a result set, row, and column return a const char* pointer to
+ /// the data value in the result set. The pointer is valid as long as
+ /// the result set has not been freed. It may point to text or binary
+ /// data depending on how query was structured. You should not attempt
+ /// to free this pointer.
+ ///
+ /// @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 a const char* pointer to the column's raw data
+ /// @throw DbOperationError if the value cannot be fetched.
+ 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.
+ ///
+ /// @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.
+ 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.
+ ///
+ /// @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.
+ 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 IPv4 address.
+ ///
+ /// This is used to fetch values from inet type columns.
+ ///
+ /// @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 IPv4 address.
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static isc::asiolink::IOAddress getInetValue4(const PgSqlResult& r,
+ const int row,
+ const size_t col);
+
+ /// @brief Converts a column in a row in a result set into IPv6 address.
+ ///
+ /// This is used to fetch values from inet type columns.
+ ///
+ /// @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 getInetValue6(const PgSqlResult& r,
+ const int row,
+ const size_t col);
+
+ /// @brief Converts a column in a row in a result set into IPv6 address.
+ ///
+ /// This used for IPv6 columns stored as varchar.
+ ///
+ /// @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(db::DbOperationError, "Invalid data:[" << data
+ << "] for row: " << row << " col: " << col << ","
+ << getColumnLabel(r, col) << " : " << ex.what());
+ }
+ }
+
+ /// @brief Fetches a timestamp column as a ptime.
+ ///
+ /// @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 ptime parameter to receive the converted timestamp
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, boost::posix_time::ptime& value);
+
+ /// @brief Fetches a JSON column as an ElementPtr.
+ ///
+ /// @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 ElementPtr to receive the column data
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, data::ElementPtr& value);
+
+ /// @brief Converts a column in a row in a result set to a binary bytes
+ ///
+ /// Method is used to convert columns stored as BYTEA into a buffer of
+ /// binary bytes, (uint8_t). It uses PQunescapeBytea to do the conversion.
+ ///
+ /// @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] buffer pre-allocated buffer to which the converted bytes
+ /// will be stored.
+ /// @param buffer_size size of the output buffer
+ /// @param[out] bytes_converted number of bytes converted
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ 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 Converts a column in a row in a result set to a binary bytes
+ ///
+ /// Method is used to convert columns stored as BYTEA into a vector of
+ /// binary bytes, (uint8_t). It uses PQunescapeBytea to do the conversion.
+ ///
+ /// @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 vector to receive the converted bytes
+ /// value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void convertFromBytea(const PgSqlResult& r, const int row,
+ const size_t col, std::vector<uint8_t>& value);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a single
+ /// column value
+ ///
+ /// @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. If the column
+ /// is null, the Triplet is returned as unspecified.
+ /// @return Triplet to receive the column value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static isc::util::Triplet<uint32_t> getTripletValue(const PgSqlResult& r,
+ const int row,
+ const size_t col);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a three
+ /// column values: default, minimum, and maximum
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param def_col the column number within the row that contains the
+ /// default value. If this column is null, the Triplet is returned
+ /// as unspecified.
+ /// @param min_col the column number within the row that contains the
+ /// minimum value.
+ /// @param max_col the column number within the row that contains the
+ /// maximum value.
+ /// @return Triplet to receive the column value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static isc::util::Triplet<uint32_t> getTripletValue(const PgSqlResult& r,
+ const int row,
+ const size_t def_col,
+ const size_t min_col,
+ const size_t max_col);
+
+ /// @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> columns_;
+};
+
+/// @brief Convenience class which facilitates fetching column values
+/// from a result set row.
+class PgSqlResultRowWorker {
+public:
+ /// @brief Constructor
+ ///
+ /// @param r result set containing the fetched rows of data.
+ /// @param row zero-based index of the desired row, (e.g.
+ /// 0 .. n - 1 where n = number of rows in r)
+ /// @throw DbOperationError if row value is invalid.
+ PgSqlResultRowWorker(const PgSqlResult& r, const int row);
+
+ /// @brief Indicates whether or not the given column value is null.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return true if the value is null, false otherwise.
+ bool isColumnNull(const size_t col);
+
+ /// @brief Fetches the column value as a string.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return std::string containing the column value.
+ std::string getString(const size_t col);
+
+ /// @brief Fetches the boolean value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return bool containing the column value.
+ bool getBool(const size_t col);
+
+ /// @brief Fetches the floating point value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return double containing the column value.
+ double getDouble(const size_t col);
+
+ /// @brief Gets a pointer to the raw column value in a result set row
+ ///
+ /// Given a column return a const char* pointer to the data value in
+ /// the result set row. The pointer is valid as long as the underlying
+ /// result set has not been freed. It may point to text or binary
+ /// data depending on how query was structured. You should not attempt
+ /// to free this pointer.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return a const char* pointer to the column's raw data
+ const char* getRawColumnValue(const size_t col);
+
+ /// @brief Fetches the uint64_t value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return uint64_t containing the column value
+ uint64_t getBigInt(const size_t col);
+
+ /// @brief Fetches the uint32_t value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return uint32_t containing the column value
+ uint32_t getInt(const size_t col);
+
+ /// @brief Fetches the uint16_t value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return uint16_t containing the column value
+ uint16_t getSmallInt(const size_t col);
+
+ /// @brief Fetches binary data at the given column into a vector.
+ ///
+ /// @param col the column number within the row
+ /// @param[out] value vector to receive the fetched data.
+ void getBytes(const size_t col, std::vector<uint8_t>& value);
+
+ /// @brief Fetches the v4 IP address at the given column.
+ ///
+ /// This is used to fetch values from inet type columns.
+ /// @param col the column number within the row
+ ///
+ /// @return isc::asiolink::IOAddress containing the IPv4 address.
+ isc::asiolink::IOAddress getInet4(const size_t col);
+
+ /// @brief Fetches the v6 IP address at the given column.
+ ///
+ /// This is used to fetch values from inet type columns.
+ /// @param col the column number within the row
+ ///
+ /// @return isc::asiolink::IOAddress containing the IPv6 address.
+ isc::asiolink::IOAddress getInet6(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 col the column number within the row
+ /// @param[out] value parameter to receive the converted value
+ template<typename T>
+ void getColumnValue(const size_t col, T& value) {
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ }
+
+ /// @brief Fetches a timestamp column as a ptime.
+ ///
+ /// @param col the column number within the row
+ /// @return ptime parameter to receive the converted timestamp
+ boost::posix_time::ptime getTimestamp(const size_t col);
+
+ /// @brief Fetches a JSON column as an ElementPtr.
+ ///
+ /// @param col the column number within the row
+ /// @return ElementPtr parameter to receive the column value
+ data::ElementPtr getJSON(const size_t col);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a single
+ /// column value
+ ///
+ /// @param col the column number within the row If the column
+ /// is null, the Triplet is returned as unspecified.
+ /// @return Triplet to receive the column value
+ isc::util::Triplet<uint32_t> getTriplet(const size_t col);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a three
+ /// column values: default, minimum, and maximum
+ ///
+ /// @param def_col the column number within the row that contains the
+ /// default value. If this column is null, the Triplet is returned
+ /// as unspecified.
+ /// @param min_col the column number within the row that contains the
+ /// minimum value.
+ /// @param max_col the column number within the row that contains the
+ /// maximum value.
+ /// @return Triplet to receive the column value
+ isc::util::Triplet<uint32_t> getTriplet(const size_t def_col,
+ const size_t min_col,
+ const size_t max_col);
+
+ /// @brief Diagnostic tool which dumps the Result row contents as a string
+ ///
+ /// @return A string representation of the row contents.
+ std::string dumpRow();
+
+private:
+ /// @brief Result set containing the row.
+ const PgSqlResult& r_;
+
+ /// @brief Index of the desired row.
+ size_t row_;
+};
+
+/// @brief Pointer to a result row worker.
+typedef boost::shared_ptr<PgSqlResultRowWorker> PgSqlResultRowWorkerPtr;
+
+} // end of isc::db namespace
+} // end of isc namespace
+
+#endif // PGSQL_EXCHANGE_H
diff --git a/src/lib/pgsql/tests/Makefile.am b/src/lib/pgsql/tests/Makefile.am
new file mode 100644
index 0000000..425eea0
--- /dev/null
+++ b/src/lib/pgsql/tests/Makefile.am
@@ -0,0 +1,40 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(PGSQL_CPPFLAGS)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += libpgsql_unittests
+
+libpgsql_unittests_SOURCES = pgsql_basics.cc pgsql_basics.h
+libpgsql_unittests_SOURCES += pgsql_connection_unittest.cc
+libpgsql_unittests_SOURCES += pgsql_exchange_unittest.cc
+libpgsql_unittests_SOURCES += run_unittests.cc
+
+libpgsql_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libpgsql_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(PGSQL_LIBS)
+
+libpgsql_unittests_LDADD = $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libpgsql_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/pgsql/tests/Makefile.in b/src/lib/pgsql/tests/Makefile.in
new file mode 100644
index 0000000..71f383d
--- /dev/null
+++ b/src/lib/pgsql/tests/Makefile.in
@@ -0,0 +1,1066 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+TESTS = $(am__EXEEXT_1)
+@HAVE_GTEST_TRUE@am__append_1 = libpgsql_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/pgsql/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = libpgsql_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libpgsql_unittests_SOURCES_DIST = pgsql_basics.cc pgsql_basics.h \
+ pgsql_connection_unittest.cc pgsql_exchange_unittest.cc \
+ run_unittests.cc
+@HAVE_GTEST_TRUE@am_libpgsql_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ libpgsql_unittests-pgsql_basics.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libpgsql_unittests-pgsql_connection_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libpgsql_unittests-pgsql_exchange_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libpgsql_unittests-run_unittests.$(OBJEXT)
+libpgsql_unittests_OBJECTS = $(am_libpgsql_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libpgsql_unittests_DEPENDENCIES = $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/pgsql/libkea-pgsql.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libpgsql_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libpgsql_unittests_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/libpgsql_unittests-pgsql_basics.Po \
+ ./$(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po \
+ ./$(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po \
+ ./$(DEPDIR)/libpgsql_unittests-run_unittests.Po
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libpgsql_unittests_SOURCES)
+DIST_SOURCES = $(am__libpgsql_unittests_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(PGSQL_CPPFLAGS)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libpgsql_unittests_SOURCES = pgsql_basics.cc \
+@HAVE_GTEST_TRUE@ pgsql_basics.h pgsql_connection_unittest.cc \
+@HAVE_GTEST_TRUE@ pgsql_exchange_unittest.cc run_unittests.cc
+@HAVE_GTEST_TRUE@libpgsql_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libpgsql_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(PGSQL_LIBS)
+@HAVE_GTEST_TRUE@libpgsql_unittests_LDADD = $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/pgsql/libkea-pgsql.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(GTEST_LDADD)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/pgsql/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/pgsql/tests/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+libpgsql_unittests$(EXEEXT): $(libpgsql_unittests_OBJECTS) $(libpgsql_unittests_DEPENDENCIES) $(EXTRA_libpgsql_unittests_DEPENDENCIES)
+ @rm -f libpgsql_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libpgsql_unittests_LINK) $(libpgsql_unittests_OBJECTS) $(libpgsql_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpgsql_unittests-pgsql_basics.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpgsql_unittests-run_unittests.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libpgsql_unittests-pgsql_basics.o: pgsql_basics.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_basics.o -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_basics.Tpo -c -o libpgsql_unittests-pgsql_basics.o `test -f 'pgsql_basics.cc' || echo '$(srcdir)/'`pgsql_basics.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_basics.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_basics.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_basics.cc' object='libpgsql_unittests-pgsql_basics.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_basics.o `test -f 'pgsql_basics.cc' || echo '$(srcdir)/'`pgsql_basics.cc
+
+libpgsql_unittests-pgsql_basics.obj: pgsql_basics.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_basics.obj -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_basics.Tpo -c -o libpgsql_unittests-pgsql_basics.obj `if test -f 'pgsql_basics.cc'; then $(CYGPATH_W) 'pgsql_basics.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_basics.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_basics.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_basics.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_basics.cc' object='libpgsql_unittests-pgsql_basics.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_basics.obj `if test -f 'pgsql_basics.cc'; then $(CYGPATH_W) 'pgsql_basics.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_basics.cc'; fi`
+
+libpgsql_unittests-pgsql_connection_unittest.o: pgsql_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_connection_unittest.o -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Tpo -c -o libpgsql_unittests-pgsql_connection_unittest.o `test -f 'pgsql_connection_unittest.cc' || echo '$(srcdir)/'`pgsql_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_connection_unittest.cc' object='libpgsql_unittests-pgsql_connection_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_connection_unittest.o `test -f 'pgsql_connection_unittest.cc' || echo '$(srcdir)/'`pgsql_connection_unittest.cc
+
+libpgsql_unittests-pgsql_connection_unittest.obj: pgsql_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_connection_unittest.obj -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Tpo -c -o libpgsql_unittests-pgsql_connection_unittest.obj `if test -f 'pgsql_connection_unittest.cc'; then $(CYGPATH_W) 'pgsql_connection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_connection_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_connection_unittest.cc' object='libpgsql_unittests-pgsql_connection_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_connection_unittest.obj `if test -f 'pgsql_connection_unittest.cc'; then $(CYGPATH_W) 'pgsql_connection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_connection_unittest.cc'; fi`
+
+libpgsql_unittests-pgsql_exchange_unittest.o: pgsql_exchange_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_exchange_unittest.o -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Tpo -c -o libpgsql_unittests-pgsql_exchange_unittest.o `test -f 'pgsql_exchange_unittest.cc' || echo '$(srcdir)/'`pgsql_exchange_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_exchange_unittest.cc' object='libpgsql_unittests-pgsql_exchange_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_exchange_unittest.o `test -f 'pgsql_exchange_unittest.cc' || echo '$(srcdir)/'`pgsql_exchange_unittest.cc
+
+libpgsql_unittests-pgsql_exchange_unittest.obj: pgsql_exchange_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_exchange_unittest.obj -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Tpo -c -o libpgsql_unittests-pgsql_exchange_unittest.obj `if test -f 'pgsql_exchange_unittest.cc'; then $(CYGPATH_W) 'pgsql_exchange_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_exchange_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_exchange_unittest.cc' object='libpgsql_unittests-pgsql_exchange_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_exchange_unittest.obj `if test -f 'pgsql_exchange_unittest.cc'; then $(CYGPATH_W) 'pgsql_exchange_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_exchange_unittest.cc'; fi`
+
+libpgsql_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libpgsql_unittests-run_unittests.Tpo -c -o libpgsql_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-run_unittests.Tpo $(DEPDIR)/libpgsql_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libpgsql_unittests-run_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libpgsql_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libpgsql_unittests-run_unittests.Tpo -c -o libpgsql_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-run_unittests.Tpo $(DEPDIR)/libpgsql_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libpgsql_unittests-run_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-pgsql_basics.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-run_unittests.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-pgsql_basics.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-run_unittests.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-TESTS check-am clean clean-generic \
+ clean-libtool clean-noinstPROGRAMS cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/pgsql/tests/pgsql_basics.cc b/src/lib/pgsql/tests/pgsql_basics.cc
new file mode 100644
index 0000000..e6d061a
--- /dev/null
+++ b/src/lib/pgsql/tests/pgsql_basics.cc
@@ -0,0 +1,149 @@
+// Copyright (C) 2021-2022 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 <pgsql/pgsql_exchange.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <pgsql/tests/pgsql_basics.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <vector>
+
+using namespace isc;
+using namespace isc::db;
+using namespace isc::db::test;
+
+PgSqlBasicsTest::PgSqlBasicsTest() : expected_col_names_(NUM_BASIC_COLS) {
+ // Create database connection parameter list
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+
+ // Create and open the database connection
+ conn_.reset(new PgSqlConnection(params));
+ conn_->openDatabase();
+
+ // Create the list of expected column names
+ expected_col_names_[ID_COL] = "id";
+ expected_col_names_[BOOL_COL] = "bool_col";
+ expected_col_names_[BYTEA_COL] = "bytea_col";
+ expected_col_names_[BIGINT_COL] = "bigint_col";
+ expected_col_names_[SMALLINT_COL] = "smallint_col";
+ expected_col_names_[INT_COL] = "int_col";
+ expected_col_names_[TEXT_COL] = "text_col";
+ expected_col_names_[TIMESTAMP_COL] = "timestamp_col";
+ expected_col_names_[VARCHAR_COL] = "varchar_col";
+ expected_col_names_[INET4_COL] = "inet4_col";
+ expected_col_names_[FLOAT_COL] = "float_col";
+ expected_col_names_[JSON_COL] = "json_col";
+ expected_col_names_[MIN_INT_COL] = "min_int_col";
+ expected_col_names_[MAX_INT_COL] = "max_int_col";
+ expected_col_names_[INET6_COL] = "inet6_col";
+ expected_col_names_[LOCALTIME_COL] = "localtime_col";
+
+ destroySchema();
+ createSchema();
+}
+
+PgSqlBasicsTest::~PgSqlBasicsTest () {
+ destroySchema();
+}
+
+const std::string&
+PgSqlBasicsTest::expectedColumnName(int col) {
+ if (col < 0 || col >= NUM_BASIC_COLS) {
+ isc_throw(BadValue,
+ "definedColumnName: invalid column value" << col);
+ }
+
+ return (expected_col_names_[col]);
+}
+
+void
+PgSqlBasicsTest::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), "
+ " inet4_col INET, "
+ " float_col FLOAT, "
+ " json_col JSON,"
+ " min_int_col INT, "
+ " max_int_col INT, "
+ " inet6_col INET, "
+ " localtime_col TIMESTAMP WITH TIME ZONE "
+ "); ";
+
+ PgSqlResult r(PQexec(*conn_, sql));
+ ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
+ << " create basics table failed: " << PQerrorMessage(*conn_);
+}
+
+void
+PgSqlBasicsTest::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_);
+ }
+}
+
+void
+PgSqlBasicsTest::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_);
+}
+
+void
+PgSqlBasicsTest::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_);
+}
+
+void
+PgSqlBasicsTest::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, inet4_col, float_col, json_col,"
+ " min_int_col, max_int_col, inet6_col,"
+ " (extract(epoch from localtime_col) + extract(timezone from localtime_col))::bigint as localtime_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();
+}
diff --git a/src/lib/pgsql/tests/pgsql_basics.h b/src/lib/pgsql/tests/pgsql_basics.h
new file mode 100644
index 0000000..5129cf9
--- /dev/null
+++ b/src/lib/pgsql/tests/pgsql_basics.h
@@ -0,0 +1,161 @@
+// Copyright (C) 2021-2022 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 TEST_PGSQL_BASICS_H
+#define TEST_PGSQL_BASICS_H
+
+#include <config.h>
+
+#include <pgsql/pgsql_connection.h>
+#include <pgsql/pgsql_exchange.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace db {
+namespace test {
+
+/// @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, // Used when epoch coming back is GMT (e.g. lease mgr)
+ VARCHAR_COL,
+ INET4_COL,
+ FLOAT_COL,
+ JSON_COL,
+ MIN_INT_COL,
+ MAX_INT_COL,
+ INET6_COL,
+ LOCALTIME_COL, // Used when epoch coming back is LOCAL (e.g. CB)
+ NUM_BASIC_COLS
+ };
+
+ /// @brief Constructor
+ ///
+ /// Creates the database connection, opens the database, and destroys
+ /// the table (if present) and then recreates it.
+ PgSqlBasicsTest();
+
+ /// @brief Destructor
+ ///
+ /// Destroys the table. The database resources are freed and the connection
+ /// closed by the destruction of conn_.
+ virtual ~PgSqlBasicsTest ();
+
+ /// @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);
+
+ /// @brief Creates the basics table
+ /// Asserts if the creation step fails
+ void createSchema();
+
+ /// @brief Destroys the basics table
+ /// Asserts if the destruction fails
+ void destroySchema();
+
+ /// @brief Executes a SQL statement and tests for an expected outcome
+ ///
+ /// @param r pointer which will contain the result set returned by the
+ /// statement'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.
+ /// @param 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);
+
+ /// @brief Executes a SQL statement and tests for an expected outcome
+ ///
+ /// @param r pointer which will contain the result set returned by the
+ /// statement'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.
+ /// @param 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);
+
+ /// @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
+ /// statement's execution.
+ /// @param exp_rows expected number of rows fetched. (This can be 0).
+ /// @param 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);
+
+ /// @brief Database connection
+ PgSqlConnectionPtr conn_;
+
+ /// @brief List of column names as we expect them to be in fetched rows
+ std::vector<std::string> expected_col_names_;
+};
+
+// 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))
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/pgsql/tests/pgsql_connection_unittest.cc b/src/lib/pgsql/tests/pgsql_connection_unittest.cc
new file mode 100644
index 0000000..ab09009
--- /dev/null
+++ b/src/lib/pgsql/tests/pgsql_connection_unittest.cc
@@ -0,0 +1,651 @@
+// Copyright (C) 2021-2023 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 <database/db_exceptions.h>
+#include <pgsql/pgsql_connection.h>
+#include <pgsql/pgsql_exchange.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <pgsql/tests/pgsql_basics.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <vector>
+
+using namespace isc;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::util;
+
+namespace {
+
+// A small extension of PgSqlBasicsTest that instantiates the actual Kea schema.
+// Those tests tend to be a bit heavy (especially with the CB and its tables),
+// so please consider adding your tests in PgSqlBasicsTest, unless you really need
+// the full schema.
+class PgSqlSchemaTest: public PgSqlBasicsTest {
+public:
+ PgSqlSchemaTest() : PgSqlBasicsTest() {
+ destroySchema(); // We don't need this fake schema with just "basics" table.
+
+ // Create the actual full Kea schema.
+ isc::db::test::createPgSQLSchema(true, true);
+ }
+
+ virtual ~PgSqlSchemaTest() {
+ // Clean up after ourselves.
+ isc::db::test::destroyPgSQLSchema(true, true);
+ }
+};
+
+/// @brief Checks if the schema version is really as expected.
+TEST_F(PgSqlSchemaTest, schemaVersion) {
+
+ PgSqlResultPtr r;
+ std::string sql = "SELECT version, minor FROM schema_version";
+ RUN_SQL(r, sql, PGRES_TUPLES_OK);
+ // There should be one row with 7,0 returned or whatever the latest schema is.
+ ASSERT_EQ(r->getRows(), 1) << "failed to check schema version, expected 1 row, have: "
+ << r->getRows();
+
+ int value = 0;
+ // Get row 0, column 0 (i.e. version field)
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, 0, 0));
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, 0, 0, value));
+ EXPECT_EQ(value, PGSQL_SCHEMA_VERSION_MAJOR)
+ << "invalid schema version reported, major expected " << PGSQL_SCHEMA_VERSION_MAJOR
+ << ", actual:" << value;
+
+ // Get row 0, column 1 (i.e. minor field)
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, 0, 1));
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, 0, 1, value));
+ EXPECT_EQ(value, PGSQL_SCHEMA_VERSION_MINOR)
+ << "invalid schema version reported, minor expected " << PGSQL_SCHEMA_VERSION_MINOR
+ << ", actual:" << value;
+
+}
+
+/// @brief Test fixture for exercising higher order PgSqlConnection functions
+/// selectQuery, insertQuery, updateDeleteQuery. These tests only use two of
+/// the columns in the BASICS table: int_col and text_col. Inserting rows with
+/// varying types and values are tested above. These tests focus on the higher
+/// order function mechanics.
+class PgSqlConnectionTest : public PgSqlBasicsTest {
+public:
+
+ /// @brief Indexes of prepared statements used within the tests.
+ enum StatementIndex {
+ GET_BY_INT_VALUE,
+ GET_BY_INT_RANGE,
+ DELETE_BY_INT_RANGE,
+ INSERT_VALUE,
+ UPDATE_BY_INT_VALUE,
+ GET_ALL_ROWS,
+ DELETE_ALL_ROWS,
+ NUM_STATEMENTS
+ };
+
+ /// @brief Array of tagged PgSql statements.
+ typedef std::array<PgSqlTaggedStatement, NUM_STATEMENTS> TaggedStatementArray;
+
+ /// @brief Prepared PgSql statements used in the tests.
+ TaggedStatementArray tagged_statements = {{
+ { 1, { OID_INT4 }, "GET_BY_INT_VALUE",
+ "SELECT int_col, text_col"
+ " FROM basics WHERE int_col = $1" },
+
+ { 2, { OID_INT4, OID_INT4 }, "GET_BY_INT_RANGE",
+ "SELECT int_col, text_col"
+ " FROM basics WHERE int_col >= $1 and int_col <= $2" },
+
+ { 2, { OID_INT4, OID_INT4 }, "DEL_BY_INT_RANGE",
+ "DELETE FROM basics WHERE int_col >= $1 and int_col <= $2" },
+
+ { 2, { OID_INT4, OID_TEXT }, "INSERT_INT_TEXT",
+ "INSERT INTO basics (int_col,text_col)"
+ " VALUES ($1, $2)" },
+
+ { 2, { OID_INT4, OID_TEXT }, "UPDATE_BY_INT_VALUE",
+ "UPDATE basics SET text_col = $2"
+ " WHERE int_col = $1" },
+
+ { 0, { OID_NONE }, "GET_ALL_ROWS",
+ "SELECT int_col, text_col FROM basics" },
+
+ { 0, { OID_NONE }, "DELETE_ALL_ROWS",
+ "DELETE FROM basics" }
+ }};
+
+ /// @brief Structure for holding data values describing a single
+ /// row. These tests only use two of the columns in the BASICS table:
+ /// int_col and text_col. Inserting rows with varying types and values
+ /// are tested above. These tests focus on the higher order mechanics.
+ struct TestRow {
+ int int_col;
+ std::string text_col;
+
+ bool operator==(const TestRow& other) const {
+ return (int_col == other.int_col &&
+ text_col == other.text_col);
+ }
+ };
+
+ /// @brief Defines a set of test rows.
+ typedef std::vector<TestRow> TestRowSet;
+
+ /// @brief Constructor.
+ PgSqlConnectionTest() : PgSqlBasicsTest() {};
+
+ /// @brief Destructor.
+ virtual ~PgSqlConnectionTest() {
+ if (conn_->isTransactionStarted()) {
+ conn_->rollback();
+ }
+ };
+
+ /// @brief SetUp function which prepares the tagged statements.
+ virtual void SetUp() {
+ ASSERT_NO_THROW_LOG(conn_->prepareStatements(tagged_statements.begin(),
+ tagged_statements.end()));
+ }
+
+ /// @brief Tests inserting data into the database.
+ ///
+ /// @param insert_rows Collection of rows of data to insert. Note that
+ /// each row is inserted as a separate statement execution.
+ void testInsert(const TestRowSet& insert_rows) {
+ for (auto row : insert_rows ) {
+ // Set the insert parameters based on the current insert row.
+ PsqlBindArray in_bindings;
+ in_bindings.add(row.int_col);
+ in_bindings.add(row.text_col);
+
+ // Insert the row into the database.
+ conn_->insertQuery(tagged_statements[INSERT_VALUE], in_bindings);
+ }
+ }
+
+ /// @brief Tests fetching data using PgSqlConnection::selectQuery()
+ ///
+ /// Selects rows from the BASICS table whose int_col value falls within
+ /// an inclusive range.
+ ///
+ /// @param expected_rows Collection of rows of data that we expect to be
+ /// fetched. Note the rows should be in the order you expect them to be
+ /// returned from the database.
+ /// @param begin_int beginning of the range to include.
+ /// @param end_int end fo the range to include.
+ void testSelect(const TestRowSet& expected_rows, const int begin_int, const int end_int) {
+ // Set the where clause parameters to the desired range values.
+ PsqlBindArray in_bindings;
+ in_bindings.add(begin_int);
+ in_bindings.add(end_int);
+
+ // Row set that will receive the fetched rows.
+ TestRowSet fetched_rows;
+
+ // Run the select. The row consumption lambda should populate
+ // fetched_rows based on the the result set returned by the select.
+ conn_->selectQuery(tagged_statements[GET_BY_INT_RANGE], in_bindings,
+ [&](PgSqlResult& r, int row) {
+ TestRow fetched_row;
+ if (row >= expected_rows.size()) {
+ // We have too many rows.
+ isc_throw(Unexpected, "row index exceeded expected row count of "
+ << expected_rows.size());
+ }
+
+ // First column should be int_col and not NULL.
+ if (PgSqlExchange::isColumnNull(r, row, 0)) {
+ isc_throw(Unexpected, "first column is null!");
+ }
+
+ // Fetch the int_col value.
+ PgSqlExchange::getColumnValue(r, row, 0, fetched_row.int_col);
+
+ // Second column should be text and not NULL.
+ if (PgSqlExchange::isColumnNull(r, row, 1)) {
+ isc_throw(Unexpected, "second column is null!");
+ }
+
+ // Fetch the text_col value.
+ PgSqlExchange::getColumnValue(r, row, 1, fetched_row.text_col);
+
+ // Add the fetched row into set of fetched rows.
+ fetched_rows.push_back(fetched_row);
+ });
+
+ // Make sure fetched rows match the expected rows.
+ ASSERT_EQ(fetched_rows, expected_rows);
+ }
+
+ /// @brief Tests updating data using PgSqlConnection::updateDeleteQuery()
+ ///
+ /// In this test, the input data is a set of rows that describe
+ /// which rows in the database to update and how. For each row
+ /// in the set we find the record in the database with matching
+ /// int_col value and replace its text_col value with the the
+ /// text value from the input the row.
+ ///
+ /// @param update_rows Collection of rows of data to update.
+ void testUpdate(const TestRowSet& update_rows) {
+ size_t update_count = 0;
+ for (auto row : update_rows ) {
+ // Set the text value and where clause parameters based on the
+ // this row's values.
+ PsqlBindArray in_bindings;
+ in_bindings.add(row.int_col);
+ in_bindings.add(row.text_col);
+
+ // Update the database.
+ update_count += conn_->updateDeleteQuery(tagged_statements[UPDATE_BY_INT_VALUE],
+ in_bindings);
+ }
+
+ // Number of rows updated should match rows we passed in.
+ ASSERT_EQ(update_count, update_rows.size());
+ }
+
+ /// @brief Tests deleting data using PgSqlConnection::updateDeleteQuery()
+ ///
+ /// Deletes rows from the BASICS table whose int_col value falls within
+ /// an inclusive range.
+ ///
+ /// @param begin_int beginning of the range to include.
+ /// @param end_int end of the range to include.
+ /// @param expected_delete_count number of rows of data we expect to be
+ /// deleted.
+ void testDelete(const int begin_int, const int end_int, size_t expected_delete_count) {
+ // Set the where clause parameters to the desired range values.
+ PsqlBindArray in_bindings;
+ in_bindings.add(begin_int);
+ in_bindings.add(end_int);
+
+ // Execute the delete statement.
+ size_t delete_count = 0;
+ delete_count = conn_->updateDeleteQuery(tagged_statements[DELETE_BY_INT_RANGE],
+ in_bindings);
+
+ // Verify the number of records deleted is as expected.
+ ASSERT_EQ(delete_count, expected_delete_count);
+ }
+};
+
+/// @brief Verifies basics of input parameter sanity checking and statement
+/// execution enforced by executePreparedStatement. Higher order tests
+/// verify actual data CRUD results.
+TEST_F(PgSqlConnectionTest, executePreparedStatement) {
+
+ // Executing with no parameters when they are required should throw.
+ // First we'll omit the bindings (defaults to empty).
+ PgSqlResultPtr r;
+ ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[INSERT_VALUE]),
+ InvalidOperation,
+ "executePreparedStatement: expected: 2 parameters, given: 0,"
+ " statement: INSERT_INT_TEXT, SQL: INSERT INTO basics "
+ "(int_col,text_col) VALUES ($1, $2)");
+
+ // Now we'll pass in an empty array.
+ PsqlBindArray in_bindings;
+ ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[INSERT_VALUE],
+ in_bindings),
+ InvalidOperation,
+ "executePreparedStatement: expected: 2 parameters, given: 0,"
+ " statement: INSERT_INT_TEXT, SQL: INSERT INTO basics "
+ "(int_col,text_col) VALUES ($1, $2)");
+
+ // Executing without parameters when none are expected should be fine.
+ // First we'll simply omit the array.
+ ASSERT_NO_THROW(r = conn_->executePreparedStatement(tagged_statements[GET_ALL_ROWS]));
+
+ // Now with an empty array.
+ ASSERT_NO_THROW(r = conn_->executePreparedStatement(tagged_statements[GET_ALL_ROWS], in_bindings));
+
+ // Executing with parameters when none are required should throw.
+ in_bindings.add(1);
+ in_bindings.add(2);
+ ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[GET_ALL_ROWS],
+ in_bindings),
+ InvalidOperation,
+ "executePreparedStatement: expected: 0 parameters, given: 2,"
+ " statement: GET_ALL_ROWS, SQL: SELECT int_col, text_col FROM basics");
+
+ // Executing with the correct number of parameters should work.
+ ASSERT_NO_THROW(r = conn_->executePreparedStatement(tagged_statements[GET_BY_INT_RANGE],
+ in_bindings));
+
+ // Executing with too many parameters should fail.
+ in_bindings.add(3);
+ ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[GET_BY_INT_RANGE],
+ in_bindings),
+ InvalidOperation,
+ "executePreparedStatement: expected: 2 parameters, given: 3,"
+ " statement: GET_BY_INT_RANGE, SQL: SELECT int_col, text_col"
+ " FROM basics WHERE int_col >= $1 and int_col <= $2");
+}
+
+/// @brief Verify that we can insert rows with
+/// PgSqlConnection::insertQuery() and fetch
+/// them using PgSqlConnection::selectQuery().
+TEST_F(PgSqlConnectionTest, insertSelectTest) {
+
+ // Define the list of rows we want to insert.
+ TestRowSet insert_rows = {
+ { 7, "seven" },
+ { 8, "eight" },
+ { 9, "nine" },
+ };
+
+ // Insert the rows.
+ ASSERT_NO_THROW_LOG(testInsert(insert_rows));
+
+ // Make sure we can fetch a single row.
+ ASSERT_NO_THROW_LOG(testSelect(TestRowSet({{ 8, "eight" }}), 8, 8));
+
+ // Make sure we can fetch all the rows.
+ ASSERT_NO_THROW_LOG(testSelect(insert_rows, 7, 9));
+}
+
+/// @brief Verify that we can update rows with
+/// PgSqlConnection::updateDeleteQuery()
+TEST_F(PgSqlConnectionTest, updateTest) {
+
+ // Define the list of rows we want to insert.
+ TestRowSet insert_rows = {
+ { 7, "seven" },
+ { 8, "eight" },
+ { 9, "nine" },
+ };
+
+ // Insert the rows.
+ ASSERT_NO_THROW_LOG(testInsert(insert_rows));
+
+ // Define the list of updates.
+ TestRowSet update_rows = {
+ { 8, "ate" },
+ { 9, "mine" }
+ };
+
+ // Update the rows.
+ ASSERT_NO_THROW_LOG(testUpdate(update_rows));
+
+ // Fetch the updated rows.
+ ASSERT_NO_THROW_LOG(testSelect(update_rows, 8, 9));
+}
+
+/// @brief Verify that we can delete rows with
+/// PgSqlConnection::updateDeleteQuery()
+TEST_F(PgSqlConnectionTest, deleteTest) {
+
+ // Define the list of rows we want to insert.
+ TestRowSet insert_rows = {
+ { 6, "six" },
+ { 7, "seven" },
+ { 8, "eight" },
+ { 9, "nine" },
+ };
+
+ // Insert the rows.
+ ASSERT_NO_THROW_LOG(testInsert(insert_rows));
+
+ // Fetch the all rows.
+ ASSERT_NO_THROW_LOG(testSelect(insert_rows, 0, 10));
+
+ // Delete rows 7 and 8.
+ ASSERT_NO_THROW_LOG(testDelete(7, 8, 2));
+
+ // Fetch the all rows.
+ ASSERT_NO_THROW_LOG(testSelect(TestRowSet({{6, "six"}, {9, "nine"}}), 0, 10));
+}
+
+// Verifies that transaction nesting and operations: start, commit,
+// and rollback work correctly.
+TEST_F(PgSqlConnectionTest, transactions) {
+ ASSERT_FALSE(conn_->isTransactionStarted());
+
+ // Two inserts within a transaction and successful commit.
+ TestRowSet two_rows = {{1, "one"}, {2, "two"}};
+ conn_->startTransaction();
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert(two_rows));
+ conn_->commit();
+
+ // Should not be in a transaction and we should have both
+ // rows we inserted.
+ ASSERT_FALSE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10));
+
+ // Add third row but roll back the transaction. We should still have
+ // two rows in the table.
+ conn_->startTransaction();
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert({{ 3, "three"}}));
+ conn_->rollback();
+
+ // We should not be in a transaction and should still have
+ // only the first two rows.
+ ASSERT_FALSE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10));
+
+ // Nested transaction. The inner transaction should be ignored and the outer
+ // transaction rolled back. We should have only the original two rows in the
+ // database.
+ conn_->startTransaction();
+ EXPECT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert({{ 3, "three"}}));
+
+ conn_->startTransaction();
+ EXPECT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert({{ 4, "four"}}));
+
+ // First commit should do nothing other than decrement
+ // the transaction ref count.
+ conn_->commit();
+ ASSERT_TRUE(conn_->isTransactionStarted());
+
+ // Rollback should end the transaction without committing changes.
+ conn_->rollback();
+ ASSERT_FALSE(conn_->isTransactionStarted());
+
+ // We should still have only the first two rows.
+ ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10));
+
+ // Nested transaction. The inner transaction is rolled back but this should
+ // be ignored because nested transactions are not supported. We should
+ // have two new rows.
+
+ // Insert five row.
+ conn_->startTransaction();
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ TestRow row_five({ 5, "five" });
+ ASSERT_NO_THROW_LOG(testInsert(TestRowSet({ row_five })));
+ two_rows.push_back(row_five);
+
+ // Insert six row.
+ conn_->startTransaction();
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ TestRow row_six({ 6, "six" });
+ ASSERT_NO_THROW_LOG(testInsert(TestRowSet({ row_six })));
+ two_rows.push_back(row_six);
+
+ // Rollback should do nothing other than decrement the
+ // reference count.
+ conn_->rollback();
+ EXPECT_TRUE(conn_->isTransactionStarted());
+
+ // Commit should complete the transaction and commit the inserts.
+ conn_->commit();
+ EXPECT_FALSE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10));
+
+ // Committing or rolling back a not started transaction is a coding error.
+ EXPECT_THROW(conn_->commit(), isc::Unexpected);
+ EXPECT_THROW(conn_->rollback(), isc::Unexpected);
+}
+
+// Verifies that savepoints operate correctly.
+TEST_F(PgSqlConnectionTest, savepoints) {
+ // We want to trigger DuplicateEntry errors so let's
+ // add a unique constraint to the table.
+ ASSERT_NO_THROW(conn_->executeSQL("ALTER TABLE basics ADD CONSTRAINT"
+ " unique_int_col UNIQUE (int_col);"));
+ // Verify we are not in a transaction.
+ ASSERT_FALSE(conn_->isTransactionStarted());
+
+ // Creating or rollback to savepoints outside of transactions
+ // should throw.
+ ASSERT_THROW_MSG(conn_->createSavepoint("rubbish"), InvalidOperation,
+ "no transaction, cannot create savepoint: rubbish");
+ ASSERT_THROW_MSG(conn_->rollbackToSavepoint("rubbish"), InvalidOperation,
+ "no transaction, cannot rollback to savepoint: rubbish");
+
+ // Test that we can create and rollback to a savepoint, then
+ // committing only the pre savepoint work.
+ TestRowSet first_row = {{1, "one"}};
+ ASSERT_NO_THROW_LOG(conn_->startTransaction());
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert(first_row));
+
+ // Create a savepoint.
+ ASSERT_NO_THROW_LOG(conn_->createSavepoint("sp_one"));
+
+ // Insert a second row, without committing it.
+ TestRowSet second_row = {{2, "two"}};
+ ASSERT_NO_THROW_LOG(testInsert(second_row));
+
+ // Rollback to the savepoint.
+ ASSERT_NO_THROW_LOG(conn_->rollbackToSavepoint("sp_one"));
+
+ // Commit the transaction.
+ conn_->commit();
+
+ // We should not be in a transaction but we should
+ // only have the first row.
+ ASSERT_FALSE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testSelect(first_row, 0, 10));
+
+ // Now we'll test that we can create and rollback to a
+ // savepoint after Postgresql aborts an insert due to
+ // duplicate key error. We should still be able to
+ // commit the pre-savepoint and post rollback work.
+ ASSERT_NO_THROW_LOG(conn_->startTransaction());
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert(second_row));
+
+ // Create a savepoint.
+ ASSERT_NO_THROW_LOG(conn_->createSavepoint("sp_two"));
+
+ // Attempt to insert a duplicate first row.
+ ASSERT_THROW(testInsert(first_row), DuplicateEntry);
+
+ // Rollback to the savepoint.
+ ASSERT_NO_THROW_LOG(conn_->rollbackToSavepoint("sp_two"));
+
+ // Now insert a third row.
+ TestRowSet third_row = {{3, "three"}};
+ ASSERT_NO_THROW_LOG(testInsert(third_row));
+
+ // Commit the transaction.
+ conn_->commit();
+
+ // We should not be in a transaction and we should
+ // two rows.
+ ASSERT_FALSE(conn_->isTransactionStarted());
+ TestRowSet three_rows{{1, "one"}, {2, "two"}, {3, "three"}};
+ ASSERT_NO_THROW_LOG(testSelect(three_rows, 0, 10));
+}
+
+// Tests that invalid port value causes an error.
+TEST_F(PgSqlConnectionTest, portInvalid) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_PORT_1);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidPort);
+}
+
+// Tests that valid connection timeout is accepted.
+TEST_F(PgSqlConnectionTest, connectionTimeout) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ VALID_TIMEOUT);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ std::string parameters;
+ ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters());
+ EXPECT_TRUE(parameters.find("connect_timeout = 10") != std::string::npos)
+ << "parameter not found in " << parameters;
+}
+
+// Tests that invalid timeout value type causes an error.
+TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_1);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+// Tests that a negative connection timeout value causes an error.
+TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid2) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_2);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+// Tests that a zero connection timeout value causes an error.
+TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid3) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_3);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+// Tests that valid tcp user timeout is accepted. This parameter is
+// supported by PostgreSQL 12 and later.
+#ifdef HAVE_PGSQL_TCP_USER_TIMEOUT
+TEST_F(PgSqlConnectionTest, tcpUserTimeout) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ VALID_TCP_USER_TIMEOUT);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ std::string parameters;
+ ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters());
+ EXPECT_TRUE(parameters.find("tcp_user_timeout = 8000") != std::string::npos)
+ << "parameter not found in " << parameters;
+}
+#endif
+
+// Tests that a zero tcp user timeout is accepted.
+TEST_F(PgSqlConnectionTest, tcpUserTimeoutZero) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ VALID_TCP_USER_TIMEOUT_ZERO);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ std::string parameters;
+ ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters());
+ EXPECT_FALSE(parameters.find("tcp_user_timeout") != std::string::npos)
+ << "parameter found in " << parameters << " but expected to be gone";
+}
+
+// Tests that an invalid tcp user timeout causes an error.
+TEST_F(PgSqlConnectionTest, tcpUserTimeoutInvalid) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_1);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+
+}; // namespace
diff --git a/src/lib/pgsql/tests/pgsql_exchange_unittest.cc b/src/lib/pgsql/tests/pgsql_exchange_unittest.cc
new file mode 100644
index 0000000..7bbf747
--- /dev/null
+++ b/src/lib/pgsql/tests/pgsql_exchange_unittest.cc
@@ -0,0 +1,1545 @@
+// Copyright (C) 2016-2022 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 <pgsql/pgsql_connection.h>
+#include <pgsql/pgsql_exchange.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <pgsql/tests/pgsql_basics.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <vector>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::util;
+using namespace boost::posix_time;
+using namespace boost::gregorian;
+
+namespace {
+
+/// @brief Verifies the ability to add various data types to
+/// the bind array.
+TEST(PsqlBindArray, addDataTest) {
+
+ 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);
+
+ // Add an empty string.
+ b.add(std::string(""));
+
+ // Add a v4 address.
+ asiolink::IOAddress addr4("192.168.1.1");
+ b.addInet4(addr4);
+
+ // Add a v6 address.
+ asiolink::IOAddress addr6("3001::1");
+ b.addInet6(addr6);
+
+ // Add a double. Not sure how portably reliable this test will be.
+ double dbl = 2.0;
+ b.add(dbl);
+
+ // Add a JSON.
+ ElementPtr elems = Element::fromJSON("{ \"foo\": \"bar\" }");
+ b.add(elems);
+
+ // Add a Temporary blob
+ std::vector<uint8_t> blob({0x0a,0x0b,0x0,0xc,0xd});
+ b.addTempBinary(blob);
+ }
+
+ // 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"
+ "11 : empty\n"
+ "12 : \"192.168.1.1\"\n"
+ "13 : \"3001::1\"\n"
+ "14 : \"2\"\n"
+ "15 : \"{ \"foo\": \"bar\" }\"\n"
+ "16 : 0x0a0b000c0d\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to add Triplets to
+/// the bind array.
+TEST(PsqlBindArray, addTriplet) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Triplet<uint32_t> empty;
+ Triplet<uint32_t> not_empty(1,2,3);
+
+ // Add triplets to the array.
+ b.add(empty);
+ b.add(not_empty);
+ b.addMin(empty);
+ b.addMin(not_empty);
+ b.addMax(empty);
+ b.addMax(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(6, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"2\"\n"
+ "2 : empty\n"
+ "3 : \"1\"\n"
+ "4 : empty\n"
+ "5 : \"3\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to add Optional strings to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalString) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<std::string> empty;
+ Optional<std::string> not_empty("whoopee!");
+
+ // Add strings to the array.
+ b.addOptional(empty);
+ b.addOptional(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"whoopee!\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to add Optional booleans to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalBool) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<bool> empty;
+ Optional<bool> am_false(false);
+ Optional<bool> am_true(true);
+
+ // Add booleans to the array.
+ b.addOptional(empty);
+ b.addOptional(am_false);
+ b.addOptional(am_true);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(3, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"0\"\n"
+ "2 : \"1\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+
+/// @brief Verifies the ability to add OptionalIntegers to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalInteger) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<uint32_t> empty;
+ Optional<uint32_t> not_empty(123);
+
+ // Add the integers to the array..
+ b.addOptional(empty);
+ b.addOptional(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"123\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to add Optional IPv4 addresses to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalInet4) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<asiolink::IOAddress> empty;
+ Optional<asiolink::IOAddress> not_empty(asiolink::IOAddress("192.16.1.1"));
+
+ // Verify we cannot add a v6 address.
+ Optional<asiolink::IOAddress> not_v4(asiolink::IOAddress("3001::1"));
+ ASSERT_THROW_MSG(b.addOptionalInet4(not_v4), BadValue,
+ "unable to add address to PsqlBindAray"
+ " '3001::1' is not an IPv4 address");
+
+ // Add addresses to bind array.
+ b.addOptionalInet4(empty);
+ b.addOptionalInet4(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"192.16.1.1\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to add optional IPv6 addresses to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalInet6) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<asiolink::IOAddress> empty;
+ Optional<asiolink::IOAddress> not_empty(asiolink::IOAddress("3001::1"));
+
+ // Verify we cannot add a v6 address.
+ Optional<asiolink::IOAddress> not_v6(asiolink::IOAddress("192.168.1.1"));
+ ASSERT_THROW_MSG(b.addOptionalInet6(not_v6), BadValue,
+ "unable to add address to PsqlBindAray"
+ " '192.168.1.1' is not an IPv6 address");
+
+ // Add addresses to bind array.
+ b.addOptionalInet6(empty);
+ b.addOptionalInet6(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"3001::1\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies that PgResultSet row and column 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 determined 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 use the methods used for testing:
+/// amNull(), getInteger<T>(), getType()
+TEST_F(PgSqlBasicsTest, get) {
+ PsqlBindArray bind_array;
+
+ // The array itself is empty, its first column is not null
+ EXPECT_THROW(bind_array.amNull(), OutOfRange);
+ EXPECT_THROW(bind_array.getInteger<uint32_t>(), OutOfRange);
+ EXPECT_THROW(bind_array.getType(), OutOfRange);
+
+ // Now try again with proper values.
+ bind_array.add(123); // This will be converted to "123" string.
+ bind_array.addNull();
+ bind_array.add("sagittarius");
+ EXPECT_FALSE(bind_array.amNull(0)); // first column is not null
+ EXPECT_TRUE(bind_array.amNull(1)); // second column is null
+ EXPECT_FALSE(bind_array.amNull(2)); // third column is not null
+
+ EXPECT_EQ(123, bind_array.getInteger<uint32_t>(0)); // first column is 123
+ EXPECT_THROW(bind_array.getInteger<uint32_t>(1), BadValue);
+ EXPECT_THROW(bind_array.getInteger<uint32_t>(2), boost::bad_lexical_cast);
+
+ EXPECT_EQ(PsqlBindArray::TEXT_FMT, bind_array.getType(0));
+ EXPECT_EQ(PsqlBindArray::TEXT_FMT, bind_array.getType(1));
+ EXPECT_EQ(PsqlBindArray::TEXT_FMT, bind_array.getType(2));
+}
+
+/// @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);
+}
+
+/// @brief Verify that we can read and write ptime using TIMESTAMP columns.
+TEST_F(PgSqlBasicsTest, ptimeTimestamp) {
+ // Create a prepared statement for inserting a TIMESTAMP
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TIMESTAMP }, "timestamp_insert",
+ "INSERT INTO BASICS (localtime_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ // Create an empty array.
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Make sure we catch values before the epoch.
+ ptime christmas1969(date(1969, Dec, 25));
+ ASSERT_THROW_MSG(bind_array->addTimestamp(christmas1969), BadValue,
+ "Time value is before the epoch");
+
+ // Make sure we catch values that are too big.
+ time_duration duration = hours(10) + minutes(14) + seconds(15);
+ ptime day_too_far(date(2038, Jan, 21), duration);
+ ASSERT_THROW_MSG(bind_array->addTimestamp(day_too_far), BadValue,
+ "Time value is too large: 2147681655");
+
+ // Now add reasonable day, US National Ice Cream day.
+ ptime nice_day(date(2021, Jul, 18), duration);
+ bind_array->addTimestamp(nice_day);
+
+ PgSqlResultPtr r;
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Timestamp column should not be null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, 0, LOCALTIME_COL));
+
+ // Convert fetched value into a ptime.
+ ptime fetched_time;
+ ASSERT_NO_THROW_LOG(PgSqlExchange::getColumnValue(*r, 0, LOCALTIME_COL,
+ fetched_time));
+
+ ASSERT_EQ(fetched_time, nice_day);
+}
+
+/// @brief Verifies the ability to insert a string into
+/// the bind array.
+TEST(PsqlBindArray, insertString) {
+ PsqlBindArray b;
+
+ // Make a non-temporary string to insert.
+ std::string one("one");
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ // Make sure you can "insert" at the front of an empty array.
+ b.insert("two", 0);
+
+ // Add a binding.
+ b.add("four");
+
+ // Verify an out of range index throws.
+ ASSERT_THROW_MSG(b.insert(std::string("too far"), 5), OutOfRange,
+ "PsqlBindArray::insert - index: 5, "
+ "is larger than the array size: 2");
+
+ // Insert a non-temporary string at the front.
+ b.insert(one, 0);
+
+ // Insert a temporary string.
+ b.insert("three", 2);
+
+ // Add another one.
+ b.add("five");
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(5, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : \"one\"\n"
+ "1 : \"two\"\n"
+ "2 : \"three\"\n"
+ "3 : \"four\"\n"
+ "4 : \"five\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to pop bindings off
+/// the end of a bind array.
+TEST(PsqlBindArray, popBackTest) {
+ PsqlBindArray b;
+
+ // Popping on an empty array should throw.
+ ASSERT_THROW_MSG(b.popBack(), OutOfRange,
+ "PsqlBindArray::pop_back - array empty");
+
+ // Add five integers.
+ for (int i = 1; i < 6; ++i) {
+ b.add(i);
+ }
+
+ // Verify size.
+ EXPECT_EQ(b.size(), 5);
+
+ // Pop one off.
+ ASSERT_NO_THROW_LOG(b.popBack());
+
+ // Verify size.
+ EXPECT_EQ(b.size(), 4);
+
+ // Pop another one off.
+ ASSERT_NO_THROW_LOG(b.popBack());
+
+ // Verify size.
+ EXPECT_EQ(b.size(), 3);
+
+ // This is what we should have left.
+ std::string expected =
+ "0 : \"1\"\n"
+ "1 : \"2\"\n"
+ "2 : \"3\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verify that we can read and write IPv4 addresses
+/// using INET columns.
+TEST_F(PgSqlBasicsTest, inetTest4) {
+ // Create a prepared statement for inserting an IPv4 address
+ const char* st_name = "inet4_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TEXT }, st_name,
+ "INSERT INTO BASICS (inet4_col) values (cast($1 as inet))" },
+ { 1, { OID_TEXT }, "check_where",
+ "select * from BASICS where inet4_col = cast($1 as inet)" }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[1]));
+
+ // Build our reference list of reference values
+ std::vector<asiolink::IOAddress>inets;
+ inets.push_back(asiolink::IOAddress("0.0.0.0"));
+ inets.push_back(asiolink::IOAddress("192.168.1.9"));
+ inets.push_back(asiolink::IOAddress("192.168.1.1"));
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < inets.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addInet4(inets[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, inets.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ asiolink::IOAddress fetched_inet("0.0.0.0");
+ for ( ; row < inets.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INET4_COL));
+
+ // Fetch and verify the column value
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue4(*r, row, INET4_COL));
+ EXPECT_EQ(fetched_inet, inets[row]);
+ }
+
+ // Verify that casting from string to inet works in where clauses.
+ r.reset();
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addInet4(inets[1]);
+ RUN_PREP(r, statement[1], bind_array, PGRES_TUPLES_OK);
+ ASSERT_EQ(r->getRows(),1);
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue4(*r, 0, INET4_COL));
+ EXPECT_EQ(fetched_inet, inets[1]);
+
+ // 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, INET4_COL));
+}
+
+/// @brief Verify that we can read and write IPv6 addresses
+/// using INET columns.
+TEST_F(PgSqlBasicsTest, inetTest6) {
+ // Create a prepared statement for inserting an IPv6 address
+ const char* st_name = "inet6_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TEXT }, st_name,
+ "INSERT INTO BASICS (inet6_col) values (cast($1 as inet))" },
+ { 1, { OID_TEXT }, "check_where",
+ "select * from BASICS where inet6_col = cast($1 as inet)" }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[1]));
+
+ // Build our reference list of reference values
+ std::vector<asiolink::IOAddress>inets;
+ inets.push_back(asiolink::IOAddress("::"));
+ inets.push_back(asiolink::IOAddress("3001::1"));
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < inets.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addInet6(inets[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, inets.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ asiolink::IOAddress fetched_inet("::");
+ for ( ; row < inets.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INET6_COL));
+
+ // Fetch and verify the column value
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue6(*r, row, INET6_COL));
+ EXPECT_EQ(fetched_inet, inets[row]);
+ }
+
+ // Verify that casting from string to inet works in where clauses.
+ r.reset();
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addInet6(inets[1]);
+ RUN_PREP(r, statement[1], bind_array, PGRES_TUPLES_OK);
+ ASSERT_EQ(r->getRows(),1);
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue6(*r, 0, INET6_COL));
+ EXPECT_EQ(fetched_inet, inets[1]);
+
+ // 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, INET6_COL));
+}
+
+/// @brief Verify that we can read and write floats
+TEST_F(PgSqlBasicsTest, floatTest) {
+ // Create a prepared statement for inserting a FLOAT
+ const char* st_name = "float_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TEXT }, st_name,
+ "INSERT INTO BASICS (float_col) values (cast($1 as float))" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ // Build our reference list of reference values
+ std::vector<double>floats;
+ floats.push_back(1.345);
+ floats.push_back(200);
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < floats.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(floats[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, floats.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ for ( ; row < floats.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, FLOAT_COL));
+
+ // Fetch and verify the column value
+ double fetched_float;
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, FLOAT_COL, fetched_float));
+ EXPECT_EQ(fetched_float, floats[row]);
+ }
+
+ // 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, FLOAT_COL));
+}
+
+/// @brief Verify that we can read and write JSON columns.
+TEST_F(PgSqlBasicsTest, jsonTest) {
+ // Create a prepared statement for inserting a JSON
+ const char* st_name = "json_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TEXT }, st_name,
+ "INSERT INTO BASICS (json_col) values (cast($1 as json))" }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+
+ // Build our reference list of reference values
+ std::vector<ElementPtr>elem_ptrs;
+ ASSERT_NO_THROW_LOG(elem_ptrs.push_back(Element::fromJSON("{ \"one\":1 }")));
+ ASSERT_NO_THROW_LOG(elem_ptrs.push_back(Element::fromJSON("{ \"two\":\"two\" }")));
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < elem_ptrs.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(elem_ptrs[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, elem_ptrs.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ for ( ; row < elem_ptrs.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, JSON_COL));
+
+ // Fetch and verify the column value
+ ElementPtr fetched_ptr;
+ ASSERT_NO_THROW_LOG(PgSqlExchange::getColumnValue(*r, row, JSON_COL, fetched_ptr));
+ EXPECT_TRUE(fetched_ptr->equals(*(elem_ptrs[row])));
+ }
+
+ // 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, JSON_COL));
+}
+
+/// @brief Verify that we can read and write integer Triplets.
+TEST_F(PgSqlBasicsTest, tripleTest) {
+ // Create a prepared statement for inserting a Triplet
+ const char* st_name = "triplets_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 3, { OID_INT4, OID_INT4, OID_INT4 }, st_name,
+ "INSERT INTO BASICS (int_col, min_int_col, max_int_col) values ($1, $2, $3)" }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+
+ // Build our reference list of reference values
+ std::vector<Triplet<uint32_t>> triplets;
+ triplets.push_back(Triplet<uint32_t>()); // def column is null
+ triplets.push_back(Triplet<uint32_t>(10)); // only default column
+ triplets.push_back(Triplet<uint32_t>(5,10,15)); // all three columns
+ triplets.push_back(Triplet<uint32_t>(10,10,15)); // min column is null
+ triplets.push_back(Triplet<uint32_t>(5,10,10)); // max column is null
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (auto triplet : triplets) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(triplet);
+ bind_array->addMin(triplet);
+ bind_array->addMax(triplet);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, triplets.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ for (auto expected : triplets) {
+ Triplet<uint32_t> fetched;
+ // First we test making a triplet only with default value column.
+ ASSERT_NO_THROW_LOG(fetched = PgSqlExchange::getTripletValue(*r, row, INT_COL));
+ if (expected.unspecified()) {
+ EXPECT_TRUE(fetched.unspecified());
+ } else {
+ EXPECT_FALSE(fetched.unspecified());
+ EXPECT_EQ(fetched.get(), expected.get());
+ }
+
+ // Now test making a triplet with all three columns.
+ ASSERT_NO_THROW_LOG(
+ fetched = PgSqlExchange::getTripletValue(*r, row,
+ INT_COL, MIN_INT_COL, MAX_INT_COL));
+ if (expected.unspecified()) {
+ EXPECT_TRUE(fetched.unspecified());
+ } else {
+ EXPECT_FALSE(fetched.unspecified());
+ EXPECT_EQ(fetched.get(), expected.get());
+ EXPECT_EQ(fetched.getMin(), expected.getMin());
+ EXPECT_EQ(fetched.getMax(), expected.getMax());
+ }
+
+ ++row;
+ }
+
+ // Clean out the table
+ WIPE_ROWS(r);
+}
+
+/// @brief Verify PgResultRowWorker operations.
+TEST_F(PgSqlBasicsTest, resultRowWorker) {
+ // Create a prepared statement for inserting multiple types
+ const char* st_name = "row_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 14,
+ {
+ OID_BOOL,
+ OID_BYTEA,
+ OID_INT8,
+ OID_INT2,
+ OID_INT4,
+ OID_TEXT,
+ OID_TIMESTAMP,
+ OID_TEXT,
+ OID_TEXT,
+ OID_TEXT,
+ OID_TEXT,
+ OID_INT4,
+ OID_INT4,
+ }, st_name,
+ "INSERT INTO BASICS ("
+ " bool_col, "
+ " bytea_col, "
+ " bigint_col, "
+ " smallint_col, "
+ " int_col, "
+ " text_col, "
+ " localtime_col, "
+ " varchar_col, "
+ " inet4_col, "
+ " float_col, "
+ " json_col, "
+ " min_int_col, "
+ " max_int_col, "
+ " inet6_col) "
+ " VALUES ($1, $2, $3, $4, $5, $6, $7, $8, cast($9 as inet), "
+ " cast($10 as float), cast($11 as json), $12, $13, $14)"
+ }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+
+ // Create a bind array of input values.
+ PsqlBindArrayPtr b(new PsqlBindArray());
+
+ bool exp_bool(true);
+ b->add(exp_bool);
+
+ std::vector<uint8_t> exp_bytes({0x01, 0x02, 0x03, 0x04});
+ b->add(exp_bytes);
+
+ uint64_t exp_bigint = 9876;
+ b->add(exp_bigint);
+
+ uint16_t exp_smallint = 12;
+ b->add(exp_smallint);
+
+ uint32_t exp_int = 345;
+ b->add(exp_int);
+
+ std::string exp_text = "just some string";
+ b->add(exp_text);
+
+ time_duration duration = hours(7) + minutes(45) + seconds(9);
+ ptime exp_timestamp(date(2021, Jul, 18), duration);
+ b->addTimestamp(exp_timestamp);
+
+ const char* exp_varchar = "really just a string";
+ b->add(exp_varchar);
+
+ asiolink::IOAddress exp_inet4("192.168.1.35");
+ b->addInet4(exp_inet4);
+
+ double exp_double(2.5);
+ b->add(exp_double);
+
+ ElementPtr exp_elems = Element::fromJSON("{ \"foo\": \"bar\" }");
+ b->add(exp_elems);
+
+ uint32_t exp_min = 100;
+ b->add(exp_min);
+
+ uint32_t exp_max = 500;
+ b->add(exp_max);
+
+ asiolink::IOAddress exp_inet6("3001::77");
+ b->addInet6(exp_inet6);
+
+ PgSqlResultPtr r;
+ RUN_PREP(r, statement[0], b, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Create a row worker.
+ PgSqlResultRowWorkerPtr worker;
+
+ // Creating the row worker for the first (and only) row should succeed.
+ ASSERT_NO_THROW_LOG(worker.reset(new PgSqlResultRowWorker(*r, 0)));
+
+ // Now let's test all the getters.
+ EXPECT_EQ(exp_bool, worker->getBool(BOOL_COL));
+
+ std::vector<uint8_t> fetched_bytes;
+ ASSERT_NO_THROW_LOG(worker->getBytes(BYTEA_COL, fetched_bytes));
+ EXPECT_EQ(exp_bytes, fetched_bytes);
+
+ EXPECT_EQ(exp_bigint, worker->getBigInt(BIGINT_COL));
+ EXPECT_EQ(exp_smallint, worker->getSmallInt(SMALLINT_COL));
+ EXPECT_EQ(exp_int, worker->getInt(INT_COL));
+ EXPECT_EQ(exp_text, worker->getString(TEXT_COL));
+ EXPECT_EQ(exp_timestamp, worker->getTimestamp(LOCALTIME_COL));
+ EXPECT_EQ(exp_varchar, worker->getString(VARCHAR_COL));
+ EXPECT_EQ(exp_inet4, worker->getInet4(INET4_COL));
+ EXPECT_EQ(exp_double, worker->getDouble(FLOAT_COL));
+ EXPECT_EQ(*exp_elems, *(worker->getJSON(JSON_COL)));
+ EXPECT_EQ(exp_min, worker->getInt(MIN_INT_COL));
+ EXPECT_EQ(exp_max, worker->getInt(MAX_INT_COL));
+ EXPECT_EQ(exp_inet6, worker->getInet6(INET6_COL));
+
+ // Get a triplet using int_col as the sole value.
+ Triplet<uint32_t>fetched_triplet = worker->getTriplet(INT_COL);
+ EXPECT_EQ(exp_int, fetched_triplet.get());
+
+ // Get a triplet using int_col, min_col, and max_col values.
+ fetched_triplet = worker->getTriplet(INT_COL, MIN_INT_COL, MAX_INT_COL);
+ EXPECT_EQ(exp_int, fetched_triplet.get());
+ EXPECT_EQ(exp_min, fetched_triplet.getMin());
+ EXPECT_EQ(exp_max, fetched_triplet.getMax());
+
+ // Attempting to access an invalid row should throw.
+ ASSERT_THROW_MSG(worker.reset(new PgSqlResultRowWorker(*r, 1)),
+ DbOperationError, "row: 1, out of range: 0..1");
+}
+
+} // namespace
diff --git a/src/lib/pgsql/tests/run_unittests.cc b/src/lib/pgsql/tests/run_unittests.cc
new file mode 100644
index 0000000..4e83d4b
--- /dev/null
+++ b/src/lib/pgsql/tests/run_unittests.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 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 <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/pgsql/testutils/Makefile.am b/src/lib/pgsql/testutils/Makefile.am
new file mode 100644
index 0000000..195f981
--- /dev/null
+++ b/src/lib/pgsql/testutils/Makefile.am
@@ -0,0 +1,24 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\"
+AM_CPPFLAGS += -DDATABASE_WIPE_DIR=\"$(abs_top_builddir)/src/share/database/scripts\"
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libpgsqltest.la
+
+libpgsqltest_la_SOURCES = pgsql_schema.cc pgsql_schema.h
+
+libpgsqltest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libpgsqltest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(PGSQL_CPPFLAGS)
+libpgsqltest_la_LDFLAGS = $(AM_LDFLAGS) $(PGSQL_LIBS)
+
+libpgsqltest_la_LIBADD = $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+
+endif
diff --git a/src/lib/pgsql/testutils/Makefile.in b/src/lib/pgsql/testutils/Makefile.in
new file mode 100644
index 0000000..9b8bc29
--- /dev/null
+++ b/src/lib/pgsql/testutils/Makefile.in
@@ -0,0 +1,862 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/pgsql/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libpgsqltest_la_DEPENDENCIES = $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+am__libpgsqltest_la_SOURCES_DIST = pgsql_schema.cc pgsql_schema.h
+@HAVE_GTEST_TRUE@am_libpgsqltest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libpgsqltest_la-pgsql_schema.lo
+libpgsqltest_la_OBJECTS = $(am_libpgsqltest_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libpgsqltest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libpgsqltest_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libpgsqltest_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libpgsqltest_la_rpath =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/libpgsqltest_la-pgsql_schema.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libpgsqltest_la_SOURCES)
+DIST_SOURCES = $(am__libpgsqltest_la_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\" \
+ -DDATABASE_WIPE_DIR=\"$(abs_top_builddir)/src/share/database/scripts\" \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libpgsqltest.la
+@HAVE_GTEST_TRUE@libpgsqltest_la_SOURCES = pgsql_schema.cc pgsql_schema.h
+@HAVE_GTEST_TRUE@libpgsqltest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libpgsqltest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(PGSQL_CPPFLAGS)
+@HAVE_GTEST_TRUE@libpgsqltest_la_LDFLAGS = $(AM_LDFLAGS) $(PGSQL_LIBS)
+@HAVE_GTEST_TRUE@libpgsqltest_la_LIBADD = $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/pgsql/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/pgsql/testutils/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libpgsqltest.la: $(libpgsqltest_la_OBJECTS) $(libpgsqltest_la_DEPENDENCIES) $(EXTRA_libpgsqltest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libpgsqltest_la_LINK) $(am_libpgsqltest_la_rpath) $(libpgsqltest_la_OBJECTS) $(libpgsqltest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpgsqltest_la-pgsql_schema.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libpgsqltest_la-pgsql_schema.lo: pgsql_schema.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsqltest_la_CPPFLAGS) $(CPPFLAGS) $(libpgsqltest_la_CXXFLAGS) $(CXXFLAGS) -MT libpgsqltest_la-pgsql_schema.lo -MD -MP -MF $(DEPDIR)/libpgsqltest_la-pgsql_schema.Tpo -c -o libpgsqltest_la-pgsql_schema.lo `test -f 'pgsql_schema.cc' || echo '$(srcdir)/'`pgsql_schema.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsqltest_la-pgsql_schema.Tpo $(DEPDIR)/libpgsqltest_la-pgsql_schema.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_schema.cc' object='libpgsqltest_la-pgsql_schema.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsqltest_la_CPPFLAGS) $(CPPFLAGS) $(libpgsqltest_la_CXXFLAGS) $(CXXFLAGS) -c -o libpgsqltest_la-pgsql_schema.lo `test -f 'pgsql_schema.cc' || echo '$(srcdir)/'`pgsql_schema.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libpgsqltest_la-pgsql_schema.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/libpgsqltest_la-pgsql_schema.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/pgsql/testutils/pgsql_schema.cc b/src/lib/pgsql/testutils/pgsql_schema.cc
new file mode 100644
index 0000000..1cf79a5
--- /dev/null
+++ b/src/lib/pgsql/testutils/pgsql_schema.cc
@@ -0,0 +1,103 @@
+// Copyright (C) 2016-2022 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 <string>
+#include <pgsql//pgsql_connection.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <exceptions/exceptions.h>
+
+#include <libpq-fe.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <stdlib.h>
+
+using namespace std;
+
+namespace isc {
+namespace db {
+namespace test {
+
+const char* PGSQL_VALID_TYPE = "type=postgresql";
+
+string
+validPgSQLConnectionString() {
+ return (connectionString(PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST,
+ VALID_USER, VALID_PASSWORD));
+}
+
+void destroyPgSQLSchema(bool show_err, bool force) {
+ // If force is true or wipePgSQLData() fails, destroy the schema.
+ if (force || (!softWipeEnabled()) || wipePgSQLData(show_err)) {
+ runPgSQLScript(DATABASE_SCRIPTS_DIR, "pgsql/dhcpdb_drop.pgsql", show_err);
+ }
+}
+
+void createPgSQLSchema(bool show_err, bool force) {
+ // If force is true or wipePgSQLData() fails, recreate the schema.
+ if (force || (!softWipeEnabled()) || wipePgSQLData(show_err)) {
+ destroyPgSQLSchema(show_err, true);
+ runPgSQLScript(DATABASE_SCRIPTS_DIR, "pgsql/dhcpdb_create.pgsql", show_err);
+ }
+}
+
+bool wipePgSQLData(bool show_err) {
+ std::ostringstream cmd;
+
+ // Pass psql the password via environment variable.
+ cmd << "export PGPASSWORD=keatest;";
+
+ // Add in the wipe shell script invocation.
+ cmd << " sh " << DATABASE_WIPE_DIR << "/pgsql/wipe_data.sh";
+
+ // Add expected schema version as the wipe script's first argument.
+ cmd << " " << PGSQL_SCHEMA_VERSION_MAJOR << "." << PGSQL_SCHEMA_VERSION_MINOR;
+
+ // Now add command line arguments for psql.
+ cmd << " --set ON_ERROR_STOP=1 -A -t -h localhost -q -U keatest -d keatest";
+
+ // Suppress error output.
+ if (!show_err) {
+ cmd << " 2>/dev/null ";
+ }
+
+ // Execute the command string.
+ int retval = ::system(cmd.str().c_str());
+ if (retval) {
+ std::cerr << "wipePgSQLData failed:[" << cmd.str() << "]" << std::endl;
+ }
+
+ return(retval);
+}
+
+void runPgSQLScript(const std::string& path, const std::string& script_name,
+ bool show_err) {
+ std::ostringstream cmd;
+
+ cmd << "export PGPASSWORD=keatest; cat ";
+ if (!path.empty()) {
+ cmd << " < " << path << "/";
+ }
+
+ cmd << script_name
+ << " | psql --set ON_ERROR_STOP=1 -A -t -h localhost -q -U keatest -d keatest";
+
+ if (!show_err) {
+ cmd << " 2>/dev/null ";
+ }
+
+ int retval = ::system(cmd.str().c_str());
+ if (retval) {
+ std::cerr << "runPgSQLSchema failed: " << cmd.str() << std::endl;
+ isc_throw(Unexpected, "runPgSQLSchema failed: " << cmd.str());
+ }
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/pgsql/testutils/pgsql_schema.h b/src/lib/pgsql/testutils/pgsql_schema.h
new file mode 100644
index 0000000..5919670
--- /dev/null
+++ b/src/lib/pgsql/testutils/pgsql_schema.h
@@ -0,0 +1,105 @@
+// Copyright (C) 2016-2022 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 TEST_PGSQL_SCHEMA_H
+#define TEST_PGSQL_SCHEMA_H
+
+#include <config.h>
+#include <database/testutils/schema.h>
+#include <string>
+
+namespace isc {
+namespace db {
+namespace test {
+
+extern const char* PGSQL_VALID_TYPE;
+
+/// Return valid connection string
+///
+/// @return valid PgSQL connection string.
+std::string validPgSQLConnectionString();
+
+/// @brief Clear the unit test database
+///
+/// In order to reduce test execution time, this function
+/// defaults to first attempting to delete transient data
+/// from the database by calling @c wipePgSQLData. If that
+/// function fails it will then attempt to destroy the database
+/// schema by running the SQL script:
+///
+/// <DATABASE_SCRIPTS_DIR>/pgsql/dhcpdb_drop.pgsql
+///
+/// The default behavior of wiping the data only may be overridden
+/// in one of two ways:
+///
+/// -# Setting the force parameter to true
+/// -# Defining the environment variable:
+/// KEA_TEST_DB_WIPE_DATA_ONLY="false"
+///
+/// @param show_err flag which governs whether or not stderr is suppressed.
+/// @param force if true, the function will skip deleting the data and
+/// destroy the schema.
+void destroyPgSQLSchema(bool show_err = false, bool force = false);
+
+/// @brief Create the unit test PgSQL Schema
+///
+/// Ensures the unit test database is empty and version-correct.
+/// Unless,the force parameter is true, it will first attempt
+/// to wipe the data from the database by calling @c wipePgSQLData.
+/// If this call succeeds the function returns, otherwise it will
+/// call @c destroyPgSQLSchema to forcibly remove the existing
+/// schema and then submits the SQL script:
+///
+/// <DATABASE_SCRIPTS_DIR>/pgsql/dhcpdb_create.pgsql
+///
+/// to the unit test PgSQL database.
+///
+/// The default behavior of wiping the data only may be overridden
+/// in one of two ways:
+///
+/// -# Setting the force parameter to true
+/// -# Defining the environment variable:
+/// KEA_TEST_DB_WIPE_DATA_ONLY="false"
+///
+/// @param show_err flag which governs whether or not stderr is suppressed.
+/// @param force flag when true, the function will recreate the database
+/// schema.
+void createPgSQLSchema(bool show_err = false, bool force = false);
+
+/// @brief Attempts to wipe data from the PgSQL unit test database
+///
+/// Runs the shell script
+///
+/// <DATABASE_WIPE_DIR>/pgsql/wipe_data.sh
+///
+/// This will fail if there is no schema, if the existing schema
+/// version is incorrect (i.e. does not match PGSQL_SCHEMA_VERSION_MAJOR
+/// and PGSQL_SCHEMA_VERSION_MINOR), or a SQL error occurs. Otherwise,
+/// the script is should delete all transient data, leaving intact
+/// reference tables.
+///
+/// @param show_err flag which governs whether or not stderr is suppressed.
+bool wipePgSQLData(bool show_err = false);
+
+/// @brief Run a PgSQL SQL script against the Postgresql unit test database
+///
+/// Submits the given SQL script to Postgresql via psql CLI. The output of
+/// stderr is suppressed unless the parameter, show_err is true. The is done
+/// to suppress warnings that might otherwise make test output needlessly
+/// noisy. An exception is thrown if the script fails to execute.
+///
+/// @param path - path (if not blank) of the script to execute
+/// @param script_name - file name of the path to execute
+/// @param show_err flag which governs whether or not stderr is suppressed.
+/// @throw Unexpected when the script returns an error.
+void runPgSQLScript(const std::string& path, const std::string& script_name,
+ bool show_err);
+
+};
+};
+};
+
+#endif