summaryrefslogtreecommitdiffstats
path: root/src/lib/pgsql/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/pgsql/tests')
-rw-r--r--src/lib/pgsql/tests/Makefile.am41
-rw-r--r--src/lib/pgsql/tests/Makefile.in1044
-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.cc565
-rw-r--r--src/lib/pgsql/tests/pgsql_exchange_unittest.cc1540
-rw-r--r--src/lib/pgsql/tests/run_unittests.cc20
7 files changed, 3520 insertions, 0 deletions
diff --git a/src/lib/pgsql/tests/Makefile.am b/src/lib/pgsql/tests/Makefile.am
new file mode 100644
index 0000000..ac24adb
--- /dev/null
+++ b/src/lib/pgsql/tests/Makefile.am
@@ -0,0 +1,41 @@
+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/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 += $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.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..3b8cd50
--- /dev/null
+++ b/src/lib/pgsql/tests/Makefile.in
@@ -0,0 +1,1044 @@
+# 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_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_sysrepo.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/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@ $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_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_SYSREPO = @HAVE_SYSREPO@
+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@
+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_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+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/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@ $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.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..b3b23e2
--- /dev/null
+++ b/src/lib/pgsql/tests/pgsql_connection_unittest.cc
@@ -0,0 +1,565 @@
+// 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 <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));
+}
+}; // 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..fe5dfdd
--- /dev/null
+++ b/src/lib/pgsql/tests/pgsql_exchange_unittest.cc
@@ -0,0 +1,1540 @@
+// 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 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);
+}