First Commit
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled

This commit is contained in:
2025-11-19 16:23:45 +07:00
commit dbdc5bcc4a
1791 changed files with 489451 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
if(NOT PostgreSQL_FOUND)
if(POLICY CMP0074)
cmake_policy(PUSH)
# CMP0074 is `OLD` by `cmake_minimum_required(VERSION 3.7)`,
# sets `NEW` to enable support CMake variable `PostgreSQL_ROOT`.
cmake_policy(SET CMP0074 NEW)
endif()
find_package(PostgreSQL REQUIRED)
if(POLICY CMP0074)
cmake_policy(POP)
endif()
endif()
# When setting up the include paths, mention the binary tree's include
# directory *before* the source tree's include directory. If the source tree
# happens to contain autoconf-generated config headers, we should still prefer
# the ones in the binary tree.
macro(library_target_setup tgt)
target_include_directories(${tgt}
PUBLIC
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE
${PostgreSQL_INCLUDE_DIRS}
)
target_link_libraries(${tgt} PRIVATE ${PostgreSQL_LIBRARIES})
if(WIN32)
target_link_libraries(${tgt} PUBLIC wsock32 ws2_32)
endif()
install(TARGETS ${tgt} EXPORT libpqxx-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
get_target_property(name ${tgt} NAME)
get_target_property(output_name ${tgt} OUTPUT_NAME)
if(NOT CMAKE_HOST_WIN32)
# Create library symlink
get_target_property(target_type ${tgt} TYPE)
if(target_type STREQUAL "SHARED_LIBRARY")
set(library_prefix ${CMAKE_SHARED_LIBRARY_PREFIX})
set(library_suffix ${CMAKE_SHARED_LIBRARY_SUFFIX})
elseif(target_type STREQUAL "STATIC_LIBRARY")
set(library_prefix ${CMAKE_STATIC_LIBRARY_PREFIX})
set(library_suffix ${CMAKE_STATIC_LIBRARY_SUFFIX})
endif()
list(APPEND noop_command "${CMAKE_COMMAND}" "-E" "true")
list(APPEND create_symlink_command "${CMAKE_COMMAND}" "-E" "create_symlink" "${library_prefix}${output_name}${library_suffix}" "${library_prefix}${name}${library_suffix}")
# `add_custom_command()` does nothing if the `OUTPUT_NAME` and `NAME`
# properties are equal, otherwise it creates library symlink.
add_custom_command(TARGET ${tgt} POST_BUILD
COMMAND "$<IF:$<STREQUAL:${name},${output_name}>,${noop_command},${create_symlink_command}>"
VERBATIM
COMMAND_EXPAND_LISTS
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${library_prefix}${name}${library_suffix}
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
endif()
endmacro()
file(GLOB CXX_SOURCES *.cxx)
add_library(pqxx ${CXX_SOURCES})
get_target_property(pqxx_target_type pqxx TYPE)
if(pqxx_target_type STREQUAL "SHARED_LIBRARY")
target_compile_definitions(pqxx PUBLIC PQXX_SHARED)
endif()
set_target_properties(
pqxx PROPERTIES
OUTPUT_NAME $<IF:$<PLATFORM_ID:Windows>,pqxx,pqxx-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}>
)
library_target_setup(pqxx)
# install pkg-config file
set(prefix ${CMAKE_INSTALL_PREFIX})
set(exec_prefix \${prefix})
set(libdir "\${prefix}/${CMAKE_INSTALL_LIBDIR}")
set(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
set(VERSION ${PROJECT_VERSION})
configure_file(${PROJECT_SOURCE_DIR}/libpqxx.pc.in ${PROJECT_BINARY_DIR}/libpqxx.pc)
install(FILES ${PROJECT_BINARY_DIR}/libpqxx.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
+44
View File
@@ -0,0 +1,44 @@
lib_LTLIBRARIES = libpqxx.la
libpqxx_la_SOURCES = \
array.cxx \
binarystring.cxx \
blob.cxx \
connection.cxx \
cursor.cxx \
encodings.cxx \
errorhandler.cxx \
except.cxx \
field.cxx \
largeobject.cxx \
notification.cxx \
params.cxx \
pipeline.cxx \
result.cxx \
robusttransaction.cxx \
sql_cursor.cxx \
strconv.cxx \
stream_from.cxx \
stream_to.cxx \
subtransaction.cxx \
time.cxx \
transaction.cxx \
transaction_base.cxx \
row.cxx \
util.cxx \
version.cxx \
wait.cxx
libpqxx_version = -release $(PQXX_ABI)
libpqxx_la_LDFLAGS = $(libpqxx_version) \
-rpath $(libdir) \
${POSTGRES_LIB}
AM_CPPFLAGS = \
-I$(top_srcdir)/include -I$(top_builddir)/include ${POSTGRES_INCLUDE}
# Override automatically generated list of default includes. It contains only
# unnecessary entries, and incorrectly mentions include/pqxx directly.
DEFAULT_INCLUDES=
MAINTAINERCLEANFILES=Makefile.in
+809
View File
@@ -0,0 +1,809 @@
# Makefile.in generated by automake 1.16.4 from Makefile.am.
# @configure_input@
# Copyright (C) 1994-2021 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
@SET_MAKE@
VPATH = @srcdir@
am__is_gnu_make = { \
if test -z '$(MAKELEVEL)'; then \
false; \
elif test -n '$(MAKE_HOST)'; then \
true; \
elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
true; \
else \
false; \
fi; \
}
am__make_running_with_option = \
case $${target_option-} in \
?) ;; \
*) echo "am__make_running_with_option: internal error: invalid" \
"target option '$${target_option-}' specified" >&2; \
exit 1;; \
esac; \
has_opt=no; \
sane_makeflags=$$MAKEFLAGS; \
if $(am__is_gnu_make); then \
sane_makeflags=$$MFLAGS; \
else \
case $$MAKEFLAGS in \
*\\[\ \ ]*) \
bs=\\; \
sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
| sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
esac; \
fi; \
skip_next=no; \
strip_trailopt () \
{ \
flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
}; \
for flg in $$sane_makeflags; do \
test $$skip_next = yes && { skip_next=no; continue; }; \
case $$flg in \
*=*|--*) continue;; \
-*I) strip_trailopt 'I'; skip_next=yes;; \
-*I?*) strip_trailopt 'I';; \
-*O) strip_trailopt 'O'; skip_next=yes;; \
-*O?*) strip_trailopt 'O';; \
-*l) strip_trailopt 'l'; skip_next=yes;; \
-*l?*) strip_trailopt 'l';; \
-[dEDm]) skip_next=yes;; \
-[JT]) skip_next=yes;; \
esac; \
case $$flg in \
*$$target_option*) has_opt=yes; break;; \
esac; \
done; \
test $$has_opt = yes
am__make_dryrun = (target_option=n; $(am__make_running_with_option))
am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
pkgdatadir = $(datadir)/@PACKAGE@
pkgincludedir = $(includedir)/@PACKAGE@
pkglibdir = $(libdir)/@PACKAGE@
pkglibexecdir = $(libexecdir)/@PACKAGE@
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
install_sh_DATA = $(install_sh) -c -m 644
install_sh_PROGRAM = $(install_sh) -c
install_sh_SCRIPT = $(install_sh) -c
INSTALL_HEADER = $(INSTALL_DATA)
transform = $(program_transform_name)
NORMAL_INSTALL = :
PRE_INSTALL = :
POST_INSTALL = :
NORMAL_UNINSTALL = :
PRE_UNINSTALL = :
POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
subdir = src
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/config/m4/libtool.m4 \
$(top_srcdir)/config/m4/ltoptions.m4 \
$(top_srcdir)/config/m4/ltsugar.m4 \
$(top_srcdir)/config/m4/ltversion.m4 \
$(top_srcdir)/config/m4/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 = $(SHELL) $(top_srcdir)/config/mkinstalldirs
CONFIG_HEADER = $(top_builddir)/include/pqxx/config.h
CONFIG_CLEAN_FILES =
CONFIG_CLEAN_VPATH_FILES =
am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
am__vpath_adj = case $$p in \
$(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
*) f=$$p;; \
esac;
am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
am__install_max = 40
am__nobase_strip_setup = \
srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
am__nobase_strip = \
for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
am__nobase_list = $(am__nobase_strip_setup); \
for p in $$list; do echo "$$p $$p"; done | \
sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
$(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
if (++n[$$2] == $(am__install_max)) \
{ print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
END { for (dir in files) print dir, files[dir] }'
am__base_list = \
sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
am__uninstall_files_from_dir = { \
test -z "$$files" \
|| { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
|| { echo " ( cd '$$dir' && rm -f" $$files ")"; \
$(am__cd) "$$dir" && rm -f $$files; }; \
}
am__installdirs = "$(DESTDIR)$(libdir)"
LTLIBRARIES = $(lib_LTLIBRARIES)
libpqxx_la_LIBADD =
am_libpqxx_la_OBJECTS = array.lo binarystring.lo blob.lo connection.lo \
cursor.lo encodings.lo errorhandler.lo except.lo field.lo \
largeobject.lo notification.lo params.lo pipeline.lo result.lo \
robusttransaction.lo sql_cursor.lo strconv.lo stream_from.lo \
stream_to.lo subtransaction.lo time.lo transaction.lo \
transaction_base.lo row.lo util.lo version.lo wait.lo
libpqxx_la_OBJECTS = $(am_libpqxx_la_OBJECTS)
AM_V_lt = $(am__v_lt_@AM_V@)
am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
am__v_lt_0 = --silent
am__v_lt_1 =
libpqxx_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
$(CXXFLAGS) $(libpqxx_la_LDFLAGS) $(LDFLAGS) -o $@
AM_V_P = $(am__v_P_@AM_V@)
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
am__v_P_0 = false
am__v_P_1 = :
AM_V_GEN = $(am__v_GEN_@AM_V@)
am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
am__v_GEN_0 = @echo " GEN " $@;
am__v_GEN_1 =
AM_V_at = $(am__v_at_@AM_V@)
am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
am__v_at_0 = @
am__v_at_1 =
depcomp = $(SHELL) $(top_srcdir)/config/depcomp
am__maybe_remake_depfiles = depfiles
am__depfiles_remade = ./$(DEPDIR)/array.Plo \
./$(DEPDIR)/binarystring.Plo ./$(DEPDIR)/blob.Plo \
./$(DEPDIR)/connection.Plo ./$(DEPDIR)/cursor.Plo \
./$(DEPDIR)/encodings.Plo ./$(DEPDIR)/errorhandler.Plo \
./$(DEPDIR)/except.Plo ./$(DEPDIR)/field.Plo \
./$(DEPDIR)/largeobject.Plo ./$(DEPDIR)/notification.Plo \
./$(DEPDIR)/params.Plo ./$(DEPDIR)/pipeline.Plo \
./$(DEPDIR)/result.Plo ./$(DEPDIR)/robusttransaction.Plo \
./$(DEPDIR)/row.Plo ./$(DEPDIR)/sql_cursor.Plo \
./$(DEPDIR)/strconv.Plo ./$(DEPDIR)/stream_from.Plo \
./$(DEPDIR)/stream_to.Plo ./$(DEPDIR)/subtransaction.Plo \
./$(DEPDIR)/time.Plo ./$(DEPDIR)/transaction.Plo \
./$(DEPDIR)/transaction_base.Plo ./$(DEPDIR)/util.Plo \
./$(DEPDIR)/version.Plo ./$(DEPDIR)/wait.Plo
am__mv = mv -f
CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
$(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
$(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
$(AM_CXXFLAGS) $(CXXFLAGS)
AM_V_CXX = $(am__v_CXX_@AM_V@)
am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
am__v_CXX_0 = @echo " CXX " $@;
am__v_CXX_1 =
CXXLD = $(CXX)
CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
$(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
am__v_CXXLD_0 = @echo " CXXLD " $@;
am__v_CXXLD_1 =
SOURCES = $(libpqxx_la_SOURCES)
DIST_SOURCES = $(libpqxx_la_SOURCES)
am__can_run_installinfo = \
case $$AM_UPDATE_INFO_DIR in \
n|no|NO) false;; \
*) (install-info --version) >/dev/null 2>&1;; \
esac
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)`
am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp \
$(top_srcdir)/config/mkinstalldirs
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
ACLOCAL = @ACLOCAL@
AMTAR = @AMTAR@
AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
AR = @AR@
AUTOCONF = @AUTOCONF@
AUTOHEADER = @AUTOHEADER@
AUTOMAKE = @AUTOMAKE@
AWK = @AWK@
CC = @CC@
CCDEPMODE = @CCDEPMODE@
CFLAGS = @CFLAGS@
CPP = @CPP@
CPPFLAGS = @CPPFLAGS@
CSCOPE = @CSCOPE@
CTAGS = @CTAGS@
CXX = @CXX@
CXXCPP = @CXXCPP@
CXXDEPMODE = @CXXDEPMODE@
CXXFLAGS = @CXXFLAGS@
CYGPATH_W = @CYGPATH_W@
DEFS = @DEFS@
DEPDIR = @DEPDIR@
DLLTOOL = @DLLTOOL@
DOXYGEN = @DOXYGEN@
DSYMUTIL = @DSYMUTIL@
DUMPBIN = @DUMPBIN@
ECHO_C = @ECHO_C@
ECHO_N = @ECHO_N@
ECHO_T = @ECHO_T@
EGREP = @EGREP@
ETAGS = @ETAGS@
EXEEXT = @EXEEXT@
FGREP = @FGREP@
GREP = @GREP@
HAVE_DOT = @HAVE_DOT@
INSTALL = @INSTALL@
INSTALL_DATA = @INSTALL_DATA@
INSTALL_PROGRAM = @INSTALL_PROGRAM@
INSTALL_SCRIPT = @INSTALL_SCRIPT@
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
LD = @LD@
LDFLAGS = @LDFLAGS@
LIBOBJS = @LIBOBJS@
LIBS = @LIBS@
LIBTOOL = @LIBTOOL@
LIPO = @LIPO@
LN_S = @LN_S@
LTLIBOBJS = @LTLIBOBJS@
LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
MAINT = @MAINT@
MAKEINFO = @MAKEINFO@
MANIFEST_TOOL = @MANIFEST_TOOL@
MKDIR = @MKDIR@
MKDIR_P = @MKDIR_P@
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@
PATH_SEPARATOR = @PATH_SEPARATOR@
PG_CONFIG = @PG_CONFIG@
PKG_CONFIG = @PKG_CONFIG@
POSTGRES_INCLUDE = @POSTGRES_INCLUDE@
PQXXVERSION = @PQXXVERSION@
PQXX_ABI = @PQXX_ABI@
RANLIB = @RANLIB@
SED = @SED@
SET_MAKE = @SET_MAKE@
SHELL = @SHELL@
STRIP = @STRIP@
VERSION = @VERSION@
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@
prefix = @prefix@
program_transform_name = @program_transform_name@
psdir = @psdir@
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@
with_postgres_lib = @with_postgres_lib@
lib_LTLIBRARIES = libpqxx.la
libpqxx_la_SOURCES = \
array.cxx \
binarystring.cxx \
blob.cxx \
connection.cxx \
cursor.cxx \
encodings.cxx \
errorhandler.cxx \
except.cxx \
field.cxx \
largeobject.cxx \
notification.cxx \
params.cxx \
pipeline.cxx \
result.cxx \
robusttransaction.cxx \
sql_cursor.cxx \
strconv.cxx \
stream_from.cxx \
stream_to.cxx \
subtransaction.cxx \
time.cxx \
transaction.cxx \
transaction_base.cxx \
row.cxx \
util.cxx \
version.cxx \
wait.cxx
libpqxx_version = -release $(PQXX_ABI)
libpqxx_la_LDFLAGS = $(libpqxx_version) \
-rpath $(libdir) \
${POSTGRES_LIB}
AM_CPPFLAGS = \
-I$(top_srcdir)/include -I$(top_builddir)/include ${POSTGRES_INCLUDE}
# Override automatically generated list of default includes. It contains only
# unnecessary entries, and incorrectly mentions include/pqxx directly.
DEFAULT_INCLUDES =
MAINTAINERCLEANFILES = Makefile.in
all: all-am
.SUFFIXES:
.SUFFIXES: .cxx .lo .o .obj
$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(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) --gnu src/Makefile'; \
$(am__cd) $(top_srcdir) && \
$(AUTOMAKE) --gnu src/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: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
$(am__aclocal_m4_deps):
install-libLTLIBRARIES: $(lib_LTLIBRARIES)
@$(NORMAL_INSTALL)
@list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
list2=; for p in $$list; do \
if test -f $$p; then \
list2="$$list2 $$p"; \
else :; fi; \
done; \
test -z "$$list2" || { \
echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
$(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
$(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
}
uninstall-libLTLIBRARIES:
@$(NORMAL_UNINSTALL)
@list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
for p in $$list; do \
$(am__strip_dir) \
echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
$(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
done
clean-libLTLIBRARIES:
-test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
@list='$(lib_LTLIBRARIES)'; \
locs=`for p in $$list; do echo $$p; done | \
sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
sort -u`; \
test -z "$$locs" || { \
echo rm -f $${locs}; \
rm -f $${locs}; \
}
libpqxx.la: $(libpqxx_la_OBJECTS) $(libpqxx_la_DEPENDENCIES) $(EXTRA_libpqxx_la_DEPENDENCIES)
$(AM_V_CXXLD)$(libpqxx_la_LINK) -rpath $(libdir) $(libpqxx_la_OBJECTS) $(libpqxx_la_LIBADD) $(LIBS)
mostlyclean-compile:
-rm -f *.$(OBJEXT)
distclean-compile:
-rm -f *.tab.c
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/array.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/binarystring.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blob.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connection.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cursor.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encodings.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/errorhandler.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/except.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/field.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/largeobject.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notification.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/params.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pipeline.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/result.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/robusttransaction.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/row.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sql_cursor.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strconv.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stream_from.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stream_to.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/subtransaction.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/time.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/transaction.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/transaction_base.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/util.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/version.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/wait.Plo@am__quote@ # am--include-marker
$(am__depfiles_remade):
@$(MKDIR_P) $(@D)
@echo '# dummy' >$@-t && $(am__mv) $@-t $@
am--depfiles: $(am__depfiles_remade)
.cxx.o:
@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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 $@ $<
.cxx.obj:
@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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) '$<'`
.cxx.lo:
@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
mostlyclean-libtool:
-rm -f *.lo
clean-libtool:
-rm -rf .libs _libs
ID: $(am__tagged_files)
$(am__define_uniq_tagged_files); mkid -fID $$unique
tags: tags-am
TAGS: tags
tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
set x; \
here=`pwd`; \
$(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-am
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-am
cscopelist-am: $(am__tagged_files)
list='$(am__tagged_files)'; \
case "$(srcdir)" in \
[\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
*) sdir=$(subdir)/$(srcdir) ;; \
esac; \
for i in $$list; do \
if test -f "$$i"; then \
echo "$(subdir)/$$i"; \
else \
echo "$$sdir/$$i"; \
fi; \
done >> $(top_builddir)/cscope.files
distclean-tags:
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
distdir: $(BUILT_SOURCES)
$(MAKE) $(AM_MAKEFLAGS) distdir-am
distdir-am: $(DISTFILES)
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
list='$(DISTFILES)'; \
dist_files=`for file in $$list; do echo $$file; done | \
sed -e "s|^$$srcdirstrip/||;t" \
-e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
case $$dist_files in \
*/*) $(MKDIR_P) `echo "$$dist_files" | \
sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
sort -u` ;; \
esac; \
for file in $$dist_files; do \
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
if test -d $$d/$$file; then \
dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
if test -d "$(distdir)/$$file"; then \
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
fi; \
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
fi; \
cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
else \
test -f "$(distdir)/$$file" \
|| cp -p $$d/$$file "$(distdir)/$$file" \
|| exit 1; \
fi; \
done
check-am: all-am
check: check-am
all-am: Makefile $(LTLIBRARIES)
installdirs:
for dir in "$(DESTDIR)$(libdir)"; do \
test -z "$$dir" || $(MKDIR_P) "$$dir"; \
done
install: install-am
install-exec: install-exec-am
install-data: install-data-am
uninstall: uninstall-am
install-am: all-am
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
installcheck: installcheck-am
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:
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."
-test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES)
clean: clean-am
clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
mostlyclean-am
distclean: distclean-am
-rm -f ./$(DEPDIR)/array.Plo
-rm -f ./$(DEPDIR)/binarystring.Plo
-rm -f ./$(DEPDIR)/blob.Plo
-rm -f ./$(DEPDIR)/connection.Plo
-rm -f ./$(DEPDIR)/cursor.Plo
-rm -f ./$(DEPDIR)/encodings.Plo
-rm -f ./$(DEPDIR)/errorhandler.Plo
-rm -f ./$(DEPDIR)/except.Plo
-rm -f ./$(DEPDIR)/field.Plo
-rm -f ./$(DEPDIR)/largeobject.Plo
-rm -f ./$(DEPDIR)/notification.Plo
-rm -f ./$(DEPDIR)/params.Plo
-rm -f ./$(DEPDIR)/pipeline.Plo
-rm -f ./$(DEPDIR)/result.Plo
-rm -f ./$(DEPDIR)/robusttransaction.Plo
-rm -f ./$(DEPDIR)/row.Plo
-rm -f ./$(DEPDIR)/sql_cursor.Plo
-rm -f ./$(DEPDIR)/strconv.Plo
-rm -f ./$(DEPDIR)/stream_from.Plo
-rm -f ./$(DEPDIR)/stream_to.Plo
-rm -f ./$(DEPDIR)/subtransaction.Plo
-rm -f ./$(DEPDIR)/time.Plo
-rm -f ./$(DEPDIR)/transaction.Plo
-rm -f ./$(DEPDIR)/transaction_base.Plo
-rm -f ./$(DEPDIR)/util.Plo
-rm -f ./$(DEPDIR)/version.Plo
-rm -f ./$(DEPDIR)/wait.Plo
-rm -f Makefile
distclean-am: clean-am distclean-compile distclean-generic \
distclean-tags
dvi: dvi-am
dvi-am:
html: html-am
html-am:
info: info-am
info-am:
install-data-am:
install-dvi: install-dvi-am
install-dvi-am:
install-exec-am: install-libLTLIBRARIES
install-html: install-html-am
install-html-am:
install-info: install-info-am
install-info-am:
install-man:
install-pdf: install-pdf-am
install-pdf-am:
install-ps: install-ps-am
install-ps-am:
installcheck-am:
maintainer-clean: maintainer-clean-am
-rm -f ./$(DEPDIR)/array.Plo
-rm -f ./$(DEPDIR)/binarystring.Plo
-rm -f ./$(DEPDIR)/blob.Plo
-rm -f ./$(DEPDIR)/connection.Plo
-rm -f ./$(DEPDIR)/cursor.Plo
-rm -f ./$(DEPDIR)/encodings.Plo
-rm -f ./$(DEPDIR)/errorhandler.Plo
-rm -f ./$(DEPDIR)/except.Plo
-rm -f ./$(DEPDIR)/field.Plo
-rm -f ./$(DEPDIR)/largeobject.Plo
-rm -f ./$(DEPDIR)/notification.Plo
-rm -f ./$(DEPDIR)/params.Plo
-rm -f ./$(DEPDIR)/pipeline.Plo
-rm -f ./$(DEPDIR)/result.Plo
-rm -f ./$(DEPDIR)/robusttransaction.Plo
-rm -f ./$(DEPDIR)/row.Plo
-rm -f ./$(DEPDIR)/sql_cursor.Plo
-rm -f ./$(DEPDIR)/strconv.Plo
-rm -f ./$(DEPDIR)/stream_from.Plo
-rm -f ./$(DEPDIR)/stream_to.Plo
-rm -f ./$(DEPDIR)/subtransaction.Plo
-rm -f ./$(DEPDIR)/time.Plo
-rm -f ./$(DEPDIR)/transaction.Plo
-rm -f ./$(DEPDIR)/transaction_base.Plo
-rm -f ./$(DEPDIR)/util.Plo
-rm -f ./$(DEPDIR)/version.Plo
-rm -f ./$(DEPDIR)/wait.Plo
-rm -f Makefile
maintainer-clean-am: distclean-am maintainer-clean-generic
mostlyclean: mostlyclean-am
mostlyclean-am: mostlyclean-compile mostlyclean-generic \
mostlyclean-libtool
pdf: pdf-am
pdf-am:
ps: ps-am
ps-am:
uninstall-am: uninstall-libLTLIBRARIES
.MAKE: install-am install-strip
.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
clean-generic clean-libLTLIBRARIES clean-libtool cscopelist-am \
ctags ctags-am distclean distclean-compile distclean-generic \
distclean-libtool distclean-tags distdir dvi dvi-am html \
html-am info info-am install install-am install-data \
install-data-am install-dvi install-dvi-am install-exec \
install-exec-am install-html install-html-am install-info \
install-info-am install-libLTLIBRARIES install-man install-pdf \
install-pdf-am install-ps install-ps-am install-strip \
installcheck installcheck-am installdirs maintainer-clean \
maintainer-clean-generic mostlyclean mostlyclean-compile \
mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES
.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:
+240
View File
@@ -0,0 +1,240 @@
/** Handling of SQL arrays.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <cassert>
#include <cstddef>
#include <cstring>
#include <utility>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/array.hxx"
#include "pqxx/except.hxx"
#include "pqxx/internal/array-composite.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/strconv.hxx"
#include "pqxx/util.hxx"
#include "pqxx/internal/header-post.hxx"
namespace pqxx
{
/// Scan to next glyph in the buffer. Assumes there is one.
[[nodiscard]] std::string::size_type
array_parser::scan_glyph(std::string::size_type pos) const
{
return m_scan(std::data(m_input), std::size(m_input), pos);
}
/// Scan to next glyph in a substring. Assumes there is one.
std::string::size_type array_parser::scan_glyph(
std::string::size_type pos, std::string::size_type end) const
{
return m_scan(std::data(m_input), end, pos);
}
/// Find the end of a single-quoted SQL string in an SQL array.
/** Call this while pointed at the opening quote.
*
* Returns the offset of the first character after the closing quote.
*/
std::string::size_type array_parser::scan_single_quoted_string() const
{
assert(m_input[m_pos] == '\'');
auto const sz{std::size(m_input)};
auto here{pqxx::internal::find_char<'\\', '\''>(m_scan, m_input, m_pos + 1)};
while (here < sz)
{
char const c{m_input[here]};
// Consume the slash or quote that we found.
++here;
if (c == '\'')
{
// Single quote.
// At end?
if (here >= sz)
return here;
// SQL escapes single quotes by doubling them. Terrible idea, but it's
// what we have. Inspect the next character to find out whether this
// is the closing quote, or an escaped one inside the string.
if (m_input[here] != '\'')
return here;
// Check against embedded "'" byte in a multichar byte. If we do have a
// multibyte char, then we're still out of the string.
if (scan_glyph(here, sz) > here + 1)
PQXX_UNLIKELY return here;
// We have a second quote. Consume it as well.
++here;
}
else
{
assert(c == '\\');
// Backslash escape. Skip ahead by one more character.
here = scan_glyph(here, sz);
}
// Race on to the next quote or backslash.
here = pqxx::internal::find_char<'\\', '\''>(m_scan, m_input, here);
}
throw argument_error{internal::concat("Null byte in SQL string: ", m_input)};
}
/// Parse a single-quoted SQL string: un-quote it and un-escape it.
std::string
array_parser::parse_single_quoted_string(std::string::size_type end) const
{
std::string output;
// Maximum output size is same as the input size, minus the opening and
// closing quotes. In the worst case, the real number could be half that.
// Usually it'll be a pretty close estimate.
output.reserve(end - m_pos - 2);
// XXX: find_char<'\\', '\''>().
for (auto here = m_pos + 1, next = scan_glyph(here, end); here < end - 1;
here = next, next = scan_glyph(here, end))
{
if (next - here == 1 and (m_input[here] == '\'' or m_input[here] == '\\'))
{
// Skip escape. (Performance-wise, we bet that these are relatively
// rare.)
PQXX_UNLIKELY
here = next;
next = scan_glyph(here, end);
}
output.append(std::data(m_input) + here, std::data(m_input) + next);
}
return output;
}
/// Find the end of a double-quoted SQL string in an SQL array.
std::string::size_type array_parser::scan_double_quoted_string() const
{
return pqxx::internal::scan_double_quoted_string(
std::data(m_input), std::size(m_input), m_pos, m_scan);
}
/// Parse a double-quoted SQL string: un-quote it and un-escape it.
std::string
array_parser::parse_double_quoted_string(std::string::size_type end) const
{
return pqxx::internal::parse_double_quoted_string(
std::data(m_input), end, m_pos, m_scan);
}
/// Find the end of an unquoted string in an SQL array.
/** Assumes UTF-8 or an ASCII-superset single-byte encoding.
*/
std::string::size_type array_parser::scan_unquoted_string() const
{
return pqxx::internal::scan_unquoted_string<',', ';', '}'>(
std::data(m_input), std::size(m_input), m_pos, m_scan);
}
/// Parse an unquoted SQL string.
/** Here, the special unquoted value NULL means a null value, not a string
* that happens to spell "NULL".
*/
std::string
array_parser::parse_unquoted_string(std::string::size_type end) const
{
return pqxx::internal::parse_unquoted_string(
std::data(m_input), end, m_pos, m_scan);
}
array_parser::array_parser(
std::string_view input, internal::encoding_group enc) :
m_input(input), m_scan(internal::get_glyph_scanner(enc))
{}
std::pair<array_parser::juncture, std::string> array_parser::get_next()
{
std::string value;
if (m_pos >= std::size(m_input))
return std::make_pair(juncture::done, value);
juncture found;
std::string::size_type end;
if (scan_glyph(m_pos) - m_pos > 1)
{
// Non-ASCII unquoted string.
end = scan_unquoted_string();
value = parse_unquoted_string(end);
found = juncture::string_value;
}
else
switch (m_input[m_pos])
{
case '\0': throw failure{"Unexpected zero byte in array."};
case '{':
found = juncture::row_start;
end = scan_glyph(m_pos);
break;
case '}':
found = juncture::row_end;
end = scan_glyph(m_pos);
break;
case '\'':
found = juncture::string_value;
end = scan_single_quoted_string();
value = parse_single_quoted_string(end);
break;
case '"':
found = juncture::string_value;
end = scan_double_quoted_string();
value = parse_double_quoted_string(end);
break;
default:
end = scan_unquoted_string();
value = parse_unquoted_string(end);
if (value == "NULL")
{
// In this one situation, as a special case, NULL means a null field,
// not a string that happens to spell "NULL".
value.clear();
found = juncture::null_value;
}
else
{
// The normal case: we just parsed an unquoted string. The value is
// what we need.
PQXX_LIKELY
found = juncture::string_value;
}
break;
}
// Skip a trailing field separator, if present.
if (end < std::size(m_input))
{
auto next{scan_glyph(end)};
if (next - end == 1 and (m_input[end] == ',' or m_input[end] == ';'))
PQXX_UNLIKELY
end = next;
}
m_pos = end;
return std::make_pair(found, value);
}
} // namespace pqxx
+108
View File
@@ -0,0 +1,108 @@
/** Implementation of bytea (binary string) conversions.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <new>
#include <stdexcept>
extern "C"
{
#include <libpq-fe.h>
}
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/binarystring.hxx"
#include "pqxx/field.hxx"
#include "pqxx/strconv.hxx"
#include "pqxx/internal/header-post.hxx"
namespace
{
/// Copy data to a heap-allocated buffer.
std::shared_ptr<unsigned char>
PQXX_COLD copy_to_buffer(void const *data, std::size_t len)
{
void *const output{malloc(len + 1)};
if (output == nullptr)
throw std::bad_alloc{};
static_cast<char *>(output)[len] = '\0';
memcpy(static_cast<char *>(output), data, len);
return {static_cast<unsigned char *>(output), std::free};
}
} // namespace
PQXX_COLD pqxx::binarystring::binarystring(field const &F)
{
unsigned char const *data{
reinterpret_cast<unsigned char const *>(F.c_str())};
m_buf =
std::shared_ptr<unsigned char>{PQunescapeBytea(data, &m_size), PQfreemem};
if (m_buf == nullptr)
throw std::bad_alloc{};
}
pqxx::binarystring::binarystring(std::string_view s) :
m_buf{copy_to_buffer(std::data(s), std::size(s))}, m_size{std::size(s)}
{}
pqxx::binarystring::binarystring(void const *binary_data, std::size_t len) :
m_buf{copy_to_buffer(binary_data, len)}, m_size{len}
{}
bool pqxx::binarystring::operator==(binarystring const &rhs) const noexcept
{
return (std::size(rhs) == size()) and
(std::memcmp(data(), std::data(rhs), size()) == 0);
}
pqxx::binarystring &
pqxx::binarystring::operator=(binarystring const &rhs) = default;
PQXX_COLD pqxx::binarystring::const_reference
pqxx::binarystring::at(size_type n) const
{
if (n >= m_size)
{
if (m_size == 0)
throw std::out_of_range{"Accessing empty binarystring"};
throw std::out_of_range{
"binarystring index out of range: " + to_string(n) +
" (should be below " + to_string(m_size) + ")"};
}
return data()[n];
}
PQXX_COLD void pqxx::binarystring::swap(binarystring &rhs)
{
m_buf.swap(rhs.m_buf);
// This part very obviously can't go wrong, so do it last
auto const s{m_size};
m_size = rhs.m_size;
rhs.m_size = s;
}
std::string pqxx::binarystring::str() const
{
return std::string{get(), m_size};
}
+337
View File
@@ -0,0 +1,337 @@
#include "pqxx-source.hxx"
#include <cerrno>
#include <stdexcept>
#include <utility>
#include <libpq-fe.h>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/blob.hxx"
#include "pqxx/except.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/gates/connection-largeobject.hxx"
#include "pqxx/internal/header-post.hxx"
namespace
{
constexpr int INV_WRITE{0x00020000}, INV_READ{0x00040000};
} // namespace
pqxx::internal::pq::PGconn *
pqxx::blob::raw_conn(pqxx::connection *conn) noexcept
{
pqxx::internal::gate::connection_largeobject gate{*conn};
return gate.raw_connection();
}
pqxx::internal::pq::PGconn *
pqxx::blob::raw_conn(pqxx::dbtransaction const &tx) noexcept
{
return raw_conn(&tx.conn());
}
std::string pqxx::blob::errmsg(connection const *conn)
{
pqxx::internal::gate::const_connection_largeobject gate{*conn};
return gate.error_message();
}
pqxx::blob pqxx::blob::open_internal(dbtransaction &tx, oid id, int mode)
{
auto &conn{tx.conn()};
int fd{lo_open(raw_conn(&conn), id, mode)};
if (fd == -1)
throw pqxx::failure{internal::concat(
"Could not open binary large object ", id, ": ", errmsg(&conn))};
return {conn, fd};
}
pqxx::oid pqxx::blob::create(dbtransaction &tx, oid id)
{
oid actual_id{lo_create(raw_conn(tx), id)};
if (actual_id == 0)
throw failure{internal::concat(
"Could not create binary large object: ", errmsg(&tx.conn()))};
return actual_id;
}
void pqxx::blob::remove(dbtransaction &tx, oid id)
{
if (id == 0)
throw usage_error{"Trying to delete binary large object without an ID."};
if (lo_unlink(raw_conn(tx), id) == -1)
throw failure{internal::concat(
"Could not delete large object ", id, ": ", errmsg(&tx.conn()))};
}
pqxx::blob pqxx::blob::open_r(dbtransaction &tx, oid id)
{
return open_internal(tx, id, INV_READ);
}
pqxx::blob pqxx::blob::open_w(dbtransaction &tx, oid id)
{
return open_internal(tx, id, INV_WRITE);
}
pqxx::blob pqxx::blob::open_rw(dbtransaction &tx, oid id)
{
return open_internal(tx, id, INV_READ | INV_WRITE);
}
pqxx::blob::blob(blob &&other) :
m_conn{std::exchange(other.m_conn, nullptr)},
m_fd{std::exchange(other.m_fd, -1)}
{}
pqxx::blob &pqxx::blob::operator=(blob &&other)
{
if (m_fd != -1)
lo_close(raw_conn(m_conn), m_fd);
m_conn = std::exchange(other.m_conn, nullptr);
m_fd = std::exchange(other.m_fd, -1);
return *this;
}
pqxx::blob::~blob()
{
try
{
close();
}
catch (std::exception const &e)
{
if (m_conn != nullptr)
PQXX_UNLIKELY
m_conn->process_notice(internal::concat(
"Failure while closing binary large object: ", e.what(), "\n"));
}
}
void pqxx::blob::close()
{
if (m_fd != -1)
{
lo_close(raw_conn(m_conn), m_fd);
m_fd = -1;
m_conn = nullptr;
}
}
std::size_t pqxx::blob::raw_read(std::byte buf[], std::size_t size)
{
if (m_conn == nullptr)
throw usage_error{"Attempt to read from a closed binary large object."};
if (size > chunk_limit)
throw range_error{
"Reads from a binary large object must be less than 2 GB at once."};
auto data{reinterpret_cast<char *>(buf)};
int received{lo_read(raw_conn(m_conn), m_fd, data, size)};
if (received < 0)
throw failure{
internal::concat("Could not read from binary large object: ", errmsg())};
return static_cast<std::size_t>(received);
}
std::size_t
pqxx::blob::read(std::basic_string<std::byte> &buf, std::size_t size)
{
buf.resize(size);
auto const received{raw_read(std::data(buf), size)};
buf.resize(received);
return static_cast<std::size_t>(received);
}
void pqxx::blob::raw_write(std::byte const buf[], std::size_t size)
{
if (m_conn == nullptr)
throw usage_error{"Attempt to write to a closed binary large object."};
if (size > chunk_limit)
throw range_error{
"Writes to a binary large object must be less than 2 GB at once."};
auto ptr{reinterpret_cast<char const *>(buf)};
int written{lo_write(raw_conn(m_conn), m_fd, ptr, size)};
if (written < 0)
throw failure{
internal::concat("Write to binary large object failed: ", errmsg())};
}
void pqxx::blob::resize(std::int64_t size)
{
if (m_conn == nullptr)
throw usage_error{"Attempt to resize a closed binary large object."};
if (lo_truncate64(raw_conn(m_conn), m_fd, size) < 0)
throw failure{
internal::concat("Binary large object truncation failed: ", errmsg())};
}
std::int64_t pqxx::blob::tell() const
{
if (m_conn == nullptr)
throw usage_error{"Attempt to tell() a closed binary large object."};
std::int64_t offset{lo_tell64(raw_conn(m_conn), m_fd)};
if (offset < 0)
throw failure{internal::concat(
"Error reading binary large object position: ", errmsg())};
return offset;
}
std::int64_t pqxx::blob::seek(std::int64_t offset, int whence)
{
if (m_conn == nullptr)
throw usage_error{"Attempt to seek() a closed binary large object."};
std::int64_t seek_result{lo_lseek64(raw_conn(m_conn), m_fd, offset, whence)};
if (seek_result < 0)
throw failure{internal::concat(
"Error during seek on binary large object: ", errmsg())};
return seek_result;
}
std::int64_t pqxx::blob::seek_abs(std::int64_t offset)
{
return this->seek(offset, SEEK_SET);
}
std::int64_t pqxx::blob::seek_rel(std::int64_t offset)
{
return this->seek(offset, SEEK_CUR);
}
std::int64_t pqxx::blob::seek_end(std::int64_t offset)
{
return this->seek(offset, SEEK_END);
}
pqxx::oid pqxx::blob::from_buf(
dbtransaction &tx, std::basic_string_view<std::byte> data, oid id)
{
oid actual_id{create(tx, id)};
try
{
open_w(tx, actual_id).write(data);
}
catch (std::exception const &)
{
try
{
remove(tx, id);
}
catch (std::exception const &e)
{
try
{
tx.conn().process_notice(internal::concat(
"Could not clean up partially created large object ", id, ": ",
e.what()));
}
catch (std::exception const &)
{}
}
throw;
}
return actual_id;
}
void pqxx::blob::append_from_buf(
dbtransaction &tx, std::basic_string_view<std::byte> data, oid id)
{
if (std::size(data) > chunk_limit)
throw range_error{
"Writes to a binary large object must be less than 2 GB at once."};
blob b{open_w(tx, id)};
b.seek_end();
b.write(data);
}
void pqxx::blob::to_buf(
dbtransaction &tx, oid id, std::basic_string<std::byte> &buf,
std::size_t max_size)
{
open_r(tx, id).read(buf, max_size);
}
std::size_t pqxx::blob::append_to_buf(
dbtransaction &tx, oid id, std::int64_t offset,
std::basic_string<std::byte> &buf, std::size_t append_max)
{
if (append_max > chunk_limit)
throw range_error{
"Reads from a binary large object must be less than 2 GB at once."};
auto b{open_r(tx, id)};
b.seek_abs(offset);
auto const org_size{std::size(buf)};
buf.resize(org_size + append_max);
try
{
auto here{reinterpret_cast<char *>(std::data(buf) + org_size)};
auto chunk{static_cast<std::size_t>(
lo_read(b.raw_conn(b.m_conn), b.m_fd, here, append_max))};
buf.resize(org_size + chunk);
return chunk;
}
catch (std::exception const &)
{
buf.resize(org_size);
throw;
}
}
pqxx::oid pqxx::blob::from_file(dbtransaction &tx, char const path[])
{
auto id{lo_import(raw_conn(tx), path)};
if (id == 0)
throw failure{internal::concat(
"Could not import '", path, "' as a binary large object: ", errmsg(tx))};
return id;
}
pqxx::oid pqxx::blob::from_file(dbtransaction &tx, char const path[], oid id)
{
auto actual_id{lo_import_with_oid(raw_conn(tx), path, id)};
if (actual_id == 0)
throw failure{internal::concat(
"Could not import '", path, "' as binary large object ", id, ": ",
errmsg(tx))};
return actual_id;
}
void pqxx::blob::to_file(dbtransaction &tx, oid id, char const path[])
{
if (lo_export(raw_conn(tx), id, path) < 0)
throw failure{internal::concat(
"Could not export binary large object ", id, " to file '", path,
"': ", errmsg(tx))};
}
File diff suppressed because it is too large Load Diff
+339
View File
@@ -0,0 +1,339 @@
/** Implementation of libpqxx STL-style cursor classes.
*
* These classes wrap SQL cursors in STL-like interfaces.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <iterator>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/cursor.hxx"
#include "pqxx/internal/gates/icursor_iterator-icursorstream.hxx"
#include "pqxx/internal/gates/icursorstream-icursor_iterator.hxx"
#include "pqxx/result.hxx"
#include "pqxx/strconv.hxx"
#include "pqxx/transaction.hxx"
#include "pqxx/internal/header-post.hxx"
pqxx::cursor_base::difference_type pqxx::cursor_base::all() noexcept
{
// Implemented out-of-line so we don't fall afoul of Visual Studio defining
// min() and max() macros, which turn this expression into malformed code:
return std::numeric_limits<int>::max() - 1;
}
pqxx::cursor_base::difference_type pqxx::cursor_base::backward_all() noexcept
{
// Implemented out-of-line so we don't fall afoul of Visual Studio defining
// min() and max() macros, which turn this expression into malformed code:
return std::numeric_limits<int>::min() + 1;
}
pqxx::cursor_base::cursor_base(
connection &context, std::string_view Name, bool embellish_name) :
m_name{embellish_name ? context.adorn_name(Name) : Name}
{}
pqxx::result::size_type
pqxx::internal::obtain_stateless_cursor_size(sql_cursor &cur)
{
if (cur.endpos() == -1)
cur.move(cursor_base::all());
return result::size_type(cur.endpos() - 1);
}
pqxx::result pqxx::internal::stateless_cursor_retrieve(
sql_cursor &cur, result::difference_type size,
result::difference_type begin_pos, result::difference_type end_pos)
{
if (begin_pos < 0 or begin_pos > size)
throw range_error{"Starting position out of range"};
if (end_pos < -1)
end_pos = -1;
else if (end_pos > size)
end_pos = size;
if (begin_pos == end_pos)
return cur.empty_result();
int const direction{((begin_pos < end_pos) ? 1 : -1)};
cur.move((begin_pos - direction) - (cur.pos() - 1));
return cur.fetch(end_pos - begin_pos);
}
pqxx::icursorstream::icursorstream(
transaction_base &context, std::string_view query, std::string_view basename,
difference_type sstride) :
m_cur{
context,
query,
basename,
cursor_base::forward_only,
cursor_base::read_only,
cursor_base::owned,
false},
m_stride{sstride},
m_realpos{0},
m_reqpos{0},
m_iterators{nullptr},
m_done{false}
{
set_stride(sstride);
}
pqxx::icursorstream::icursorstream(
transaction_base &context, field const &cname, difference_type sstride,
cursor_base::ownership_policy op) :
m_cur{context, cname.c_str(), op},
m_stride{sstride},
m_realpos{0},
m_reqpos{0},
m_iterators{nullptr},
m_done{false}
{
set_stride(sstride);
}
void pqxx::icursorstream::set_stride(difference_type stride) &
{
if (stride < 1)
throw argument_error{
internal::concat("Attempt to set cursor stride to ", stride)};
m_stride = stride;
}
pqxx::result pqxx::icursorstream::fetchblock()
{
result const r{m_cur.fetch(m_stride)};
m_realpos += std::size(r);
if (std::empty(r))
m_done = true;
return r;
}
pqxx::icursorstream &pqxx::icursorstream::ignore(std::streamsize n) &
{
auto offset{m_cur.move(difference_type(n))};
m_realpos += offset;
if (offset < n)
m_done = true;
return *this;
}
pqxx::icursorstream::size_type pqxx::icursorstream::forward(size_type n)
{
m_reqpos += difference_type(n) * m_stride;
return icursorstream::size_type(m_reqpos);
}
void pqxx::icursorstream::insert_iterator(icursor_iterator *i) noexcept
{
pqxx::internal::gate::icursor_iterator_icursorstream{*i}.set_next(
m_iterators);
if (m_iterators != nullptr)
pqxx::internal::gate::icursor_iterator_icursorstream{*m_iterators}
.set_prev(i);
m_iterators = i;
}
void pqxx::icursorstream::remove_iterator(icursor_iterator *i) const noexcept
{
pqxx::internal::gate::icursor_iterator_icursorstream igate{*i};
if (i == m_iterators)
{
m_iterators = igate.get_next();
if (m_iterators != nullptr)
pqxx::internal::gate::icursor_iterator_icursorstream{*m_iterators}
.set_prev(nullptr);
}
else
{
auto prev{igate.get_prev()}, next{igate.get_next()};
pqxx::internal::gate::icursor_iterator_icursorstream{*prev}.set_next(next);
if (next != nullptr)
pqxx::internal::gate::icursor_iterator_icursorstream{*next}.set_prev(
prev);
}
igate.set_prev(nullptr);
igate.set_next(nullptr);
}
void pqxx::icursorstream::service_iterators(difference_type topos)
{
if (topos < m_realpos)
return;
using todolist = std::multimap<difference_type, icursor_iterator *>;
todolist todo;
for (icursor_iterator *i{m_iterators}, *next; i != nullptr; i = next)
{
pqxx::internal::gate::icursor_iterator_icursorstream gate{*i};
auto const ipos{gate.pos()};
if (ipos >= m_realpos and ipos <= topos)
todo.insert(todolist::value_type(ipos, i));
next = gate.get_next();
}
auto const todo_end = std::end(todo);
for (auto i{std::begin(todo)}; i != todo_end;)
{
auto const readpos{i->first};
if (readpos > m_realpos)
ignore(readpos - m_realpos);
result const r{fetchblock()};
for (; i != todo_end and i->first == readpos; ++i)
pqxx::internal::gate::icursor_iterator_icursorstream{*i->second}.fill(r);
}
}
pqxx::icursor_iterator::icursor_iterator() noexcept : m_pos{0} {}
pqxx::icursor_iterator::icursor_iterator(istream_type &s) noexcept :
m_stream{&s},
m_pos{difference_type(
pqxx::internal::gate::icursorstream_icursor_iterator(s).forward(0))}
{
pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream}
.insert_iterator(this);
}
pqxx::icursor_iterator::icursor_iterator(icursor_iterator const &rhs) noexcept
:
m_stream{rhs.m_stream}, m_here{rhs.m_here}, m_pos{rhs.m_pos}
{
if (m_stream != nullptr)
pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream}
.insert_iterator(this);
}
pqxx::icursor_iterator::~icursor_iterator() noexcept
{
if (m_stream != nullptr)
pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream}
.remove_iterator(this);
}
pqxx::icursor_iterator pqxx::icursor_iterator::operator++(int)
{
icursor_iterator old{*this};
m_pos = difference_type(
pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream}.forward());
m_here.clear();
return old;
}
pqxx::icursor_iterator &pqxx::icursor_iterator::operator++()
{
m_pos = difference_type(
pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream}.forward());
m_here.clear();
return *this;
}
pqxx::icursor_iterator &pqxx::icursor_iterator::operator+=(difference_type n)
{
if (n <= 0)
{
PQXX_UNLIKELY
if (n == 0)
return *this;
throw argument_error{"Advancing icursor_iterator by negative offset."};
}
PQXX_LIKELY
m_pos = difference_type(
pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream}.forward(
icursorstream::size_type(n)));
m_here.clear();
return *this;
}
pqxx::icursor_iterator &
pqxx::icursor_iterator::operator=(icursor_iterator const &rhs) noexcept
{
if (rhs.m_stream == m_stream)
{
PQXX_UNLIKELY
m_here = rhs.m_here;
m_pos = rhs.m_pos;
}
else
{
PQXX_LIKELY
if (m_stream != nullptr)
pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream}
.remove_iterator(this);
m_here = rhs.m_here;
m_pos = rhs.m_pos;
m_stream = rhs.m_stream;
if (m_stream != nullptr)
pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream}
.insert_iterator(this);
}
return *this;
}
bool pqxx::icursor_iterator::operator==(icursor_iterator const &rhs) const
{
if (m_stream == rhs.m_stream)
return pos() == rhs.pos();
if (m_stream != nullptr and rhs.m_stream != nullptr)
return false;
refresh();
rhs.refresh();
return std::empty(m_here) and std::empty(rhs.m_here);
}
bool pqxx::icursor_iterator::operator<(icursor_iterator const &rhs) const
{
if (m_stream == rhs.m_stream)
return pos() < rhs.pos();
refresh();
rhs.refresh();
return not std::empty(m_here);
}
void pqxx::icursor_iterator::refresh() const
{
if (m_stream != nullptr)
pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream}
.service_iterators(pos());
}
void pqxx::icursor_iterator::fill(result const &r)
{
m_here = r;
}
+839
View File
@@ -0,0 +1,839 @@
/** Implementation of string encodings support
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <cstring>
#include <iomanip>
#include <map>
#include <sstream>
extern "C"
{
#include <libpq-fe.h>
}
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/except.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/encodings.hxx"
#include "pqxx/strconv.hxx"
#include "pqxx/internal/header-post.hxx"
namespace pqxx
{
PQXX_DECLARE_ENUM_CONVERSION(pqxx::internal::encoding_group);
}
// Internal helper functions
namespace
{
/// Extract byte from buffer, return as unsigned char.
constexpr PQXX_PURE unsigned char
get_byte(char const buffer[], std::size_t offset) noexcept
{
return static_cast<unsigned char>(buffer[offset]);
}
[[noreturn]] void throw_for_encoding_error(
char const *encoding_name, char const buffer[], std::size_t start,
std::size_t count)
{
std::stringstream s;
s << "Invalid byte sequence for encoding " << encoding_name << " at byte "
<< start << ": " << std::hex << std::setw(2) << std::setfill('0');
for (std::size_t i{0}; i < count; ++i)
{
s << "0x" << static_cast<unsigned int>(get_byte(buffer, start + i));
if (i + 1 < count)
s << " ";
}
throw pqxx::argument_error{s.str()};
}
/// Does value lie between bottom and top, inclusive?
constexpr PQXX_PURE bool
between_inc(unsigned char value, unsigned bottom, unsigned top)
{
return value >= bottom and value <= top;
}
/*
EUC-JP and EUC-JIS-2004 represent slightly different code points but iterate
the same:
* https://en.wikipedia.org/wiki/Extended_Unix_Code#EUC-JP
* http://x0213.org/codetable/index.en.html
*/
PQXX_PURE std::size_t next_seq_for_euc_jplike(
char const buffer[], std::size_t buffer_len, std::size_t start,
char const encoding_name[])
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error(encoding_name, buffer, start, 1);
auto const byte2{get_byte(buffer, start + 1)};
if (byte1 == 0x8e)
{
if (not between_inc(byte2, 0xa1, 0xfe))
PQXX_UNLIKELY
throw_for_encoding_error(encoding_name, buffer, start, 2);
return start + 2;
}
if (between_inc(byte1, 0xa1, 0xfe))
{
if (not between_inc(byte2, 0xa1, 0xfe))
PQXX_UNLIKELY
throw_for_encoding_error(encoding_name, buffer, start, 2);
return start + 2;
}
if (byte1 == 0x8f and start + 3 <= buffer_len)
{
auto const byte3{get_byte(buffer, start + 2)};
if (
not between_inc(byte2, 0xa1, 0xfe) or not between_inc(byte3, 0xa1, 0xfe))
PQXX_UNLIKELY
throw_for_encoding_error(encoding_name, buffer, start, 3);
return start + 3;
}
throw_for_encoding_error(encoding_name, buffer, start, 1);
}
/*
As far as I can tell, for the purposes of iterating the only difference between
SJIS and SJIS-2004 is increased range in the first byte of two-byte sequences
(0xEF increased to 0xFC). Officially, that is; apparently the version of SJIS
used by Postgres has the same range as SJIS-2004. They both have increased
range over the documented versions, not having the even/odd restriction for the
first byte in 2-byte sequences.
*/
// https://en.wikipedia.org/wiki/Shift_JIS#Shift_JIS_byte_map
// http://x0213.org/codetable/index.en.html
PQXX_PURE std::size_t next_seq_for_sjislike(
char const buffer[], std::size_t buffer_len, std::size_t start,
char const *encoding_name)
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80 or between_inc(byte1, 0xa1, 0xdf))
return start + 1;
if (
not between_inc(byte1, 0x81, 0x9f) and not between_inc(byte1, 0xe0, 0xfc))
PQXX_UNLIKELY
throw_for_encoding_error(encoding_name, buffer, start, 1);
if (start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error(encoding_name, buffer, start, buffer_len - start);
auto const byte2{get_byte(buffer, start + 1)};
if (byte2 == 0x7f)
PQXX_UNLIKELY
throw_for_encoding_error(encoding_name, buffer, start, 2);
if (between_inc(byte2, 0x40, 0x9e) or between_inc(byte2, 0x9f, 0xfc))
return start + 2;
PQXX_UNLIKELY
throw_for_encoding_error(encoding_name, buffer, start, 2);
}
} // namespace
// Implement template specializations first.
namespace pqxx::internal
{
template<encoding_group> struct glyph_scanner
{
PQXX_PURE static std::size_t
call(char const buffer[], std::size_t buffer_len, std::size_t start);
};
template<> struct glyph_scanner<encoding_group::MONOBYTE>
{
static PQXX_PURE constexpr std::size_t
call(char const /* buffer */[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
return std::string::npos;
else
return start + 1;
}
};
// https://en.wikipedia.org/wiki/Big5#Organization
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::BIG5>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (not between_inc(byte1, 0x81, 0xfe) or (start + 2 > buffer_len))
PQXX_UNLIKELY
throw_for_encoding_error("BIG5", buffer, start, 1);
auto const byte2{get_byte(buffer, start + 1)};
if (
not between_inc(byte2, 0x40, 0x7e) and not between_inc(byte2, 0xa1, 0xfe))
PQXX_UNLIKELY
throw_for_encoding_error("BIG5", buffer, start, 2);
return start + 2;
}
/*
The PostgreSQL documentation claims that the EUC_* encodings are 1-3 bytes
each, but other documents explain that the EUC sets can contain 1-(2,3,4) bytes
depending on the specific extension:
EUC_CN : 1-2
EUC_JP : 1-3
EUC_JIS_2004: 1-2
EUC_KR : 1-2
EUC_TW : 1-4
*/
// https://en.wikipedia.org/wiki/GB_2312#EUC-CN
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::EUC_CN>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (not between_inc(byte1, 0xa1, 0xf7) or start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("EUC_CN", buffer, start, 1);
auto const byte2{get_byte(buffer, start + 1)};
if (not between_inc(byte2, 0xa1, 0xfe))
PQXX_UNLIKELY
throw_for_encoding_error("EUC_CN", buffer, start, 2);
return start + 2;
}
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::EUC_JP>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
return next_seq_for_euc_jplike(buffer, buffer_len, start, "EUC_JP");
}
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::EUC_JIS_2004>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
return next_seq_for_euc_jplike(buffer, buffer_len, start, "EUC_JIS_2004");
}
// https://en.wikipedia.org/wiki/Extended_Unix_Code#EUC-KR
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::EUC_KR>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (not between_inc(byte1, 0xa1, 0xfe) or start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("EUC_KR", buffer, start, 1);
auto const byte2{get_byte(buffer, start + 1)};
if (not between_inc(byte2, 0xa1, 0xfe))
PQXX_UNLIKELY
throw_for_encoding_error("EUC_KR", buffer, start, 1);
return start + 2;
}
// https://en.wikipedia.org/wiki/Extended_Unix_Code#EUC-TW
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::EUC_TW>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
PQXX_UNLIKELY
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("EUC_KR", buffer, start, 1);
auto const byte2{get_byte(buffer, start + 1)};
if (between_inc(byte1, 0xa1, 0xfe))
{
if (not between_inc(byte2, 0xa1, 0xfe))
PQXX_UNLIKELY
throw_for_encoding_error("EUC_KR", buffer, start, 2);
return start + 2;
}
if (byte1 != 0x8e or start + 4 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("EUC_KR", buffer, start, 1);
if (
between_inc(byte2, 0xa1, 0xb0) and
between_inc(get_byte(buffer, start + 2), 0xa1, 0xfe) and
between_inc(get_byte(buffer, start + 3), 0xa1, 0xfe))
return start + 4;
PQXX_UNLIKELY
throw_for_encoding_error("EUC_KR", buffer, start, 4);
}
// https://en.wikipedia.org/wiki/GB_18030#Mapping
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::GB18030>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (byte1 == 0x80)
throw_for_encoding_error("GB18030", buffer, start, buffer_len - start);
if (start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("GB18030", buffer, start, buffer_len - start);
auto const byte2{get_byte(buffer, start + 1)};
if (between_inc(byte2, 0x40, 0xfe))
{
if (byte2 == 0x7f)
PQXX_UNLIKELY
throw_for_encoding_error("GB18030", buffer, start, 2);
return start + 2;
}
if (start + 4 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("GB18030", buffer, start, buffer_len - start);
if (
between_inc(byte2, 0x30, 0x39) and
between_inc(get_byte(buffer, start + 2), 0x81, 0xfe) and
between_inc(get_byte(buffer, start + 3), 0x30, 0x39))
return start + 4;
PQXX_UNLIKELY
throw_for_encoding_error("GB18030", buffer, start, 4);
}
// https://en.wikipedia.org/wiki/GBK_(character_encoding)#Encoding
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::GBK>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("GBK", buffer, start, 1);
auto const byte2{get_byte(buffer, start + 1)};
if (
(between_inc(byte1, 0xa1, 0xa9) and between_inc(byte2, 0xa1, 0xfe)) or
(between_inc(byte1, 0xb0, 0xf7) and between_inc(byte2, 0xa1, 0xfe)) or
(between_inc(byte1, 0x81, 0xa0) and between_inc(byte2, 0x40, 0xfe) and
byte2 != 0x7f) or
(between_inc(byte1, 0xaa, 0xfe) and between_inc(byte2, 0x40, 0xa0) and
byte2 != 0x7f) or
(between_inc(byte1, 0xa8, 0xa9) and between_inc(byte2, 0x40, 0xa0) and
byte2 != 0x7f) or
(between_inc(byte1, 0xaa, 0xaf) and between_inc(byte2, 0xa1, 0xfe)) or
(between_inc(byte1, 0xf8, 0xfe) and between_inc(byte2, 0xa1, 0xfe)) or
(between_inc(byte1, 0xa1, 0xa7) and between_inc(byte2, 0x40, 0xa0) and
byte2 != 0x7f))
return start + 2;
PQXX_UNLIKELY
throw_for_encoding_error("GBK", buffer, start, 2);
}
/*
The PostgreSQL documentation claims that the JOHAB encoding is 1-3 bytes, but
"CJKV Information Processing" describes it (actually just the Hangul portion)
as "three five-bit segments" that reside inside 16 bits (2 bytes).
CJKV Information Processing by Ken Lunde, pg. 269:
https://bit.ly/2BEOu5V
*/
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::JOHAB>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("JOHAB", buffer, start, 1);
auto const byte2{get_byte(buffer, start)};
if (
(between_inc(byte1, 0x84, 0xd3) and
(between_inc(byte2, 0x41, 0x7e) or between_inc(byte2, 0x81, 0xfe))) or
((between_inc(byte1, 0xd8, 0xde) or between_inc(byte1, 0xe0, 0xf9)) and
(between_inc(byte2, 0x31, 0x7e) or between_inc(byte2, 0x91, 0xfe))))
return start + 2;
PQXX_UNLIKELY
throw_for_encoding_error("JOHAB", buffer, start, 2);
}
/*
PostgreSQL's MULE_INTERNAL is the emacs rather than Xemacs implementation;
see the server/mb/pg_wchar.h PostgreSQL header file.
This is implemented according to the description in said header file, but I was
unable to get it to successfully iterate a MULE-encoded test CSV generated
using PostgreSQL 9.2.23. Use this at your own risk.
*/
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::MULE_INTERNAL>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("MULE_INTERNAL", buffer, start, 1);
auto const byte2{get_byte(buffer, start + 1)};
if (between_inc(byte1, 0x81, 0x8d) and byte2 >= 0xa0)
return start + 2;
if (start + 3 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("MULE_INTERNAL", buffer, start, 2);
if (
((byte1 == 0x9a and between_inc(byte2, 0xa0, 0xdf)) or
(byte1 == 0x9b and between_inc(byte2, 0xe0, 0xef)) or
(between_inc(byte1, 0x90, 0x99) and byte2 >= 0xa0)) and
(byte2 >= 0xa0))
return start + 3;
if (start + 4 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("MULE_INTERNAL", buffer, start, 3);
if (
((byte1 == 0x9c and between_inc(byte2, 0xf0, 0xf4)) or
(byte1 == 0x9d and between_inc(byte2, 0xf5, 0xfe))) and
get_byte(buffer, start + 2) >= 0xa0 and
get_byte(buffer, start + 4) >= 0xa0)
return start + 4;
PQXX_UNLIKELY
throw_for_encoding_error("MULE_INTERNAL", buffer, start, 4);
}
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::SJIS>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
return next_seq_for_sjislike(buffer, buffer_len, start, "SJIS");
}
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::SHIFT_JIS_2004>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
return next_seq_for_sjislike(buffer, buffer_len, start, "SHIFT_JIS_2004");
}
// https://en.wikipedia.org/wiki/Unified_Hangul_Code
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::UHC>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("UHC", buffer, start, buffer_len - start);
auto const byte2{get_byte(buffer, start + 1)};
if (between_inc(byte1, 0x80, 0xc6))
{
if (
between_inc(byte2, 0x41, 0x5a) or between_inc(byte2, 0x61, 0x7a) or
between_inc(byte2, 0x80, 0xfe))
return start + 2;
PQXX_UNLIKELY
throw_for_encoding_error("UHC", buffer, start, 2);
}
if (between_inc(byte1, 0xa1, 0xfe))
{
if (not between_inc(byte2, 0xa1, 0xfe))
PQXX_UNLIKELY
throw_for_encoding_error("UHC", buffer, start, 2);
return start + 2;
}
throw_for_encoding_error("UHC", buffer, start, 1);
}
// https://en.wikipedia.org/wiki/UTF-8#Description
template<>
PQXX_PURE std::size_t glyph_scanner<encoding_group::UTF8>::call(
char const buffer[], std::size_t buffer_len, std::size_t start)
{
if (start >= buffer_len)
return std::string::npos;
auto const byte1{get_byte(buffer, start)};
if (byte1 < 0x80)
return start + 1;
if (start + 2 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("UTF8", buffer, start, buffer_len - start);
auto const byte2{get_byte(buffer, start + 1)};
if (between_inc(byte1, 0xc0, 0xdf))
{
if (not between_inc(byte2, 0x80, 0xbf))
PQXX_UNLIKELY
throw_for_encoding_error("UTF8", buffer, start, 2);
return start + 2;
}
if (start + 3 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("UTF8", buffer, start, buffer_len - start);
auto const byte3{get_byte(buffer, start + 2)};
if (between_inc(byte1, 0xe0, 0xef))
{
if (between_inc(byte2, 0x80, 0xbf) and between_inc(byte3, 0x80, 0xbf))
return start + 3;
PQXX_UNLIKELY
throw_for_encoding_error("UTF8", buffer, start, 3);
}
if (start + 4 > buffer_len)
PQXX_UNLIKELY
throw_for_encoding_error("UTF8", buffer, start, buffer_len - start);
if (between_inc(byte1, 0xf0, 0xf7))
{
if (
between_inc(byte2, 0x80, 0xbf) and between_inc(byte3, 0x80, 0xbf) and
between_inc(get_byte(buffer, start + 3), 0x80, 0xbf))
return start + 4;
PQXX_UNLIKELY
throw_for_encoding_error("UTF8", buffer, start, 4);
}
PQXX_UNLIKELY
throw_for_encoding_error("UTF8", buffer, start, 1);
}
PQXX_PURE char const *name_encoding(int encoding_id)
{
return pg_encoding_to_char(encoding_id);
}
encoding_group enc_group(int libpq_enc_id)
{
return enc_group(name_encoding(libpq_enc_id));
}
encoding_group enc_group(std::string_view encoding_name)
{
struct mapping
{
std::string_view const name;
encoding_group const group;
constexpr mapping(std::string_view n, encoding_group g) : name{n}, group{g}
{}
constexpr bool operator<(mapping const &rhs) const
{
return name < rhs.name;
}
};
// C++20: Once compilers are ready, go full constexpr, leave to the compiler.
auto const sz{std::size(encoding_name)};
if (sz > 0u)
switch (encoding_name[0])
{
case 'B':
if (encoding_name == "BIG5"sv)
return encoding_group::BIG5;
PQXX_UNLIKELY
break;
case 'E':
// C++20: Use string_view::starts_with().
if ((sz >= 6u) and (encoding_name.substr(0, 4) == "EUC_"sv))
{
auto const subtype{encoding_name.substr(4)};
static constexpr std::array<mapping, 5> subtypes{
mapping{"CN"sv, encoding_group::EUC_CN},
mapping{"JIS_2004"sv, encoding_group::EUC_JIS_2004},
mapping{"JP"sv, encoding_group::EUC_JP},
mapping{"KR"sv, encoding_group::EUC_KR},
mapping{"TW"sv, encoding_group::EUC_TW},
};
for (auto const &m : subtypes)
if (m.name == subtype)
return m.group;
}
PQXX_UNLIKELY
break;
case 'G':
if (encoding_name == "GB18030"sv)
return encoding_group::GB18030;
else if (encoding_name == "GBK"sv)
return encoding_group::GBK;
PQXX_UNLIKELY
break;
case 'I':
// We know iso-8859-X, where 5 <= X < 9. They're all monobyte encodings.
if ((sz == 10) and (encoding_name.substr(0, 9) == "ISO_8859_"sv))
{
char const subtype{encoding_name[9]};
if (('5' <= subtype) and (subtype < '9'))
return encoding_group::MONOBYTE;
}
PQXX_UNLIKELY
break;
case 'J':
if (encoding_name == "JOHAB"sv)
return encoding_group::JOHAB;
PQXX_UNLIKELY
break;
case 'K':
if ((encoding_name == "KOI8R"sv) or (encoding_name == "KOI8U"sv))
return encoding_group::MONOBYTE;
PQXX_UNLIKELY
break;
case 'L':
// We know LATIN1 through LATIN10.
if (encoding_name.substr(0, 5) == "LATIN"sv)
{
auto const subtype{encoding_name.substr(5)};
if (subtype.size() == 1)
{
char const n{subtype[0]};
if (('1' <= n) and (n <= '9'))
return encoding_group::MONOBYTE;
}
else if (subtype == "10"sv)
{
return encoding_group::MONOBYTE;
}
}
PQXX_UNLIKELY
break;
case 'M':
if (encoding_name == "MULE_INTERNAL"sv)
return encoding_group::MULE_INTERNAL;
PQXX_UNLIKELY
break;
case 'S':
if (encoding_name == "SHIFT_JIS_2004"sv)
return encoding_group::SHIFT_JIS_2004;
else if (encoding_name == "SJIS"sv)
return encoding_group::SJIS;
else if (encoding_name == "SQL_ASCII"sv)
return encoding_group::MONOBYTE;
PQXX_UNLIKELY
break;
case 'U':
if (encoding_name == "UHC"sv)
return encoding_group::UHC;
else if (encoding_name == "UTF8"sv)
return encoding_group::UTF8;
PQXX_UNLIKELY
break;
case 'W':
if (encoding_name.substr(0, 3) == "WIN"sv)
{
auto const subtype{encoding_name.substr(3)};
static constexpr std::array<std::string_view, 11u> subtypes{
"866"sv, "874"sv, "1250"sv, "1251"sv, "1252"sv, "1253"sv,
"1254"sv, "1255"sv, "1256"sv, "1257"sv, "1258"sv,
};
for (auto const n : subtypes)
if (n == subtype)
return encoding_group::MONOBYTE;
}
PQXX_UNLIKELY
break;
default: PQXX_UNLIKELY break;
}
PQXX_UNLIKELY
throw std::invalid_argument{
internal::concat("Unrecognized encoding: '", encoding_name, "'.")};
}
/// Look up instantiation @c T<enc>::call at runtime.
/** Here, "T" is a struct template with a static member function "call", whose
* type is "F".
*
* The return value is a pointer to the "call" member function for the
* instantiation of T for encoding group enc.
*/
template<template<encoding_group> class T, typename F>
constexpr inline F *for_encoding(encoding_group enc)
{
#define CASE_GROUP(ENC) \
case encoding_group::ENC: return T<encoding_group::ENC>::call
switch (enc)
{
PQXX_LIKELY CASE_GROUP(MONOBYTE);
CASE_GROUP(BIG5);
CASE_GROUP(EUC_CN);
CASE_GROUP(EUC_JP);
CASE_GROUP(EUC_JIS_2004);
CASE_GROUP(EUC_KR);
CASE_GROUP(EUC_TW);
CASE_GROUP(GB18030);
CASE_GROUP(GBK);
CASE_GROUP(JOHAB);
CASE_GROUP(MULE_INTERNAL);
CASE_GROUP(SJIS);
CASE_GROUP(SHIFT_JIS_2004);
CASE_GROUP(UHC);
PQXX_LIKELY CASE_GROUP(UTF8);
}
PQXX_UNLIKELY
throw pqxx::usage_error{
internal::concat("Unsupported encoding group code ", enc, ".")};
#undef CASE_GROUP
}
PQXX_PURE glyph_scanner_func *get_glyph_scanner(encoding_group enc)
{
return for_encoding<glyph_scanner, glyph_scanner_func>(enc);
}
template<encoding_group E> struct char_finder
{
constexpr static PQXX_PURE std::size_t
call(std::string_view haystack, char needle, std::size_t start)
{
auto const buffer{std::data(haystack)};
auto const size{std::size(haystack)};
for (auto here{start}; here + 1 <= size;
here = glyph_scanner<E>::call(buffer, size, here))
{
if (haystack[here] == needle)
return here;
}
return std::string::npos;
}
};
template<encoding_group E> struct string_finder
{
PQXX_PURE constexpr static PQXX_PURE std::size_t
call(std::string_view haystack, std::string_view needle, std::size_t start)
{
auto const buffer{std::data(haystack)};
auto const size{std::size(haystack)};
auto const needle_size{std::size(needle)};
for (auto here{start}; here + needle_size <= size;
here = glyph_scanner<E>::call(buffer, size, here))
{
if (std::memcmp(buffer + here, std::data(needle), needle_size) == 0)
return here;
}
return std::string::npos;
}
};
#undef DISPATCH_ENCODING_OPERATION
} // namespace pqxx::internal
+43
View File
@@ -0,0 +1,43 @@
/** Implementation of pqxx::errorhandler and helpers.
*
* pqxx::errorhandler allows programs to receive errors and warnings.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/connection.hxx"
#include "pqxx/errorhandler.hxx"
#include "pqxx/internal/gates/connection-errorhandler.hxx"
#include "pqxx/internal/header-post.hxx"
pqxx::errorhandler::errorhandler(connection &conn) : m_home{&conn}
{
pqxx::internal::gate::connection_errorhandler{*m_home}.register_errorhandler(
this);
}
pqxx::errorhandler::~errorhandler()
{
unregister();
}
void pqxx::errorhandler::unregister() noexcept
{
if (m_home != nullptr)
{
pqxx::internal::gate::connection_errorhandler connection_gate{*m_home};
m_home = nullptr;
connection_gate.unregister_errorhandler(this);
}
}
+126
View File
@@ -0,0 +1,126 @@
/** Implementation of libpqxx exception classes.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/except.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/header-post.hxx"
pqxx::failure::failure(std::string const &whatarg) :
std::runtime_error{whatarg}
{}
pqxx::broken_connection::broken_connection() :
failure{"Connection to database failed."}
{}
pqxx::broken_connection::broken_connection(std::string const &whatarg) :
failure{whatarg}
{}
pqxx::variable_set_to_null::variable_set_to_null() :
variable_set_to_null{
"Attempt to set a variable to null. This is not allowed."}
{}
pqxx::variable_set_to_null::variable_set_to_null(std::string const &whatarg) :
failure{whatarg}
{}
pqxx::sql_error::sql_error(
std::string const &whatarg, std::string const &Q, char const sqlstate[]) :
failure{whatarg}, m_query{Q}, m_sqlstate{sqlstate ? sqlstate : ""}
{}
pqxx::sql_error::~sql_error() noexcept = default;
PQXX_PURE std::string const &pqxx::sql_error::query() const noexcept
{
return m_query;
}
PQXX_PURE std::string const &pqxx::sql_error::sqlstate() const noexcept
{
return m_sqlstate;
}
pqxx::in_doubt_error::in_doubt_error(std::string const &whatarg) :
failure{whatarg}
{}
pqxx::transaction_rollback::transaction_rollback(
std::string const &whatarg, std::string const &q, char const sqlstate[]) :
sql_error{whatarg, q, sqlstate}
{}
pqxx::serialization_failure::serialization_failure(
std::string const &whatarg, std::string const &q, char const sqlstate[]) :
transaction_rollback{whatarg, q, sqlstate}
{}
pqxx::statement_completion_unknown::statement_completion_unknown(
std::string const &whatarg, std::string const &q, char const sqlstate[]) :
transaction_rollback{whatarg, q, sqlstate}
{}
pqxx::deadlock_detected::deadlock_detected(
std::string const &whatarg, std::string const &q, char const sqlstate[]) :
transaction_rollback{whatarg, q, sqlstate}
{}
pqxx::internal_error::internal_error(std::string const &whatarg) :
std::logic_error{internal::concat("libpqxx internal error: ", whatarg)}
{}
pqxx::usage_error::usage_error(std::string const &whatarg) :
std::logic_error{whatarg}
{}
pqxx::argument_error::argument_error(std::string const &whatarg) :
invalid_argument{whatarg}
{}
pqxx::conversion_error::conversion_error(std::string const &whatarg) :
domain_error{whatarg}
{}
pqxx::conversion_overrun::conversion_overrun(std::string const &whatarg) :
conversion_error{whatarg}
{}
pqxx::range_error::range_error(std::string const &whatarg) :
out_of_range{whatarg}
{}
pqxx::blob_already_exists::blob_already_exists(std::string const &whatarg) :
failure{whatarg}
{}
+80
View File
@@ -0,0 +1,80 @@
/** Implementation of the pqxx::field class.
*
* pqxx::field refers to a field in a query result.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <cstring>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/field.hxx"
#include "pqxx/internal/libpq-forward.hxx"
#include "pqxx/result.hxx"
#include "pqxx/row.hxx"
#include "pqxx/internal/header-post.hxx"
pqxx::field::field(pqxx::row const &r, pqxx::row::size_type c) noexcept :
m_col{c}, m_home{r.m_result}, m_row{r.m_index}
{}
bool PQXX_COLD pqxx::field::operator==(field const &rhs) const
{
if (is_null() and rhs.is_null())
return true;
if (is_null() != rhs.is_null())
return false;
auto const s{size()};
return (s == std::size(rhs)) and (std::memcmp(c_str(), rhs.c_str(), s) == 0);
}
char const *pqxx::field::name() const &
{
return home().column_name(col());
}
pqxx::oid pqxx::field::type() const
{
return home().column_type(col());
}
pqxx::oid pqxx::field::table() const
{
return home().column_table(col());
}
pqxx::row::size_type pqxx::field::table_column() const
{
return home().table_column(col());
}
char const *pqxx::field::c_str() const &
{
return home().get_value(idx(), col());
}
bool pqxx::field::is_null() const noexcept
{
return home().get_is_null(idx(), col());
}
pqxx::field::size_type pqxx::field::size() const noexcept
{
return home().get_length(idx(), col());
}
+322
View File
@@ -0,0 +1,322 @@
/** Implementation of the Large Objects interface.
*
* Allows direct access to large objects, as well as though I/O streams.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <algorithm>
#include <cerrno>
#include <stdexcept>
extern "C"
{
#include <libpq-fe.h>
}
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/connection.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/gates/connection-largeobject.hxx"
#include "pqxx/largeobject.hxx"
#include "pqxx/internal/header-post.hxx"
#include "pqxx/internal/ignore-deprecated-pre.hxx"
namespace
{
constexpr inline int PQXX_COLD std_mode_to_pq_mode(std::ios::openmode mode)
{
/// Mode bits, copied from libpq-fs.h so that we no longer need that header.
constexpr int INV_WRITE{0x00020000}, INV_READ{0x00040000};
return ((mode & std::ios::in) ? INV_READ : 0) |
((mode & std::ios::out) ? INV_WRITE : 0);
}
constexpr int PQXX_COLD std_dir_to_pq_dir(std::ios::seekdir dir) noexcept
{
if constexpr (
static_cast<int>(std::ios::beg) == int(SEEK_SET) and
static_cast<int>(std::ios::cur) == int(SEEK_CUR) and
static_cast<int>(std::ios::end) == int(SEEK_END))
{
// Easy optimisation: they're the same constants. This is actually the
// case for the gcc I'm using.
return dir;
}
else
switch (dir)
{
case std::ios::beg: return SEEK_SET; break;
case std::ios::cur: return SEEK_CUR; break;
case std::ios::end: return SEEK_END; break;
// Shouldn't happen, but may silence compiler warning.
default: return dir; break;
}
}
} // namespace
PQXX_COLD pqxx::largeobject::largeobject(dbtransaction &t)
{
// (Mode is ignored as of postgres 8.1.)
m_id = lo_creat(raw_connection(t), 0);
if (m_id == oid_none)
{
int const err{errno};
if (err == ENOMEM)
throw std::bad_alloc{};
throw failure{internal::concat(
"Could not create large object: ", reason(t.conn(), err))};
}
}
PQXX_COLD
pqxx::largeobject::largeobject(dbtransaction &t, std::string_view file)
{
m_id = lo_import(raw_connection(t), std::data(file));
if (m_id == oid_none)
{
int const err{errno};
if (err == ENOMEM)
throw std::bad_alloc{};
throw failure{internal::concat(
"Could not import file '", file,
"' to large object: ", reason(t.conn(), err))};
}
}
PQXX_COLD pqxx::largeobject::largeobject(largeobjectaccess const &o) noexcept :
m_id{o.id()}
{}
void PQXX_COLD
pqxx::largeobject::to_file(dbtransaction &t, std::string_view file) const
{
if (id() == oid_none)
throw usage_error{"No object selected."};
if (lo_export(raw_connection(t), id(), std::data(file)) == -1)
{
int const err{errno};
if (err == ENOMEM)
throw std::bad_alloc{};
throw failure{internal::concat(
"Could not export large object ", m_id, " to file '", file,
"': ", reason(t.conn(), err))};
}
}
void PQXX_COLD pqxx::largeobject::remove(dbtransaction &t) const
{
if (id() == oid_none)
throw usage_error{"No object selected."};
if (lo_unlink(raw_connection(t), id()) == -1)
{
int const err{errno};
if (err == ENOMEM)
throw std::bad_alloc{};
throw failure{internal::concat(
"Could not delete large object ", m_id, ": ", reason(t.conn(), err))};
}
}
pqxx::internal::pq::PGconn *PQXX_COLD
pqxx::largeobject::raw_connection(dbtransaction const &t)
{
return pqxx::internal::gate::connection_largeobject{t.conn()}
.raw_connection();
}
std::string PQXX_COLD
pqxx::largeobject::reason(connection const &c, int err) const
{
if (err == ENOMEM)
return "Out of memory";
return pqxx::internal::gate::const_connection_largeobject{c}.error_message();
}
PQXX_COLD
pqxx::largeobjectaccess::largeobjectaccess(dbtransaction &t, openmode mode) :
largeobject{t}, m_trans{t}
{
open(mode);
}
PQXX_COLD pqxx::largeobjectaccess::largeobjectaccess(
dbtransaction &t, oid o, openmode mode) :
largeobject{o}, m_trans{t}
{
open(mode);
}
PQXX_COLD pqxx::largeobjectaccess::largeobjectaccess(
dbtransaction &t, largeobject o, openmode mode) :
largeobject{o}, m_trans{t}
{
open(mode);
}
PQXX_COLD pqxx::largeobjectaccess::largeobjectaccess(
dbtransaction &t, std::string_view file, openmode mode) :
largeobject{t, file}, m_trans{t}
{
open(mode);
}
pqxx::largeobjectaccess::size_type PQXX_COLD
pqxx::largeobjectaccess::seek(size_type dest, seekdir dir)
{
auto const res{cseek(dest, dir)};
if (res == -1)
{
int const err{errno};
if (err == ENOMEM)
throw std::bad_alloc{};
if (id() == oid_none)
throw usage_error{"No object selected."};
throw failure{
internal::concat("Error seeking in large object: ", reason(err))};
}
return res;
}
pqxx::largeobjectaccess::pos_type PQXX_COLD
pqxx::largeobjectaccess::cseek(off_type dest, seekdir dir) noexcept
{
return lo_lseek64(raw_connection(), m_fd, dest, std_dir_to_pq_dir(dir));
}
pqxx::largeobjectaccess::pos_type PQXX_COLD
pqxx::largeobjectaccess::cwrite(char const buf[], std::size_t len) noexcept
{
return std::max(lo_write(raw_connection(), m_fd, buf, len), -1);
}
pqxx::largeobjectaccess::pos_type PQXX_COLD
pqxx::largeobjectaccess::cread(char buf[], std::size_t len) noexcept
{
return std::max(lo_read(raw_connection(), m_fd, buf, len), -1);
}
pqxx::largeobjectaccess::pos_type PQXX_COLD
pqxx::largeobjectaccess::ctell() const noexcept
{
return lo_tell64(raw_connection(), m_fd);
}
void PQXX_COLD
pqxx::largeobjectaccess::write(char const buf[], std::size_t len)
{
if (id() == oid_none)
throw usage_error{"No object selected."};
if (auto const bytes{cwrite(buf, len)}; internal::cmp_less(bytes, len))
{
int const err{errno};
if (err == ENOMEM)
throw std::bad_alloc{};
if (bytes < 0)
throw failure{internal::concat(
"Error writing to large object #", id(), ": ", reason(err))};
if (bytes == 0)
throw failure{internal::concat(
"Could not write to large object #", id(), ": ", reason(err))};
throw failure{internal::concat(
"Wanted to write ", len, " bytes to large object #", id(),
"; could only write ", bytes, ".")};
}
}
pqxx::largeobjectaccess::size_type PQXX_COLD
pqxx::largeobjectaccess::read(char buf[], std::size_t len)
{
if (id() == oid_none)
throw usage_error{"No object selected."};
auto const bytes{cread(buf, len)};
if (bytes < 0)
{
int const err{errno};
if (err == ENOMEM)
throw std::bad_alloc{};
throw failure{internal::concat(
"Error reading from large object #", id(), ": ", reason(err))};
}
return bytes;
}
void PQXX_COLD pqxx::largeobjectaccess::open(openmode mode)
{
if (id() == oid_none)
throw usage_error{"No object selected."};
m_fd = lo_open(raw_connection(), id(), std_mode_to_pq_mode(mode));
if (m_fd < 0)
{
int const err{errno};
if (err == ENOMEM)
throw std::bad_alloc{};
throw failure{internal::concat(
"Could not open large object ", id(), ": ", reason(err))};
}
}
void PQXX_COLD pqxx::largeobjectaccess::close() noexcept
{
if (m_fd >= 0)
lo_close(raw_connection(), m_fd);
}
pqxx::largeobjectaccess::size_type PQXX_COLD
pqxx::largeobjectaccess::tell() const
{
auto const res{ctell()};
if (res == -1)
throw failure{reason(errno)};
return res;
}
std::string PQXX_COLD pqxx::largeobjectaccess::reason(int err) const
{
if (m_fd == -1)
return "No object opened.";
return largeobject::reason(m_trans.conn(), err);
}
void PQXX_COLD pqxx::largeobjectaccess::process_notice(zview s) noexcept
{
m_trans.process_notice(s);
}
#include "pqxx/internal/ignore-deprecated-post.hxx"
+35
View File
@@ -0,0 +1,35 @@
/** Implementation of the pqxx::notification_receiever class.
*
* pqxx::notification_receiver processes notifications.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <string>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/internal/gates/connection-notification_receiver.hxx"
#include "pqxx/notification.hxx"
#include "pqxx/internal/header-post.hxx"
pqxx::notification_receiver::notification_receiver(
connection &c, std::string_view channel) :
m_conn{c}, m_channel{channel}
{
pqxx::internal::gate::connection_notification_receiver{c}.add_receiver(this);
}
pqxx::notification_receiver::~notification_receiver()
{
pqxx::internal::gate::connection_notification_receiver{this->conn()}
.remove_receiver(this);
}
+122
View File
@@ -0,0 +1,122 @@
/* Implementations related to prepared and parameterised statements.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/params.hxx"
#include "pqxx/internal/header-post.hxx"
void pqxx::internal::c_params::reserve(std::size_t n) &
{
values.reserve(n);
lengths.reserve(n);
formats.reserve(n);
}
void pqxx::params::reserve(std::size_t n) &
{
m_params.reserve(n);
}
void pqxx::params::append() &
{
m_params.emplace_back(nullptr);
}
void pqxx::params::append(zview value) &
{
m_params.emplace_back(value);
}
void pqxx::params::append(std::string const &value) &
{
m_params.emplace_back(value);
}
void pqxx::params::append(std::string &&value) &
{
m_params.emplace_back(std::move(value));
}
void pqxx::params::append(params const &value) &
{
this->reserve(std::size(value.m_params) + std::size(this->m_params));
for (auto const &param : value.m_params) m_params.emplace_back(param);
}
void pqxx::params::append(std::basic_string_view<std::byte> value) &
{
m_params.emplace_back(value);
}
void pqxx::params::append(std::basic_string<std::byte> const &value) &
{
m_params.emplace_back(value);
}
void pqxx::params::append(std::basic_string<std::byte> &&value) &
{
m_params.emplace_back(std::move(value));
}
void PQXX_COLD pqxx::params::append(binarystring const &value) &
{
m_params.push_back(entry{value.bytes_view()});
}
void pqxx::params::append(params &&value) &
{
this->reserve(std::size(value.m_params) + std::size(this->m_params));
for (auto const &param : value.m_params)
m_params.emplace_back(std::move(param));
value.m_params.clear();
}
pqxx::internal::c_params pqxx::params::make_c_params() const
{
pqxx::internal::c_params p;
p.reserve(std::size(m_params));
for (auto const &param : m_params)
std::visit(
[&p](auto const &value) {
using T = strip_t<decltype(value)>;
if constexpr (std::is_same_v<T, std::nullptr_t>)
{
p.values.push_back(nullptr);
p.lengths.push_back(0);
}
else
{
p.values.push_back(reinterpret_cast<char const *>(std::data(value)));
p.lengths.push_back(
check_cast<int>(internal::ssize(value), s_overflow));
}
p.formats.push_back(param_format(value));
},
param);
return p;
}
+448
View File
@@ -0,0 +1,448 @@
/** Implementation of the pqxx::pipeline class.
*
* Throughput-optimized query interface.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <iterator>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/dbtransaction.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/gates/connection-pipeline.hxx"
#include "pqxx/internal/gates/result-creation.hxx"
#include "pqxx/internal/gates/result-pipeline.hxx"
#include "pqxx/pipeline.hxx"
#include "pqxx/separated_list.hxx"
#include "pqxx/internal/header-post.hxx"
namespace
{
std::string const theSeparator{"; "};
std::string const theDummyValue{"1"};
std::string const theDummyQuery{"SELECT " + theDummyValue + theSeparator};
} // namespace
void pqxx::pipeline::init()
{
m_encoding = internal::enc_group(m_trans.conn().encoding_id());
m_issuedrange = make_pair(std::end(m_queries), std::end(m_queries));
attach();
}
pqxx::pipeline::~pipeline() noexcept
{
try
{
cancel();
}
catch (std::exception const &)
{}
detach();
}
void pqxx::pipeline::attach()
{
if (not registered())
register_me();
}
void pqxx::pipeline::detach()
{
if (registered())
unregister_me();
}
pqxx::pipeline::query_id pqxx::pipeline::insert(std::string_view q) &
{
attach();
query_id const qid{generate_id()};
auto const i{m_queries.insert(std::make_pair(qid, Query(q))).first};
if (m_issuedrange.second == std::end(m_queries))
{
m_issuedrange.second = i;
if (m_issuedrange.first == std::end(m_queries))
m_issuedrange.first = i;
}
m_num_waiting++;
if (m_num_waiting > m_retain)
{
if (have_pending())
receive_if_available();
if (not have_pending())
issue();
}
return qid;
}
void pqxx::pipeline::complete()
{
if (have_pending())
receive(m_issuedrange.second);
if (m_num_waiting and (m_error == qid_limit()))
{
issue();
receive(std::end(m_queries));
}
detach();
}
void pqxx::pipeline::flush()
{
if (not std::empty(m_queries))
{
if (have_pending())
receive(m_issuedrange.second);
m_issuedrange.first = m_issuedrange.second = std::end(m_queries);
m_num_waiting = 0;
m_dummy_pending = false;
m_queries.clear();
}
detach();
}
void PQXX_COLD pqxx::pipeline::cancel()
{
while (have_pending())
{
pqxx::internal::gate::connection_pipeline(m_trans.conn()).cancel_query();
auto canceled_query{m_issuedrange.first};
++m_issuedrange.first;
m_queries.erase(canceled_query);
}
}
bool pqxx::pipeline::is_finished(pipeline::query_id q) const
{
if (m_queries.find(q) == std::end(m_queries))
throw std::logic_error{
internal::concat("Requested status for unknown query '", q, "'.")};
return (QueryMap::const_iterator(m_issuedrange.first) ==
std::end(m_queries)) or
(q < m_issuedrange.first->first and q < m_error);
}
std::pair<pqxx::pipeline::query_id, pqxx::result> pqxx::pipeline::retrieve()
{
if (std::empty(m_queries))
throw std::logic_error{"Attempt to retrieve result from empty pipeline."};
return retrieve(std::begin(m_queries));
}
int pqxx::pipeline::retain(int retain_max) &
{
if (retain_max < 0)
throw range_error{internal::concat(
"Attempt to make pipeline retain ", retain_max, " queries")};
int const oldvalue{m_retain};
m_retain = retain_max;
if (m_num_waiting >= m_retain)
resume();
return oldvalue;
}
void pqxx::pipeline::resume() &
{
if (have_pending())
receive_if_available();
if (not have_pending() and m_num_waiting)
{
issue();
receive_if_available();
}
}
pqxx::pipeline::query_id pqxx::pipeline::generate_id()
{
if (m_q_id == qid_limit())
throw std::overflow_error{"Too many queries went through pipeline."};
++m_q_id;
return m_q_id;
}
void pqxx::pipeline::issue()
{
// Retrieve that null result for the last query, if needed.
obtain_result();
// Don't issue anything if we've encountered an error.
if (m_error < qid_limit())
return;
// Start with oldest query (lowest id) not in previous issue range.
auto oldest{m_issuedrange.second};
// Construct cumulative query string for entire batch.
auto cum{separated_list(
theSeparator, oldest, std::end(m_queries),
[](QueryMap::const_iterator i) { return i->second.query; })};
auto const num_issued{
QueryMap::size_type(std::distance(oldest, std::end(m_queries)))};
bool const prepend_dummy{num_issued > 1};
if (prepend_dummy)
cum = theDummyQuery + cum;
pqxx::internal::gate::connection_pipeline{m_trans.conn()}.start_exec(
cum.c_str());
// Since we managed to send out these queries, update state to reflect this.
m_dummy_pending = prepend_dummy;
m_issuedrange.first = oldest;
m_issuedrange.second = std::end(m_queries);
m_num_waiting -= check_cast<int>(num_issued, "pipeline issue()"sv);
}
void PQXX_COLD pqxx::pipeline::internal_error(std::string const &err)
{
set_error_at(0);
throw pqxx::internal_error{err};
}
bool pqxx::pipeline::obtain_result(bool expect_none)
{
pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
auto const r{gate.get_result()};
if (r == nullptr)
{
if (have_pending() and not expect_none)
{
PQXX_UNLIKELY
set_error_at(m_issuedrange.first->first);
m_issuedrange.second = m_issuedrange.first;
}
return false;
}
result const res{pqxx::internal::gate::result_creation::create(
r, std::begin(m_queries)->second.query, m_encoding)};
if (not have_pending())
{
PQXX_UNLIKELY
set_error_at(std::begin(m_queries)->first);
throw std::logic_error{
"Got more results from pipeline than there were queries."};
}
// Must be the result for the oldest pending query.
if (not std::empty(m_issuedrange.first->second.res))
PQXX_UNLIKELY
internal_error("Multiple results for one query.");
m_issuedrange.first->second.res = res;
++m_issuedrange.first;
return true;
}
void pqxx::pipeline::obtain_dummy()
{
// Allocate once, re-use across invocations.
static auto const text{
std::make_shared<std::string>("[DUMMY PIPELINE QUERY]")};
pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
auto const r{gate.get_result()};
m_dummy_pending = false;
if (r == nullptr)
PQXX_UNLIKELY
internal_error("Pipeline got no result from backend when it expected one.");
result R{pqxx::internal::gate::result_creation::create(r, text, m_encoding)};
bool OK{false};
try
{
pqxx::internal::gate::result_creation{R}.check_status();
OK = true;
}
catch (sql_error const &)
{}
if (OK)
{
PQXX_LIKELY
if (std::size(R) > 1)
PQXX_UNLIKELY
internal_error("Unexpected result for dummy query in pipeline.");
if (R.at(0).at(0).as<std::string>() != theDummyValue)
PQXX_UNLIKELY
internal_error("Dummy query in pipeline returned unexpected value.");
return;
}
// TODO: Can we actually re-issue statements after a failure?
/* Execution of this batch failed.
*
* When we send multiple statements in one go, the backend treats them as a
* single transaction. So the entire batch was effectively rolled back.
*
* Since none of the queries in the batch were actually executed, we can
* afford to replay them one by one until we find the exact query that
* caused the error. This gives us not only a more specific error message
* to report, but also tells us which query to report it for.
*/
// First, give the whole batch the same syntax error message, in case all
// else is going to fail.
for (auto i{m_issuedrange.first}; i != m_issuedrange.second; ++i)
i->second.res = R;
// Remember where the end of this batch was
auto const stop{m_issuedrange.second};
// Retrieve that null result for the last query, if needed
obtain_result(true);
// Reset internal state to forget botched batch attempt
m_num_waiting += check_cast<int>(
std::distance(m_issuedrange.first, stop), "pipeline obtain_dummy()"sv);
m_issuedrange.second = m_issuedrange.first;
// Issue queries in failed batch one at a time.
unregister_me();
try
{
do {
m_num_waiting--;
auto const query{*m_issuedrange.first->second.query};
auto &holder{m_issuedrange.first->second};
holder.res = m_trans.exec(query);
pqxx::internal::gate::result_creation{holder.res}.check_status();
++m_issuedrange.first;
} while (m_issuedrange.first != stop);
}
catch (std::exception const &)
{
auto const thud{m_issuedrange.first->first};
++m_issuedrange.first;
m_issuedrange.second = m_issuedrange.first;
auto q{m_issuedrange.first};
set_error_at((q == std::end(m_queries)) ? thud + 1 : q->first);
}
}
std::pair<pqxx::pipeline::query_id, pqxx::result>
pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q)
{
if (q == std::end(m_queries))
throw std::logic_error{"Attempt to retrieve result for unknown query."};
if (q->first >= m_error)
throw std::runtime_error{
"Could not complete query in pipeline due to error in earlier query."};
// If query hasn't issued yet, do it now.
if (
m_issuedrange.second != std::end(m_queries) and
(q->first >= m_issuedrange.second->first))
{
if (have_pending())
receive(m_issuedrange.second);
if (m_error == qid_limit())
issue();
}
// If result not in yet, get it; else get at least whatever's convenient.
if (have_pending())
{
if (q->first >= m_issuedrange.first->first)
{
auto suc{q};
++suc;
receive(suc);
}
else
{
receive_if_available();
}
}
if (q->first >= m_error)
throw std::runtime_error{
"Could not complete query in pipeline due to error in earlier query."};
// Don't leave the backend idle if there are queries waiting to be issued.
if (m_num_waiting and not have_pending() and (m_error == qid_limit()))
issue();
result const R{q->second.res};
auto const P{std::make_pair(q->first, R)};
m_queries.erase(q);
pqxx::internal::gate::result_creation{R}.check_status();
return P;
}
void pqxx::pipeline::get_further_available_results()
{
pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
while (not gate.is_busy() and obtain_result())
if (not gate.consume_input())
throw broken_connection{};
}
void pqxx::pipeline::receive_if_available()
{
pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
if (not gate.consume_input())
throw broken_connection{};
if (gate.is_busy())
return;
if (m_dummy_pending)
obtain_dummy();
if (have_pending())
get_further_available_results();
}
void pqxx::pipeline::receive(pipeline::QueryMap::const_iterator stop)
{
if (m_dummy_pending)
obtain_dummy();
while (obtain_result() and
QueryMap::const_iterator{m_issuedrange.first} != stop)
;
// Also haul in any remaining "targets of opportunity".
if (QueryMap::const_iterator{m_issuedrange.first} == stop)
get_further_available_results();
}
+30
View File
@@ -0,0 +1,30 @@
/* Compiler settings for compiling libpqxx itself.
*
* Include this header in every source file that goes into the libpqxx library
* binary, and nowhere else.
*
* To ensure this, include this file once, as the very first header, in each
* compilation unit for the library.
*
* DO NOT INCLUDE THIS FILE when building client programs.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
// Workarounds & definitions needed to compile libpqxx into a library.
#include "pqxx/config-internal-compiler.h"
#ifdef _WIN32
# ifdef PQXX_SHARED
// We're building libpqxx as a shared library.
# undef PQXX_LIBEXPORT
# define PQXX_LIBEXPORT __declspec(dllexport)
# define PQXX_PRIVATE __declspec()
# endif // PQXX_SHARED
#endif // _WIN32
+536
View File
@@ -0,0 +1,536 @@
/** Implementation of the pqxx::result class and support classes.
*
* pqxx::result represents the set of result rows from a database query
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <cstdlib>
#include <cstring>
#include <stdexcept>
extern "C"
{
#include <libpq-fe.h>
}
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/except.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/result_iterator.hxx"
#include "pqxx/result.hxx"
#include "pqxx/row.hxx"
#include "pqxx/internal/header-post.hxx"
namespace pqxx
{
PQXX_DECLARE_ENUM_CONVERSION(ExecStatusType);
}
std::string const pqxx::result::s_empty_string;
/// C++ wrapper for libpq's PQclear.
void pqxx::internal::clear_result(pq::PGresult const *data)
{
PQclear(const_cast<pq::PGresult *>(data));
}
pqxx::result::result(
pqxx::internal::pq::PGresult *rhs, std::shared_ptr<std::string> query,
internal::encoding_group enc) :
m_data{make_data_pointer(rhs)}, m_query{query}, m_encoding(enc)
{}
bool pqxx::result::operator==(result const &rhs) const noexcept
{
if (&rhs == this)
PQXX_UNLIKELY return true;
auto const s{size()};
if (std::size(rhs) != s)
return false;
for (size_type i{0}; i < s; ++i)
if ((*this)[i] != rhs[i])
return false;
return true;
}
pqxx::result::const_reverse_iterator pqxx::result::rbegin() const
{
return const_reverse_iterator{end()};
}
pqxx::result::const_reverse_iterator pqxx::result::crbegin() const
{
return rbegin();
}
pqxx::result::const_reverse_iterator pqxx::result::rend() const
{
return const_reverse_iterator{begin()};
}
pqxx::result::const_reverse_iterator pqxx::result::crend() const
{
return rend();
}
pqxx::result::const_iterator pqxx::result::begin() const noexcept
{
return {this, 0};
}
pqxx::result::const_iterator pqxx::result::cbegin() const noexcept
{
return begin();
}
pqxx::result::size_type pqxx::result::size() const noexcept
{
return (m_data.get() == nullptr) ?
0 :
static_cast<size_type>(PQntuples(m_data.get()));
}
bool pqxx::result::empty() const noexcept
{
return (m_data.get() == nullptr) or (PQntuples(m_data.get()) == 0);
}
pqxx::result::reference pqxx::result::front() const noexcept
{
return row{*this, 0, columns()};
}
pqxx::result::reference pqxx::result::back() const noexcept
{
return row{*this, size() - 1, columns()};
}
void pqxx::result::swap(result &rhs) noexcept
{
m_data.swap(rhs.m_data);
m_query.swap(rhs.m_query);
}
pqxx::row pqxx::result::operator[](result_size_type i) const noexcept
{
return row{*this, i, columns()};
}
#if defined(PQXX_HAVE_MULTIDIMENSIONAL_SUBSCRIPT)
pqxx::field pqxx::result::operator[](
result_size_type row_num, row_size_type col_num) const noexcept
{
return {*this, row_num, field_num};
}
#endif
pqxx::row pqxx::result::at(pqxx::result::size_type i) const
{
if (i >= size())
throw range_error{"Row number out of range."};
return operator[](i);
}
pqxx::field pqxx::result::at(
pqxx::result_size_type row_num, pqxx::row_size_type col_num) const
{
if (row_num >= size())
throw range_error{"Row number out of range."};
if (col_num >= columns())
throw range_error{"Column out of range."};
return {*this, row_num, col_num};
}
namespace
{
/// C string comparison.
inline bool equal(char const lhs[], char const rhs[])
{
return strcmp(lhs, rhs) == 0;
}
} // namespace
void PQXX_COLD pqxx::result::throw_sql_error(
std::string const &Err, std::string const &Query) const
{
// Try to establish more precise error type, and throw corresponding
// type of exception.
char const *const code{PQresultErrorField(m_data.get(), PG_DIAG_SQLSTATE)};
if (code == nullptr)
{
// No SQLSTATE at all. Can this even happen?
// Let's assume the connection is no longer usable.
throw broken_connection{Err};
}
switch (code[0])
{
PQXX_UNLIKELY
case '\0':
// SQLSTATE is empty. We may have seen this happen in one
// circumstance: a client-side socket timeout (while using the
// tcp_user_timeout connection option). Unfortunately in that case the
// connection was just fine, so we had no real way of detecting the
// problem. (Trying to continue to use the connection does break
// though, so I feel justified in panicking.)
throw broken_connection{Err};
case '0':
switch (code[1])
{
case 'A': throw feature_not_supported{Err, Query, code};
case '8': throw broken_connection{Err};
case 'L':
case 'P': throw insufficient_privilege{Err, Query, code};
}
break;
case '2':
switch (code[1])
{
case '2': throw data_exception{Err, Query, code};
case '3':
if (equal(code, "23001"))
throw restrict_violation{Err, Query, code};
if (equal(code, "23502"))
throw not_null_violation{Err, Query, code};
if (equal(code, "23503"))
throw foreign_key_violation{Err, Query, code};
if (equal(code, "23505"))
throw unique_violation{Err, Query, code};
if (equal(code, "23514"))
throw check_violation{Err, Query, code};
throw integrity_constraint_violation{Err, Query, code};
case '4': throw invalid_cursor_state{Err, Query, code};
case '6': throw invalid_sql_statement_name{Err, Query, code};
}
break;
case '3':
switch (code[1])
{
case '4': throw invalid_cursor_name{Err, Query, code};
}
break;
case '4':
switch (code[1])
{
case '0':
if (equal(code, "40000"))
throw transaction_rollback{Err, Query, code};
if (equal(code, "40001"))
throw serialization_failure{Err, Query, code};
if (equal(code, "40003"))
throw statement_completion_unknown{Err, Query, code};
if (equal(code, "40P01"))
throw deadlock_detected{Err, Query, code};
break;
case '2':
if (equal(code, "42501"))
throw insufficient_privilege{Err, Query};
if (equal(code, "42601"))
throw syntax_error{Err, Query, code, errorposition()};
if (equal(code, "42703"))
throw undefined_column{Err, Query, code};
if (equal(code, "42883"))
throw undefined_function{Err, Query, code};
if (equal(code, "42P01"))
throw undefined_table{Err, Query, code};
}
break;
case '5':
switch (code[1])
{
case '3':
if (equal(code, "53100"))
throw disk_full{Err, Query, code};
if (equal(code, "53200"))
throw out_of_memory{Err, Query, code};
if (equal(code, "53300"))
throw too_many_connections{Err};
throw insufficient_resources{Err, Query, code};
}
break;
case 'P':
if (equal(code, "P0001"))
throw plpgsql_raise{Err, Query, code};
if (equal(code, "P0002"))
throw plpgsql_no_data_found{Err, Query, code};
if (equal(code, "P0003"))
throw plpgsql_too_many_rows{Err, Query, code};
throw plpgsql_error{Err, Query, code};
}
// Unknown error code.
throw sql_error{Err, Query, code};
}
void pqxx::result::check_status(std::string_view desc) const
{
if (auto err{status_error()}; not std::empty(err))
{
PQXX_UNLIKELY
if (not std::empty(desc))
err = pqxx::internal::concat("Failure during '", desc, "': ", err);
throw_sql_error(err, query());
}
}
std::string pqxx::result::status_error() const
{
if (m_data.get() == nullptr)
throw failure{"No result set given."};
std::string err;
switch (PQresultStatus(m_data.get()))
{
case PGRES_EMPTY_QUERY: // The string sent to the backend was empty.
case PGRES_COMMAND_OK: // Successful completion, no result data.
case PGRES_TUPLES_OK: // The query successfully executed.
break;
case PGRES_COPY_OUT: // Copy Out (from server) data transfer started.
case PGRES_COPY_IN: // Copy In (to server) data transfer started.
break;
case PGRES_BAD_RESPONSE: // The server's response was not understood.
case PGRES_NONFATAL_ERROR:
case PGRES_FATAL_ERROR: err = PQresultErrorMessage(m_data.get()); break;
default:
throw internal_error{internal::concat(
"pqxx::result: Unrecognized response code ",
PQresultStatus(m_data.get()))};
}
return err;
}
char const *pqxx::result::cmd_status() const noexcept
{
return PQcmdStatus(const_cast<internal::pq::PGresult *>(m_data.get()));
}
std::string const &pqxx::result::query() const &noexcept
{
return (m_query.get() == nullptr) ? s_empty_string : *m_query;
}
pqxx::oid pqxx::result::inserted_oid() const
{
if (m_data.get() == nullptr)
throw usage_error{
"Attempt to read oid of inserted row without an INSERT result"};
return PQoidValue(const_cast<internal::pq::PGresult *>(m_data.get()));
}
pqxx::result::size_type pqxx::result::affected_rows() const
{
auto const rows_str{
PQcmdTuples(const_cast<internal::pq::PGresult *>(m_data.get()))};
return (rows_str[0] == '\0') ? 0 : size_type(atoi(rows_str));
}
char const *pqxx::result::get_value(
pqxx::result::size_type row, pqxx::row::size_type col) const
{
return PQgetvalue(m_data.get(), row, col);
}
bool pqxx::result::get_is_null(
pqxx::result::size_type row, pqxx::row::size_type col) const
{
return PQgetisnull(m_data.get(), row, col) != 0;
}
pqxx::field::size_type pqxx::result::get_length(
pqxx::result::size_type row, pqxx::row::size_type col) const noexcept
{
return static_cast<pqxx::field::size_type>(
PQgetlength(m_data.get(), row, col));
}
pqxx::oid pqxx::result::column_type(row::size_type col_num) const
{
oid const t{PQftype(m_data.get(), col_num)};
if (t == oid_none)
throw argument_error{internal::concat(
"Attempt to retrieve type of nonexistent column ", col_num,
" of query result.")};
return t;
}
pqxx::row::size_type pqxx::result::column_number(zview col_name) const
{
auto const n{PQfnumber(
const_cast<internal::pq::PGresult *>(m_data.get()), col_name.c_str())};
if (n == -1)
throw argument_error{
internal::concat("Unknown column name: '", col_name, "'.")};
return static_cast<row::size_type>(n);
}
pqxx::oid pqxx::result::column_table(row::size_type col_num) const
{
oid const t{PQftable(m_data.get(), col_num)};
/* If we get oid_none, it may be because the column is computed, or because
* we got an invalid row number.
*/
if (t == oid_none and col_num >= columns())
throw argument_error{internal::concat(
"Attempt to retrieve table ID for column ", col_num, " out of ",
columns())};
return t;
}
pqxx::row::size_type pqxx::result::table_column(row::size_type col_num) const
{
auto const n{row::size_type(PQftablecol(m_data.get(), col_num))};
if (n != 0)
PQXX_LIKELY
return n - 1;
// Failed. Now find out why, so we can throw a sensible exception.
auto const col_str{to_string(col_num)};
if (col_num > columns())
throw range_error{
internal::concat("Invalid column index in table_column(): ", col_str)};
if (m_data.get() == nullptr)
throw usage_error{internal::concat(
"Can't query origin of column ", col_str,
": result is not initialized.")};
throw usage_error{internal::concat(
"Can't query origin of column ", col_str,
": not derived from table column.")};
}
int pqxx::result::errorposition() const
{
int pos{-1};
if (m_data.get())
{
auto const p{PQresultErrorField(
const_cast<internal::pq::PGresult *>(m_data.get()),
PG_DIAG_STATEMENT_POSITION)};
if (p)
pos = from_string<decltype(pos)>(p);
}
return pos;
}
char const *pqxx::result::column_name(pqxx::row::size_type number) const &
{
auto const n{PQfname(m_data.get(), number)};
if (n == nullptr)
{
PQXX_UNLIKELY
if (m_data.get() == nullptr)
throw usage_error{"Queried column name on null result."};
throw range_error{internal::concat(
"Invalid column number: ", number, " (maximum is ", (columns() - 1),
").")};
}
return n;
}
pqxx::row::size_type pqxx::result::columns() const noexcept
{
auto ptr{const_cast<internal::pq::PGresult *>(m_data.get())};
return (ptr == nullptr) ? 0 : row::size_type(PQnfields(ptr));
}
// const_result_iterator
pqxx::const_result_iterator pqxx::const_result_iterator::operator++(int)
{
const_result_iterator old{*this};
m_index++;
return old;
}
pqxx::const_result_iterator pqxx::const_result_iterator::operator--(int)
{
const_result_iterator old{*this};
m_index--;
return old;
}
pqxx::result::const_iterator
pqxx::result::const_reverse_iterator::base() const noexcept
{
iterator_type tmp{*this};
return ++tmp;
}
pqxx::const_reverse_result_iterator
pqxx::const_reverse_result_iterator::operator++(int)
{
const_reverse_result_iterator tmp{*this};
iterator_type::operator--();
return tmp;
}
pqxx::const_reverse_result_iterator
pqxx::const_reverse_result_iterator::operator--(int)
{
const_reverse_result_iterator tmp{*this};
iterator_type::operator++();
return tmp;
}
template<> std::string pqxx::to_string(field const &value)
{
return {value.c_str(), std::size(value)};
}
+221
View File
@@ -0,0 +1,221 @@
/** Implementation of the pqxx::robusttransaction class.
*
* pqxx::robusttransaction is a slower but safer transaction class.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <chrono>
#include <cstdint>
#include <stdexcept>
#include <unordered_map>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/connection.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/wait.hxx"
#include "pqxx/nontransaction.hxx"
#include "pqxx/result.hxx"
#include "pqxx/robusttransaction.hxx"
#include "pqxx/internal/header-post.hxx"
using namespace std::literals;
namespace
{
using pqxx::operator"" _zv;
/// Statuses in which we may find our transaction.
/** There's also "in the future," but it manifests as an error, not as an
* actual status.
*/
enum tx_stat
{
tx_unknown,
tx_committed,
tx_aborted,
tx_in_progress,
};
constexpr auto committed{"committed"_zv}, aborted{"aborted"_zv},
in_progress{"in progress"_zv};
/// Parse a nonempty transaction status string.
constexpr tx_stat parse_status(std::string_view text) noexcept
{
switch (text[0])
{
case 'a':
if (text == aborted)
PQXX_LIKELY return tx_aborted;
break;
case 'c':
if (text == committed)
PQXX_LIKELY return tx_committed;
break;
case 'i':
if (text == in_progress)
PQXX_LIKELY return tx_in_progress;
break;
}
return tx_unknown;
}
tx_stat query_status(std::string const &xid, std::string const &conn_str)
{
static std::string const name{"robusttxck"sv};
auto const query{pqxx::internal::concat("SELECT txid_status(", xid, ")")};
pqxx::connection c{conn_str};
pqxx::nontransaction w{c, name};
auto const status_row{w.exec1(query)};
auto const status_field{status_row[0]};
if (std::size(status_field) == 0)
throw pqxx::internal_error{"Transaction status string is empty."};
auto const status{parse_status(status_field.as<std::string_view>())};
if (status == tx_unknown)
throw pqxx::internal_error{pqxx::internal::concat(
"Unknown transaction status string: ", status_field.view())};
return status;
}
} // namespace
void pqxx::internal::basic_robusttransaction::init(zview begin_command)
{
static auto const txid_q{
std::make_shared<std::string>("SELECT txid_current()"sv)};
m_backendpid = conn().backendpid();
direct_exec(begin_command);
direct_exec(txid_q)[0][0].to(m_xid);
}
pqxx::internal::basic_robusttransaction::basic_robusttransaction(
connection &c, zview begin_command, std::string_view tname) :
dbtransaction(c, tname), m_conn_string{c.connection_string()}
{
init(begin_command);
}
pqxx::internal::basic_robusttransaction::basic_robusttransaction(
connection &c, zview begin_command) :
dbtransaction(c), m_conn_string{c.connection_string()}
{
init(begin_command);
}
pqxx::internal::basic_robusttransaction::~basic_robusttransaction() = default;
void pqxx::internal::basic_robusttransaction::do_commit()
{
static auto const check_constraints_q{
std::make_shared<std::string>("SET CONSTRAINTS ALL IMMEDIATE"sv)},
commit_q{std::make_shared<std::string>("COMMIT"sv)};
// Check constraints before sending the COMMIT to the database, so as to
// minimise our in-doubt window.
try
{
direct_exec(check_constraints_q);
}
catch (std::exception const &)
{
do_abort();
throw;
}
// Here comes the in-doubt window. If we lose our connection here, we'll be
// left clueless as to what happened on the backend. It may have received
// the commit command and completed the transaction, and ended up with a
// success it could not report back to us. Or it may have noticed the broken
// connection and aborted the transaction. It may even still be executing
// the commit, only to fail later.
//
// All this uncertainty requires some special handling, and that s what makes
// robusttransaction what it is.
try
{
direct_exec(commit_q);
// If we make it here, great. Normal, successful commit.
return;
}
catch (broken_connection const &)
{
// Oops, lost connection at the crucial moment. Fall through to in-doubt
// handling below.
}
catch (std::exception const &)
{
if (conn().is_open())
{
// Commit failed, for some other reason.
do_abort();
throw;
}
// Otherwise, fall through to in-doubt handling.
}
// If we get here, we're in doubt. Figure out what happened.
int const max_attempts{500};
static_assert(max_attempts > 0);
tx_stat stat;
for (int attempts{0}; attempts < max_attempts;
++attempts, pqxx::internal::wait_for(300u))
{
stat = tx_unknown;
try
{
stat = query_status(m_xid, m_conn_string);
}
catch (pqxx::broken_connection const &)
{
// Swallow the error. Pause and retry.
}
switch (stat)
{
case tx_unknown:
// We were unable to reconnect and query transaction status.
// Stay in it for another attempt.
return;
case tx_committed:
// Success! We're done.
return;
case tx_aborted:
// Aborted. We're done.
do_abort();
return;
case tx_in_progress:
// The transaction is still running. Stick around until we know what
// transpires.
break;
}
}
// Okay, this has taken too long. Give up, report in-doubt state.
throw in_doubt_error{internal::concat(
"Transaction ", name(), " (with transaction ID ", m_xid,
") "
"lost connection while committing. It's impossible to tell whether "
"it committed, or aborted, or is still running. "
"Attempts to find out its outcome have failed. "
"The backend process on the server had process ID ",
m_backendpid,
". "
"You may be able to check what happened to that process.")};
}
+250
View File
@@ -0,0 +1,250 @@
/** Implementation of the pqxx::result class and support classes.
*
* pqxx::result represents the set of result rows from a database query.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <cstdlib>
#include <cstring>
extern "C"
{
#include <libpq-fe.h>
}
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/except.hxx"
#include "pqxx/result.hxx"
#include "pqxx/row.hxx"
#include "pqxx/internal/header-post.hxx"
pqxx::row::row(
result const &r, result::size_type index, size_type cols) noexcept :
m_result{r}, m_index{index}, m_end{cols}
{}
pqxx::row::const_iterator pqxx::row::begin() const noexcept
{
return {*this, m_begin};
}
pqxx::row::const_iterator pqxx::row::cbegin() const noexcept
{
return begin();
}
pqxx::row::const_iterator pqxx::row::end() const noexcept
{
return {*this, m_end};
}
pqxx::row::const_iterator pqxx::row::cend() const noexcept
{
return end();
}
pqxx::row::reference pqxx::row::front() const noexcept
{
return field{m_result, m_index, m_begin};
}
pqxx::row::reference pqxx::row::back() const noexcept
{
return field{m_result, m_index, m_end - 1};
}
pqxx::row::const_reverse_iterator pqxx::row::rbegin() const
{
return const_reverse_row_iterator{end()};
}
pqxx::row::const_reverse_iterator pqxx::row::crbegin() const
{
return rbegin();
}
pqxx::row::const_reverse_iterator pqxx::row::rend() const
{
return const_reverse_row_iterator{begin()};
}
pqxx::row::const_reverse_iterator pqxx::row::crend() const
{
return rend();
}
bool pqxx::row::operator==(row const &rhs) const noexcept
{
if (&rhs == this)
return true;
auto const s{size()};
if (std::size(rhs) != s)
return false;
for (size_type i{0}; i < s; ++i)
if ((*this)[i] != rhs[i])
return false;
return true;
}
pqxx::row::reference pqxx::row::operator[](size_type i) const noexcept
{
return field{m_result, m_index, m_begin + i};
}
pqxx::row::reference pqxx::row::operator[](zview col_name) const
{
return at(col_name);
}
void pqxx::row::swap(row &rhs) noexcept
{
auto const i{m_index};
auto const b{m_begin};
auto const e{m_end};
m_result.swap(rhs.m_result);
m_index = rhs.m_index;
m_begin = rhs.m_begin;
m_end = rhs.m_end;
rhs.m_index = i;
rhs.m_begin = b;
rhs.m_end = e;
}
pqxx::field pqxx::row::at(zview col_name) const
{
return {m_result, m_index, m_begin + column_number(col_name)};
}
pqxx::field pqxx::row::at(pqxx::row::size_type i) const
{
if (i >= size())
throw range_error{"Invalid field number."};
return operator[](i);
}
pqxx::oid pqxx::row::column_type(size_type col_num) const
{
return m_result.column_type(m_begin + col_num);
}
pqxx::oid pqxx::row::column_table(size_type col_num) const
{
return m_result.column_table(m_begin + col_num);
}
pqxx::row::size_type pqxx::row::table_column(size_type col_num) const
{
return m_result.table_column(m_begin + col_num);
}
pqxx::row::size_type pqxx::row::column_number(zview col_name) const
{
auto const n{m_result.column_number(col_name)};
if (n >= m_end)
throw argument_error{
"Column '" + std::string{col_name} + "' falls outside slice."};
if (n >= m_begin)
return n - m_begin;
// This deals with a really nasty possibility: that the column name occurs
// twice - once before the beginning of the slice, and once inside the slice.
char const *const adapted_name{m_result.column_name(n)};
for (auto i{m_begin}; i < m_end; ++i)
if (strcmp(adapted_name, m_result.column_name(i)) == 0)
return i - m_begin;
// Didn't find any? Recurse just to produce the same error message.
return result{}.column_number(col_name);
}
pqxx::row PQXX_COLD pqxx::row::slice(size_type sbegin, size_type send) const
{
if (sbegin > send or send > size())
throw range_error{"Invalid field range."};
#include "pqxx/internal/ignore-deprecated-pre.hxx"
row res{*this};
#include "pqxx/internal/ignore-deprecated-post.hxx"
res.m_begin = m_begin + sbegin;
res.m_end = m_begin + send;
return res;
}
bool PQXX_COLD pqxx::row::empty() const noexcept
{
return m_begin == m_end;
}
pqxx::const_row_iterator pqxx::const_row_iterator::operator++(int)
{
auto const old{*this};
m_col++;
return old;
}
pqxx::const_row_iterator pqxx::const_row_iterator::operator--(int)
{
auto const old{*this};
m_col--;
return old;
}
pqxx::const_row_iterator
pqxx::const_reverse_row_iterator::base() const noexcept
{
iterator_type tmp{*this};
return ++tmp;
}
pqxx::const_reverse_row_iterator
pqxx::const_reverse_row_iterator::operator++(int)
{
auto tmp{*this};
operator++();
return tmp;
}
pqxx::const_reverse_row_iterator
pqxx::const_reverse_row_iterator::operator--(int)
{
auto tmp{*this};
operator--();
return tmp;
}
+276
View File
@@ -0,0 +1,276 @@
/** Implementation of libpqxx STL-style cursor classes.
*
* These classes wrap SQL cursors in STL-like interfaces.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <iterator>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/cursor.hxx"
#include "pqxx/internal/encodings.hxx"
#include "pqxx/internal/gates/connection-sql_cursor.hxx"
#include "pqxx/internal/gates/transaction-sql_cursor.hxx"
#include "pqxx/internal/header-post.hxx"
using namespace std::literals;
namespace
{
/// Is this character a "useless trailing character" in a query?
/** A character is "useless" at the end of a query if it is either whitespace
* or a semicolon.
*/
inline bool useless_trail(char c)
{
return isspace(c) or c == ';';
}
/// Find end of nonempty query, stripping off any trailing semicolon.
/** When executing a normal query, a trailing semicolon is meaningless but
* won't hurt. That's why we can't rule out that some code may include one.
*
* But for cursor queries, a trailing semicolon is a problem. The query gets
* embedded in a larger statement, which a semicolon would break into two.
* We'll have to remove it if present.
*
* A trailing semicolon may not actually be at the end. It could be masked by
* subsequent whitespace. If there's also a comment though, that's the
* caller's own lookout. We can't guard against every possible mistake, and
* text processing is actually remarkably sensitive to mistakes in a
* multi-encoding world.
*
* If there is a trailing semicolon, this function returns its offset. If
* there are more than one, it returns the offset of the first one. If there
* is no trailing semicolon, it returns the length of the query string.
*
* The query must be nonempty.
*/
std::string::size_type
find_query_end(std::string_view query, pqxx::internal::encoding_group enc)
{
auto const text{std::data(query)};
auto const size{std::size(query)};
std::string::size_type end;
if (enc == pqxx::internal::encoding_group::MONOBYTE)
{
// This is an encoding where we can scan backwards from the end.
for (end = std::size(query); end > 0 and useless_trail(text[end - 1]);
--end)
;
}
else
{
// Complex encoding. We only know how to iterate forwards, so start from
// the beginning.
end = 0;
pqxx::internal::for_glyphs(
enc,
[text, &end](char const *gbegin, char const *gend) {
if (gend - gbegin > 1 or not useless_trail(*gbegin))
end = std::string::size_type(gend - text);
},
text, size);
}
return end;
}
} // namespace
pqxx::internal::sql_cursor::sql_cursor(
transaction_base &t, std::string_view query, std::string_view cname,
cursor_base::access_policy ap, cursor_base::update_policy up,
cursor_base::ownership_policy op, bool hold) :
cursor_base{t.conn(), cname},
m_home{t.conn()},
m_adopted{false},
m_at_end{-1},
m_pos{0}
{
if (&t.conn() != &m_home)
throw internal_error{"Cursor in wrong connection"};
if (std::empty(query))
throw usage_error{"Cursor has empty query."};
auto const enc{enc_group(t.conn().encoding_id())};
auto const qend{find_query_end(query, enc)};
if (qend == 0)
throw usage_error{"Cursor has effectively empty query."};
query.remove_suffix(std::size(query) - qend);
std::string const cq{internal::concat(
"DECLARE "sv, t.quote_name(name()), " "sv,
((ap == cursor_base::forward_only) ? "NO "sv : ""sv), "SCROLL CURSOR "sv,
(hold ? "WITH HOLD "sv : ""sv), "FOR "sv, query, " "sv,
((up == cursor_base::update) ? "FOR UPDATE "sv : "FOR READ ONLY "sv))};
t.exec(cq);
// Now that we're here in the starting position, keep a copy of an empty
// result. That may come in handy later, because we may not be able to
// construct an empty result with all the right metadata due to the weird
// meaning of "FETCH 0."
init_empty_result(t);
m_ownership = op;
}
pqxx::internal::sql_cursor::sql_cursor(
transaction_base &t, std::string_view cname,
cursor_base::ownership_policy op) :
cursor_base{t.conn(), cname, false},
m_home{t.conn()},
m_empty_result{},
m_adopted{true},
m_at_end{0},
m_pos{-1}
{
m_adopted = true;
m_ownership = op;
}
void pqxx::internal::sql_cursor::close() noexcept
{
if (m_ownership == cursor_base::owned)
{
try
{
gate::connection_sql_cursor{m_home}.exec(
internal::concat("CLOSE "sv, m_home.quote_name(name())).c_str());
}
catch (std::exception const &)
{}
m_ownership = cursor_base::loose;
}
}
void pqxx::internal::sql_cursor::init_empty_result(transaction_base &t)
{
if (pos() != 0)
throw internal_error{"init_empty_result() from bad pos()."};
m_empty_result =
t.exec(internal::concat("FETCH 0 IN "sv, m_home.quote_name(name())));
}
/// Compute actual displacement based on requested and reported displacements.
pqxx::internal::sql_cursor::difference_type pqxx::internal::sql_cursor::adjust(
difference_type hoped, difference_type actual)
{
if (actual < 0)
throw internal_error{"Negative rows in cursor movement."};
if (hoped == 0)
return 0;
int const direction{((hoped < 0) ? -1 : 1)};
bool hit_end{false};
if (actual != labs(hoped))
{
if (actual > labs(hoped))
throw internal_error{"Cursor displacement larger than requested."};
// If we see fewer rows than requested, then we've hit an end (on either
// side) of the result set. Wether we make an extra step to a one-past-end
// position or whether we're already there depends on where we were
// previously: if our last move was in the same direction and also fell
// short, we're already at a one-past-end row.
if (m_at_end != direction)
++actual;
// If we hit the beginning, make sure our position calculation ends up
// at zero (even if we didn't previously know where we were!), and if we
// hit the other end, register the fact that we now know where the end
// of the result set is.
if (direction > 0)
hit_end = true;
else if (m_pos == -1)
m_pos = actual;
else if (m_pos != actual)
throw internal_error{internal::concat(
"Moved back to beginning, but wrong position: hoped=", hoped,
", actual=", actual, ", m_pos=", m_pos, ", direction=", direction,
".")};
m_at_end = direction;
}
else
{
m_at_end = 0;
}
if (m_pos >= 0)
m_pos += direction * actual;
if (hit_end)
{
if (m_endpos >= 0 and m_pos != m_endpos)
throw internal_error{"Inconsistent cursor end positions."};
m_endpos = m_pos;
}
return direction * actual;
}
pqxx::result pqxx::internal::sql_cursor::fetch(
difference_type rows, difference_type &displacement)
{
if (rows == 0)
{
displacement = 0;
return m_empty_result;
}
auto const query{pqxx::internal::concat(
"FETCH "sv, stridestring(rows), " IN "sv, m_home.quote_name(name()))};
auto const r{gate::connection_sql_cursor{m_home}.exec(query.c_str())};
displacement = adjust(rows, difference_type(std::size(r)));
return r;
}
pqxx::cursor_base::difference_type pqxx::internal::sql_cursor::move(
difference_type rows, difference_type &displacement)
{
if (rows == 0)
{
displacement = 0;
return 0;
}
auto const query{pqxx::internal::concat(
"MOVE "sv, stridestring(rows), " IN "sv, m_home.quote_name(name()))};
auto const r{gate::connection_sql_cursor{m_home}.exec(query.c_str())};
auto d{static_cast<difference_type>(r.affected_rows())};
displacement = adjust(rows, d);
return d;
}
std::string pqxx::internal::sql_cursor::stridestring(difference_type n)
{
/* Some special-casing for ALL and BACKWARD ALL here. We used to use numeric
* "infinities" for difference_type for this (the highest and lowest possible
* values for "long"), but for PostgreSQL 8.0 at least, the backend appears
* to expect a 32-bit number and fails to parse large 64-bit numbers. We
* could change the alias to match this behaviour, but that would break
* if/when Postgres is changed to accept 64-bit displacements.
*/
static std::string const All{"ALL"}, BackAll{"BACKWARD ALL"};
if (n >= cursor_base::all())
return All;
else if (n <= cursor_base::backward_all())
return BackAll;
return to_string(n);
}
+785
View File
@@ -0,0 +1,785 @@
/** Implementation of string conversions.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <limits>
#include <locale>
#include <string_view>
#include <system_error>
#if __has_include(<cxxabi.h>)
# include <cxxabi.h>
#endif
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/except.hxx"
#include "pqxx/strconv.hxx"
#include "pqxx/internal/header-post.hxx"
namespace
{
#if !defined(PQXX_HAVE_CHARCONV_FLOAT)
/// Do we have fully functional thread_local support?
/** When building with libcxxrt on clang, you can't create thread_local objects
* of non-POD types. Any attempt will result in a link error.
*/
constexpr bool have_thread_local
{
# if defined(PQXX_HAVE_THREAD_LOCAL)
true
# else
false
# endif
};
#endif
/// String comparison between string_view.
constexpr inline bool equal(std::string_view lhs, std::string_view rhs)
{
return lhs.compare(rhs) == 0;
}
/// The lowest possible value of integral type T.
template<typename T> constexpr T bottom{std::numeric_limits<T>::min()};
/// The highest possible value of integral type T.
template<typename T> constexpr T top{std::numeric_limits<T>::max()};
/// Write nonnegative integral value at end of buffer. Return start.
/** Assumes a sufficiently large buffer.
*
* Includes a single trailing null byte, right before @c *end.
*/
template<typename T> constexpr inline char *nonneg_to_buf(char *end, T value)
{
char *pos = end;
*--pos = '\0';
do {
*--pos = pqxx::internal::number_to_digit(int(value % 10));
value = T(value / 10);
} while (value > 0);
return pos;
}
/// Write negative version of value at end of buffer. Return start.
/** Like @c nonneg_to_buf, but prefixes a minus sign.
*/
template<typename T> constexpr inline char *neg_to_buf(char *end, T value)
{
char *pos = nonneg_to_buf(end, value);
*--pos = '-';
return pos;
}
/// Write lowest possible negative value at end of buffer.
/** Like @c neg_to_buf, but for the special case of the bottom value.
*/
template<typename T> constexpr inline char *bottom_to_buf(char *end)
{
static_assert(std::is_signed_v<T>);
// This is the hard case. In two's-complement systems, which includes
// any modern-day system I can think of, a signed type's bottom value
// has no positive equivalent. Luckily the C++ standards committee can't
// think of any exceptions either, so it's the required representation as
// of C++20. We'll assume it right now, while still on C++17.
static_assert(-(bottom<T> + 1) == top<T>);
// The unsigned version of T does have the unsigned version of bottom.
using unsigned_t = std::make_unsigned_t<T>;
// Careful though. If we tried to negate value in order to promote to
// unsigned_t, the value will overflow, which means behaviour is
// undefined. Promotion of a negative value to an unsigned type is
// well-defined, given a representation, so let's do that:
constexpr auto positive{static_cast<unsigned_t>(bottom<T>)};
// As luck would have it, in two's complement, this gives us exactly the
// value we want.
static_assert(positive == top<unsigned_t> / 2 + 1);
// So the only thing we need to do differently from the regular negative
// case is to skip that overflowing negation and promote to an unsigned type!
return neg_to_buf(end, positive);
}
#if defined(PQXX_HAVE_CHARCONV_INT) || defined(PQXX_HAVE_CHARCONV_FLOAT)
/// Call to_chars, report errors as exceptions, add zero, return pointer.
template<typename T>
[[maybe_unused]] inline char *
wrap_to_chars(char *begin, char *end, T const &value)
{
auto res{std::to_chars(begin, end - 1, value)};
if (res.ec != std::errc())
PQXX_UNLIKELY
switch (res.ec)
{
case std::errc::value_too_large:
throw pqxx::conversion_overrun{
"Could not convert " + pqxx::type_name<T> +
" to string: "
"buffer too small (" +
pqxx::to_string(end - begin) + " bytes)."};
default:
throw pqxx::conversion_error{
"Could not convert " + pqxx::type_name<T> + " to string."};
}
// No need to check for overrun here: we never even told to_chars about that
// last byte in the buffer, so it didn't get used up.
*res.ptr++ = '\0';
return res.ptr;
}
#endif
} // namespace
namespace pqxx::internal
{
template<typename T>
zview integral_traits<T>::to_buf(char *begin, char *end, T const &value)
{
static_assert(std::is_integral_v<T>);
auto const space{end - begin},
need{static_cast<ptrdiff_t>(size_buffer(value))};
if (space < need)
throw conversion_overrun{
"Could not convert " + type_name<T> +
" to string: "
"buffer too small. " +
pqxx::internal::state_buffer_overrun(space, need)};
char *pos;
if constexpr (std::is_unsigned_v<T>)
pos = nonneg_to_buf(end, value);
else if (value >= 0)
pos = nonneg_to_buf(end, value);
else if (value > bottom<T>)
pos = neg_to_buf(end, -value);
else
pos = bottom_to_buf<T>(end);
return {pos, end - pos - 1};
}
template zview integral_traits<short>::to_buf(char *, char *, short const &);
template zview integral_traits<unsigned short>::to_buf(
char *, char *, unsigned short const &);
template zview integral_traits<int>::to_buf(char *, char *, int const &);
template zview
integral_traits<unsigned>::to_buf(char *, char *, unsigned const &);
template zview integral_traits<long>::to_buf(char *, char *, long const &);
template zview
integral_traits<unsigned long>::to_buf(char *, char *, unsigned long const &);
template zview
integral_traits<long long>::to_buf(char *, char *, long long const &);
template zview integral_traits<unsigned long long>::to_buf(
char *, char *, unsigned long long const &);
template<typename T>
char *integral_traits<T>::into_buf(char *begin, char *end, T const &value)
{
#if defined(PQXX_HAVE_CHARCONV_INT)
// This is exactly what to_chars is good at. Trust standard library
// implementers to optimise better than we can.
return wrap_to_chars(begin, end, value);
#else
return generic_into_buf(begin, end, value);
#endif
}
template char *integral_traits<short>::into_buf(char *, char *, short const &);
template char *integral_traits<unsigned short>::into_buf(
char *, char *, unsigned short const &);
template char *integral_traits<int>::into_buf(char *, char *, int const &);
template char *
integral_traits<unsigned>::into_buf(char *, char *, unsigned const &);
template char *integral_traits<long>::into_buf(char *, char *, long const &);
template char *integral_traits<unsigned long>::into_buf(
char *, char *, unsigned long const &);
template char *
integral_traits<long long>::into_buf(char *, char *, long long const &);
template char *integral_traits<unsigned long long>::into_buf(
char *, char *, unsigned long long const &);
} // namespace pqxx::internal
namespace pqxx::internal
{
std::string demangle_type_name(char const raw[])
{
#if defined(PQXX_HAVE_CXA_DEMANGLE)
// We've got __cxa_demangle. Use it to get a friendlier type name.
int status{0};
// We've seen this fail on FreeBSD 11.3 (see #361). Trying to throw a
// meaningful exception only made things worse. So in case of error, just
// fall back to the raw name.
//
// When __cxa_demangle fails, it's guaranteed to return null.
char *demangled{abi::__cxa_demangle(raw, nullptr, nullptr, &status)};
#else
static constexpr char *demangled{nullptr};
#endif
std::string const name{(demangled == nullptr) ? raw : demangled};
// Check for nullness to work around jemalloc bug (see #508).
if (demangled != nullptr)
std::free(demangled);
return name;
}
void PQXX_COLD throw_null_conversion(std::string const &type)
{
throw conversion_error{"Attempt to convert null to " + type + "."};
}
std::string PQXX_COLD state_buffer_overrun(int have_bytes, int need_bytes)
{
// We convert these in standard library terms, not for the localisation
// so much as to avoid "error cycles," if these values in turn should fail
// to get enough buffer space.
std::stringstream have, need;
have << have_bytes;
need << need_bytes;
return "Have " + have.str() + " bytes, need " + need.str() + ".";
}
} // namespace pqxx::internal
namespace
{
#if defined(PQXX_HAVE_CHARCONV_INT) || defined(PQXX_HAVE_CHARCONV_FLOAT)
template<typename TYPE>
[[maybe_unused]] inline TYPE from_string_arithmetic(std::string_view in)
{
char const *here;
auto const end{std::data(in) + std::size(in)};
// Skip whitespace. This is not the proper way to do it, but I see no way
// that any of the supported encodings could ever produce a valid character
// whose byte sequence would confuse this code.
for (here = std::data(in); here < end and (*here == ' ' or *here == '\t');
++here)
;
TYPE out{};
auto const res{std::from_chars(here, end, out)};
if (res.ec == std::errc() and res.ptr == end)
PQXX_LIKELY
return out;
std::string msg;
if (res.ec == std::errc())
{
msg = "Could not parse full string.";
}
else
{
switch (res.ec)
{
case std::errc::result_out_of_range: msg = "Value out of range."; break;
case std::errc::invalid_argument: msg = "Invalid argument."; break;
default: break;
}
}
auto const base{
"Could not convert '" + std::string(in) +
"' "
"to " +
pqxx::type_name<TYPE>};
if (std::empty(msg))
throw pqxx::conversion_error{base + "."};
else
throw pqxx::conversion_error{base + ": " + msg};
}
#endif
} // namespace
namespace
{
#if !defined(PQXX_HAVE_CHARCONV_INT)
[[noreturn, maybe_unused]] void PQXX_COLD report_overflow()
{
throw pqxx::conversion_error{
"Could not convert string to integer: value out of range."};
}
template<typename T> struct numeric_ten
{
static inline constexpr T value = 10;
};
template<typename T> struct numeric_high_threshold
{
static inline constexpr T value =
(std::numeric_limits<T>::max)() / numeric_ten<T>::value;
};
template<typename T> struct numeric_low_threshold
{
static inline constexpr T value =
(std::numeric_limits<T>::min)() / numeric_ten<T>::value;
};
/// Return 10*n, or throw exception if it overflows.
template<typename T>
[[maybe_unused]] constexpr inline T safe_multiply_by_ten(T n)
{
using limits = std::numeric_limits<T>;
if (n > numeric_high_threshold<T>::value)
PQXX_UNLIKELY
report_overflow();
if constexpr (limits::is_signed)
{
if (numeric_low_threshold<T>::value > n)
PQXX_UNLIKELY
report_overflow();
}
return T(n * numeric_ten<T>::value);
}
/// Add digit d to nonnegative n, or throw exception if it overflows.
template<typename T>
[[maybe_unused]] constexpr inline T safe_add_digit(T n, T d)
{
T const high_threshold{static_cast<T>(std::numeric_limits<T>::max() - d)};
if (n > high_threshold)
PQXX_UNLIKELY
report_overflow();
return static_cast<T>(n + d);
}
/// Subtract digit d to nonpositive n, or throw exception if it overflows.
template<typename T>
[[maybe_unused]] constexpr inline T safe_sub_digit(T n, T d)
{
T const low_threshold{static_cast<T>(std::numeric_limits<T>::min() + d)};
if (n < low_threshold)
PQXX_UNLIKELY
report_overflow();
return static_cast<T>(n - d);
}
/// For use in string parsing: add new numeric digit to intermediate value.
template<typename L, typename R>
[[maybe_unused]] constexpr inline L absorb_digit_positive(L value, R digit)
{
return safe_add_digit(safe_multiply_by_ten(value), L(digit));
}
/// For use in string parsing: subtract digit from intermediate value.
template<typename L, typename R>
[[maybe_unused]] constexpr inline L absorb_digit_negative(L value, R digit)
{
return safe_sub_digit(safe_multiply_by_ten(value), L(digit));
}
template<typename T>
[[maybe_unused]] constexpr T from_string_integer(std::string_view text)
{
if (std::size(text) == 0)
throw pqxx::conversion_error{
"Attempt to convert empty string to " + pqxx::type_name<T> + "."};
char const *const data{std::data(text)};
std::size_t i{0};
// Skip whitespace. This is not the proper way to do it, but I see no way
// that any of the supported encodings could ever produce a valid character
// whose byte sequence would confuse this code.
//
// Why skip whitespace? Because that's how integral conversions are meant to
// work _for composite types._ I see no clean way to support leading
// whitespace there without putting the code in here. A shame about the
// overhead, modest as it is, for the normal case.
for (; i < std::size(text) and (data[i] == ' ' or data[i] == '\t'); ++i)
;
if (i == std::size(text))
throw pqxx::conversion_error{
"Converting string to " + pqxx::type_name<T> +
", but it contains only whitespace."};
char const initial{data[i]};
T result{0};
if (pqxx::internal::is_digit(initial))
{
for (; pqxx::internal::is_digit(data[i]); ++i)
result = absorb_digit_positive(
result, pqxx::internal::digit_to_number(data[i]));
}
else if (initial == '-')
{
if constexpr (not std::is_signed_v<T>)
throw pqxx::conversion_error{
"Attempt to convert negative value to " + pqxx::type_name<T> + "."};
++i;
if (i >= std::size(text))
throw pqxx::conversion_error{
"Converting string to " + pqxx::type_name<T> +
", but it contains only a sign."};
for (; i < std::size(text) and pqxx::internal::is_digit(data[i]); ++i)
result = absorb_digit_negative(
result, pqxx::internal::digit_to_number(data[i]));
}
else
{
throw pqxx::conversion_error{
"Could not convert string to " + pqxx::type_name<T> +
": "
"'" +
std::string{text} + "'."};
}
if (i < std::size(text))
throw pqxx::conversion_error{
"Unexpected text after " + pqxx::type_name<T> +
": "
"'" +
std::string{text} + "'."};
return result;
}
#endif // !PQXX_HAVE_CHARCONV_INT
} // namespace
namespace
{
[[maybe_unused]] constexpr bool
valid_infinity_string(std::string_view text) noexcept
{
return equal("infinity", text) or equal("Infinity", text) or
equal("INFINITY", text) or equal("inf", text);
}
} // namespace
#if !defined(PQXX_HAVE_CHARCONV_FLOAT)
namespace
{
/// Wrapper for std::stringstream with C locale.
/** We use this to work around missing std::to_chars for floating-point types.
*
* Initialising the stream (including locale and tweaked precision) seems to
* be expensive. So, create thread-local instances which we re-use. It's a
* lockless way of keeping global variables thread-safe, basically.
*
* The stream initialisation happens once per thread, in the constructor.
* And that's why we need to wrap this in a class. We can't just do it at the
* call site, or we'd still be doing it for every call.
*/
template<typename T> class dumb_stringstream : public std::stringstream
{
public:
// Do not initialise the base-class object using "stringstream{}" (with curly
// braces): that breaks on Visual C++. The classic "stringstream()" syntax
// (with parentheses) does work.
PQXX_COLD dumb_stringstream()
{
this->imbue(std::locale::classic());
this->precision(std::numeric_limits<T>::max_digits10);
}
};
template<typename F>
inline bool PQXX_COLD from_dumb_stringstream(
dumb_stringstream<F> &s, F &result, std::string_view text)
{
s.str(std::string{text});
return static_cast<bool>(s >> result);
}
// These are hard, and some popular compilers still lack std::from_chars.
template<typename T>
inline T PQXX_COLD from_string_awful_float(std::string_view text)
{
if (std::empty(text))
throw pqxx::conversion_error{
"Trying to convert empty string to " + pqxx::type_name<T> + "."};
bool ok{false};
T result;
switch (text[0])
{
case 'N':
case 'n':
// Accept "NaN," "nan," etc.
ok =
(std::size(text) == 3 and (text[1] == 'A' or text[1] == 'a') and
(text[2] == 'N' or text[2] == 'n'));
result = std::numeric_limits<T>::quiet_NaN();
break;
case 'I':
case 'i':
ok = valid_infinity_string(text);
result = std::numeric_limits<T>::infinity();
break;
default:
if (text[0] == '-' and valid_infinity_string(text.substr(1)))
{
ok = true;
result = -std::numeric_limits<T>::infinity();
}
else
{
PQXX_LIKELY
if constexpr (have_thread_local)
{
thread_local dumb_stringstream<T> S;
// Visual Studio 2017 seems to fail on repeated conversions if the
// clear() is done before the seekg(). Still don't know why! See #124
// and #125.
S.seekg(0);
S.clear();
ok = from_dumb_stringstream(S, result, text);
}
else
{
dumb_stringstream<T> S;
ok = from_dumb_stringstream(S, result, text);
}
}
break;
}
if (not ok)
throw pqxx::conversion_error{
"Could not convert string to numeric value: '" + std::string{text} +
"'."};
return result;
}
} // namespace
#endif // !PQXX_HAVE_CHARCONV_FLOAT
namespace pqxx::internal
{
/// Floating-point to_buf implemented in terms of to_string.
template<typename T>
zview float_traits<T>::to_buf(char *begin, char *end, T const &value)
{
#if defined(PQXX_HAVE_CHARCONV_FLOAT)
{
// Definitely prefer to let the standard library handle this!
auto const ptr{wrap_to_chars(begin, end, value)};
return zview{begin, std::size_t(ptr - begin - 1)};
}
#else
{
// Implement it ourselves. Weird detail: since this workaround is based on
// std::stringstream, which produces a std::string, it's actually easier to
// build the to_buf() on top of the to_string() than the other way around.
if (std::isnan(value))
return "nan"_zv;
if (std::isinf(value))
return (value > 0) ? "infinity"_zv : "-infinity"_zv;
auto text{to_string_float(value)};
auto have{end - begin};
auto need{std::size(text) + 1};
if (need > std::size_t(have))
throw conversion_error{
"Could not convert floating-point number to string: "
"buffer too small. " +
state_buffer_overrun(have, need)};
text.copy(begin, need);
return zview{begin, std::size(text)};
}
#endif
}
template zview float_traits<float>::to_buf(char *, char *, float const &);
template zview float_traits<double>::to_buf(char *, char *, double const &);
template zview
float_traits<long double>::to_buf(char *, char *, long double const &);
template<typename T>
char *float_traits<T>::into_buf(char *begin, char *end, T const &value)
{
#if defined(PQXX_HAVE_CHARCONV_FLOAT)
return wrap_to_chars(begin, end, value);
#else
return generic_into_buf(begin, end, value);
#endif
}
template char *float_traits<float>::into_buf(char *, char *, float const &);
template char *float_traits<double>::into_buf(char *, char *, double const &);
template char *
float_traits<long double>::into_buf(char *, char *, long double const &);
#if !defined(PQXX_HAVE_CHARCONV_FLOAT)
template<typename F>
inline std::string PQXX_COLD
to_dumb_stringstream(dumb_stringstream<F> &s, F value)
{
s.str("");
s << value;
return s.str();
}
#endif
/// Floating-point implementations for @c pqxx::to_string().
template<typename T> std::string to_string_float(T value)
{
#if defined(PQXX_HAVE_CHARCONV_FLOAT)
{
static constexpr auto space{float_traits<T>::size_buffer(value)};
std::string buf;
buf.resize(space);
std::string_view const view{
float_traits<T>::to_buf(std::data(buf), std::data(buf) + space, value)};
buf.resize(static_cast<std::size_t>(std::end(view) - std::begin(view)));
return buf;
}
#else
{
// In this rare case, we can convert to std::string but not to a simple
// buffer. So, implement to_buf in terms of to_string instead of the other
// way around.
if constexpr (have_thread_local)
{
thread_local dumb_stringstream<T> s;
return to_dumb_stringstream(s, value);
}
else
{
dumb_stringstream<T> s;
return to_dumb_stringstream(s, value);
}
}
#endif
}
} // namespace pqxx::internal
namespace pqxx::internal
{
template<typename T> T integral_traits<T>::from_string(std::string_view text)
{
#if defined(PQXX_HAVE_CHARCONV_INT)
return from_string_arithmetic<T>(text);
#else
return from_string_integer<T>(text);
#endif
}
template short integral_traits<short>::from_string(std::string_view);
template unsigned short
integral_traits<unsigned short>::from_string(std::string_view);
template int integral_traits<int>::from_string(std::string_view);
template unsigned integral_traits<unsigned>::from_string(std::string_view);
template long integral_traits<long>::from_string(std::string_view);
template unsigned long
integral_traits<unsigned long>::from_string(std::string_view);
template long long integral_traits<long long>::from_string(std::string_view);
template unsigned long long
integral_traits<unsigned long long>::from_string(std::string_view);
template<typename T> T float_traits<T>::from_string(std::string_view text)
{
#if defined(PQXX_HAVE_CHARCONV_FLOAT)
return from_string_arithmetic<T>(text);
#else
return from_string_awful_float<T>(text);
#endif
}
template float float_traits<float>::from_string(std::string_view);
template double float_traits<double>::from_string(std::string_view);
template long double float_traits<long double>::from_string(std::string_view);
template std::string to_string_float(float);
template std::string to_string_float(double);
template std::string to_string_float(long double);
} // namespace pqxx::internal
bool pqxx::string_traits<bool>::from_string(std::string_view text)
{
std::optional<bool> result;
switch (std::size(text))
{
case 0: result = false; break;
case 1:
switch (text[0])
{
case 'f':
case 'F':
case '0': result = false; break;
case 't':
case 'T':
case '1': result = true; break;
default: break;
}
break;
case 4:
if (equal(text, "true") or equal(text, "TRUE"))
result = true;
break;
case 5:
if (equal(text, "false") or equal(text, "FALSE"))
result = false;
break;
default: break;
}
if (result)
return *result;
else
throw conversion_error{
"Failed conversion to bool: '" + std::string{text} + "'."};
}
+327
View File
@@ -0,0 +1,327 @@
/** Implementation of the pqxx::stream_from class.
*
* pqxx::stream_from enables optimized batch reads from a database table.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <cassert>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/internal/encodings.hxx"
#include "pqxx/internal/gates/connection-stream_from.hxx"
#include "pqxx/stream_from.hxx"
#include "pqxx/transaction_base.hxx"
#include "pqxx/internal/header-post.hxx"
namespace
{
pqxx::internal::glyph_scanner_func *
get_scanner(pqxx::transaction_base const &tx)
{
auto const group{pqxx::internal::enc_group(tx.conn().encoding_id())};
return pqxx::internal::get_glyph_scanner(group);
}
constexpr std::string_view class_name{"stream_from"};
} // namespace
pqxx::stream_from::stream_from(
transaction_base &tx, from_query_t, std::string_view query) :
transaction_focus{tx, class_name}, m_glyph_scanner{get_scanner(tx)}
{
tx.exec0(internal::concat("COPY ("sv, query, ") TO STDOUT"sv));
register_me();
}
pqxx::stream_from::stream_from(
transaction_base &tx, from_table_t, std::string_view table) :
transaction_focus{tx, class_name, table},
m_glyph_scanner{get_scanner(tx)}
{
tx.exec0(internal::concat("COPY "sv, tx.quote_name(table), " TO STDOUT"sv));
register_me();
}
pqxx::stream_from::stream_from(
transaction_base &tx, std::string_view table, std::string_view columns,
from_table_t) :
transaction_focus{tx, class_name, table},
m_glyph_scanner{get_scanner(tx)}
{
if (std::empty(columns))
PQXX_UNLIKELY
tx.exec0(internal::concat("COPY "sv, table, " TO STDOUT"sv));
else PQXX_LIKELY tx.exec0(
internal::concat("COPY "sv, table, "("sv, columns, ") TO STDOUT"sv));
register_me();
}
pqxx::stream_from::stream_from(
transaction_base &tx, std::string_view unquoted_table,
std::string_view columns, from_table_t, int) :
stream_from{
tx, tx.conn().quote_table(unquoted_table), columns, from_table}
{}
pqxx::stream_from pqxx::stream_from::raw_table(
transaction_base &tx, std::string_view path, std::string_view columns)
{
return {tx, path, columns, from_table};
}
pqxx::stream_from pqxx::stream_from::table(
transaction_base &tx, table_path path,
std::initializer_list<std::string_view> columns)
{
auto const &conn{tx.conn()};
return raw_table(tx, conn.quote_table(path), conn.quote_columns(columns));
}
pqxx::stream_from::~stream_from() noexcept
{
try
{
close();
}
catch (std::exception const &e)
{
reg_pending_error(e.what());
}
}
pqxx::stream_from::raw_line pqxx::stream_from::get_raw_line()
{
if (*this)
{
internal::gate::connection_stream_from gate{m_trans.conn()};
try
{
raw_line line{gate.read_copy_line()};
if (line.first.get() == nullptr)
close();
return line;
}
catch (std::exception const &)
{
close();
throw;
}
}
else
{
return {};
}
}
void pqxx::stream_from::close()
{
if (not m_finished)
{
PQXX_UNLIKELY
m_finished = true;
unregister_me();
}
}
void pqxx::stream_from::complete()
{
if (m_finished)
return;
try
{
// Flush any remaining lines - libpq will automatically close the stream
// when it hits the end.
bool done{false};
while (not done)
{
auto [line, size] = get_raw_line();
ignore_unused(size);
done = not line.get();
}
}
catch (broken_connection const &)
{
close();
throw;
}
catch (std::exception const &e)
{
reg_pending_error(e.what());
}
close();
}
void pqxx::stream_from::parse_line()
{
if (m_finished)
PQXX_UNLIKELY
return;
auto const next_seq{m_glyph_scanner};
m_fields.clear();
auto const [line, line_size] = get_raw_line();
if (line.get() == nullptr)
{
m_finished = true;
return;
}
if (line_size >= (std::numeric_limits<decltype(line_size)>::max() / 2))
throw range_error{"Stream produced a ridiculously long line."};
// Make room for unescaping the line. It's a pessimistic size.
// Unusually, we're storing terminating zeroes *inside* the string.
// This is the only place where we modify m_row. MAKE SURE THE BUFFER DOES
// NOT GET RESIZED while we're working, because we're working with views into
// its buffer.
m_row.resize(line_size + 1);
char const *line_begin{line.get()};
char const *line_end{line_begin + line_size};
char const *read{line_begin};
// Output iterator for unescaped text.
char *write{m_row.data()};
// The pointer cannot be null at this point. But we initialise field_begin
// with this value, and carry it around the loop, and it can later become
// null. Static analysis in clang-tidy then likes to assume a case where
// field_begin is null, and deduces from this that "write" must have been
// null -- and so it marks "*write" as a null pointer dereference.
//
// This assertion tells clang-tidy just what it needs in order to deduce
// that *write never dereferences a null pointer.
assert(write != nullptr);
// Beginning of current field in m_row, or nullptr for null fields.
char const *field_begin{write};
while (read < line_end)
{
auto const offset{static_cast<std::size_t>(read - line_begin)};
auto const glyph_end{line_begin + next_seq(line_begin, line_size, offset)};
// XXX: find_char<'\t', '\\'>().
if (glyph_end == read + 1)
{
// Single-byte character.
char c{*read++};
switch (c)
{
case '\t': // Field separator.
// End the field.
if (field_begin == nullptr)
{
m_fields.emplace_back();
}
else
{
// Would love to emplace_back() here, but gcc 9.1 warns about the
// constructor not throwing. It suggests adding "noexcept." Which
// we can hardly do, without std::string_view guaranteeing it.
m_fields.push_back(zview{field_begin, write - field_begin});
*write++ = '\0';
}
field_begin = write;
break;
PQXX_UNLIKELY
case '\\': {
// Escape sequence.
if (read >= line_end)
throw failure{"Row ends in backslash"};
c = *read++;
switch (c)
{
case 'N':
// Null value.
if (write != field_begin)
throw failure{"Null sequence found in nonempty field"};
field_begin = nullptr;
// (If there's any characters _after_ the null we'll just crash.)
break;
case 'b': // Backspace.
PQXX_UNLIKELY
*write++ = '\b';
break;
case 'f': // Form feed
PQXX_UNLIKELY
*write++ = '\f';
break;
case 'n': // Line feed.
*write++ = '\n';
break;
case 'r': // Carriage return.
*write++ = '\r';
break;
case 't': // Horizontal tab.
*write++ = '\t';
break;
case 'v': // Vertical tab.
*write++ = '\v';
break;
default:
PQXX_LIKELY
// Regular character ("self-escaped").
*write++ = c;
break;
}
}
break;
PQXX_LIKELY
default: *write++ = c; break;
}
}
else
{
// Multi-byte sequence. Never treated specially, so just append.
while (read < glyph_end) *write++ = *read++;
}
}
// End the last field here.
if (field_begin == nullptr)
{
m_fields.emplace_back();
}
else
{
m_fields.push_back(zview{field_begin, write - field_begin});
*write++ = '\0';
}
// DO NOT shrink m_row to fit. We're carrying string_views pointing into
// the buffer. (Also, how useful would shrinking really be?)
}
std::vector<pqxx::zview> const *pqxx::stream_from::read_row() &
{
parse_line();
return m_finished ? nullptr : &m_fields;
}
+170
View File
@@ -0,0 +1,170 @@
/** Implementation of the pqxx::stream_to class.
*
* pqxx::stream_to enables optimized batch updates to a database table.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/gates/connection-stream_to.hxx"
#include "pqxx/stream_from.hxx"
#include "pqxx/stream_to.hxx"
#include "pqxx/internal/header-post.hxx"
namespace
{
using namespace std::literals;
void begin_copy(
pqxx::transaction_base &tx, std::string_view table, std::string_view columns)
{
tx.exec0(
std::empty(columns) ?
pqxx::internal::concat("COPY "sv, table, " FROM STDIN"sv) :
pqxx::internal::concat(
"COPY "sv, table, "("sv, columns, ") FROM STDIN"sv));
}
} // namespace
pqxx::stream_to::~stream_to() noexcept
{
try
{
complete();
}
catch (std::exception const &e)
{
reg_pending_error(e.what());
}
}
void pqxx::stream_to::write_raw_line(std::string_view text)
{
internal::gate::connection_stream_to{m_trans.conn()}.write_copy_line(text);
}
void pqxx::stream_to::write_buffer()
{
if (not std::empty(m_buffer))
{
// In append_to_buffer() we write a tab after each field. We only want a
// tab _between_ fields. Remove that last one.
assert(m_buffer[std::size(m_buffer) - 1] == '\t');
m_buffer.resize(std::size(m_buffer) - 1);
}
write_raw_line(m_buffer);
m_buffer.clear();
}
pqxx::stream_to &pqxx::stream_to::operator<<(stream_from &tr)
{
while (tr)
{
const auto [line, size] = tr.get_raw_line();
if (line.get() == nullptr)
break;
write_raw_line(std::string_view{line.get(), size});
}
return *this;
}
pqxx::stream_to::stream_to(
transaction_base &tx, std::string_view path, std::string_view columns) :
transaction_focus{tx, s_classname, path},
m_scanner{get_glyph_scanner(
pqxx::internal::enc_group(tx.conn().encoding_id()))}
{
begin_copy(tx, path, columns);
register_me();
}
void pqxx::stream_to::complete()
{
if (!m_finished)
{
m_finished = true;
unregister_me();
internal::gate::connection_stream_to{m_trans.conn()}.end_copy_write();
}
}
/// Return escape letter for c's backslash sequence, or 0 if not needed.
/** The API is a bit weird: you pass the width of the character, and its first
* byte. That's because we never need to escape a multibyte character anyway.
*/
constexpr char escape(std::size_t width, char c)
{
if (width == 1u)
switch (c)
{
case '\b': return 'b';
case '\f': return 'f';
case '\n': return 'n';
case '\r': return 'r';
case '\t': return 't';
case '\v': return 'v';
case '\\': return '\\';
}
PQXX_LIKELY
return '\0';
}
void pqxx::stream_to::escape_field_to_buffer(std::string_view data)
{
if (not std::empty(data))
{
// Mark the beginning of a stretch that we can copy into our buffer in one
// go. It feels like a waste to invoke generic multi-byte copies for every
// individual character in this loop, most of them actually probably only
// one byte long.
std::size_t begin_stretch{0};
std::size_t begin_char{0}, end;
// XXX: find_char<'\b', '\f', '\n', '\r', '\t', \v', '\\'>().
for (end = m_scanner(std::data(data), std::size(data), begin_char);
begin_char < std::size(data); begin_char = end,
end = m_scanner(std::data(data), std::size(data), begin_char))
{
// Escape sequence letter, if needed.
char const esc{escape(end - begin_char, data[begin_char])};
if (esc != '\0')
{
// This character needs escaping. So, it ends any trivially copyable
// stretch that we may have been having.
// Copy the stretch we've built up into our buffer.
m_buffer.append(
std::data(data) + begin_stretch, begin_char - begin_stretch);
// Escape the current character.
m_buffer.push_back('\\');
m_buffer.push_back(esc);
// Start a new stretch, right after the current character.
begin_stretch = end;
}
}
// Copy the final stretch.
m_buffer.append(
std::data(data) + begin_stretch, begin_char - begin_stretch);
}
m_buffer.push_back('\t');
}
+68
View File
@@ -0,0 +1,68 @@
/** Implementation of the pqxx::subtransaction class.
*
* pqxx::transaction is a nested transaction, i.e. one within a transaction
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <memory>
#include <stdexcept>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/connection.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/subtransaction.hxx"
#include "pqxx/internal/header-post.hxx"
namespace
{
using namespace std::literals;
constexpr std::string_view class_name{"subtransaction"sv};
} // namespace
pqxx::subtransaction::subtransaction(
dbtransaction &t, std::string_view tname) :
transaction_focus{t, class_name, t.conn().adorn_name(tname)},
// We can't initialise the rollback command here, because we don't yet
// have a full object to implement quoted_name().
dbtransaction{t.conn(), tname, std::shared_ptr<std::string>{}}
{
set_rollback_cmd(std::make_shared<std::string>(
internal::concat("ROLLBACK TO SAVEPOINT ", quoted_name())));
direct_exec(std::make_shared<std::string>(
internal::concat("SAVEPOINT ", quoted_name())));
}
namespace
{
using dbtransaction_ref = pqxx::dbtransaction &;
}
pqxx::subtransaction::subtransaction(
subtransaction &t, std::string_view tname) :
subtransaction(dbtransaction_ref(t), tname)
{}
pqxx::subtransaction::~subtransaction() noexcept
{
close();
}
void pqxx::subtransaction::do_commit()
{
direct_exec(std::make_shared<std::string>(
internal::concat("RELEASE SAVEPOINT ", quoted_name())));
}
+226
View File
@@ -0,0 +1,226 @@
/** Implementation of date/time support.
*/
#include "pqxx-source.hxx"
#include <cstdlib>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/time.hxx"
#include "pqxx/internal/header-post.hxx"
// std::chrono::year_month_day is C++20, so let's worry a bit less about C++17
// compatibility in this file.
#if defined(PQXX_HAVE_YEAR_MONTH_DAY)
namespace
{
using namespace std::literals;
/// Render the numeric part of a year value into a buffer.
/** Converts the year from "common era" (with a Year Zero) to "anno domini"
* (without a Year Zero).
*
* Doesn't render the sign. When you're rendering a date, you indicate a
* negative year by suffixing "BC" at the very end.
*
* Where @c string_traits::into_buf() returns a pointer to the position right
* after the terminating zero, this function returns a pointer to the character
* right after the last digit. (It may or may not write a terminating zero at
* that position itself.)
*/
inline char *
year_into_buf(char *begin, char *end, std::chrono::year const &value)
{
int const y{value};
if (y == int{(std::chrono::year::min)()})
{
// This is an evil special case: C++ year -32767 translates to 32768 BC,
// which is a number we can't fit into a short. At the moment postgres
// doesn't handle years before 4713 BC, but who knows, right?
static_assert(int{(std::chrono::year::min)()} == -32767);
constexpr auto hardcoded{"32768"sv};
PQXX_UNLIKELY
begin += hardcoded.copy(begin, std::size(hardcoded));
}
else
{
// C++ std::chrono::year has a year zero. PostgreSQL does not. So, C++
// year zero is 1 BC in the postgres calendar; C++ 1 BC is postgres 2 BC,
// and so on.
auto const absy{static_cast<short>(std::abs(y) + int{y <= 0})};
// PostgreSQL requires year input to be at least 3 digits long, or it
// won't be able to deduce the date format correctly. However on output
// it always writes years as at least 4 digits, and we'll do the same.
// Dates and times are a dirty, dirty business.
if (absy < 1000)
{
PQXX_UNLIKELY
*begin++ = '0';
if (absy < 100)
*begin++ = '0';
if (absy < 10)
*begin++ = '0';
}
begin = pqxx::string_traits<short>::into_buf(begin, end, absy) - 1;
}
return begin;
}
/// Parse the numeric part of a year value.
inline int year_from_buf(std::string_view text)
{
if (std::size(text) < 4)
throw pqxx::conversion_error{
pqxx::internal::concat("Year field is too small: '", text, "'.")};
// Parse as int, so we can accommodate 32768 BC which won't fit in a short
// as-is, but equates to 32767 BCE which will.
int const year{pqxx::string_traits<int>::from_string(text)};
if (year <= 0)
throw pqxx::conversion_error{
pqxx::internal::concat("Bad year: '", text, "'.")};
return year;
}
/// Render a valid 1-based month number into a buffer.
/* Where @c string_traits::into_buf() returns a pointer to the position right
* after the terminating zero, this function returns a pointer to the character
* right after the last digit. (It may or may not write a terminating zero at
* that position itself.)
*/
inline static char *
month_into_buf(char *begin, std::chrono::month const &value)
{
unsigned const m{value};
if (m >= 10)
*begin = '1';
else
*begin = '0';
++begin;
*begin++ = pqxx::internal::number_to_digit(static_cast<int>(m % 10));
return begin;
}
/// Parse a 1-based month value.
inline std::chrono::month month_from_string(std::string_view text)
{
if (
not pqxx::internal::is_digit(text[0]) or
not pqxx::internal::is_digit(text[1]))
throw pqxx::conversion_error{
pqxx::internal::concat("Invalid month: '", text, "'.")};
return std::chrono::month{unsigned(
(10 * pqxx::internal::digit_to_number(text[0])) +
pqxx::internal::digit_to_number(text[1]))};
}
/// Render a valid 1-based day-of-month value into a buffer.
inline char *day_into_buf(char *begin, std::chrono::day const &value)
{
unsigned d{value};
*begin++ = pqxx::internal::number_to_digit(static_cast<int>(d / 10));
*begin++ = pqxx::internal::number_to_digit(static_cast<int>(d % 10));
return begin;
}
/// Parse a 1-based day-of-month value.
inline std::chrono::day day_from_string(std::string_view text)
{
if (
not pqxx::internal::is_digit(text[0]) or
not pqxx::internal::is_digit(text[1]))
throw pqxx::conversion_error{
pqxx::internal::concat("Bad day in date: '", text, "'.")};
std::chrono::day const d{unsigned(
(10 * pqxx::internal::digit_to_number(text[0])) +
pqxx::internal::digit_to_number(text[1]))};
if (not d.ok())
throw pqxx::conversion_error{
pqxx::internal::concat("Bad day in date: '", text, "'.")};
return d;
}
/// Look for the dash separating year and month.
/** Assumes that @c text is nonempty.
*/
inline std::size_t find_year_month_separator(std::string_view text) noexcept
{
// We're looking for a dash. PostgreSQL won't output a negative year, so
// no worries about a leading dash. We could start searching at offset 4,
// but starting at the beginning produces more helpful error messages for
// malformed years.
std::size_t here;
for (here = 0; here < std::size(text) and text[here] != '-'; ++here)
;
return here;
}
/// Componse generic "invalid date" message for given (invalid) date text.
std::string make_parse_error(std::string_view text)
{
return pqxx::internal::concat("Invalid date: '", text, "'.");
}
} // namespace
namespace pqxx
{
char *string_traits<std::chrono::year_month_day>::into_buf(
char *begin, char *end, std::chrono::year_month_day const &value)
{
if (std::size_t(end - begin) < size_buffer(value))
throw conversion_overrun{"Not enough room in buffer for date."};
begin = year_into_buf(begin, end, value.year());
*begin++ = '-';
begin = month_into_buf(begin, value.month());
*begin++ = '-';
begin = day_into_buf(begin, value.day());
if (int{value.year()} <= 0)
{
PQXX_UNLIKELY
begin += s_bc.copy(begin, std::size(s_bc));
}
*begin++ = '\0';
return begin;
}
std::chrono::year_month_day
string_traits<std::chrono::year_month_day>::from_string(std::string_view text)
{
// We can't just re-use the std::chrono::year conversions, because the "BC"
// suffix comes at the very end.
if (std::size(text) < 9)
throw conversion_error{make_parse_error(text)};
bool const is_bc{text.ends_with(s_bc)};
if (is_bc)
PQXX_UNLIKELY
text = text.substr(0, std::size(text) - std::size(s_bc));
auto const ymsep{find_year_month_separator(text)};
if ((std::size(text) - ymsep) != 6)
throw conversion_error{make_parse_error(text)};
auto const base_year{
year_from_buf(std::string_view{std::data(text), ymsep})};
if (base_year == 0)
throw conversion_error{"Year zero conversion."};
std::chrono::year const y{is_bc ? (-base_year + 1) : base_year};
auto const m{month_from_string(text.substr(ymsep + 1, 2))};
if (text[ymsep + 3] != '-')
throw conversion_error{make_parse_error(text)};
auto const d{day_from_string(text.substr(ymsep + 4, 2))};
std::chrono::year_month_day const date{y, m, d};
if (not date.ok())
throw conversion_error{make_parse_error(text)};
return date;
}
} // namespace pqxx
#endif
+107
View File
@@ -0,0 +1,107 @@
/** Implementation of the pqxx::transaction class.
*
* pqxx::transaction represents a regular database transaction.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <stdexcept>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/connection.hxx"
#include "pqxx/result.hxx"
#include "pqxx/transaction.hxx"
#include "pqxx/internal/header-post.hxx"
pqxx::internal::basic_transaction::basic_transaction(
connection &c, zview begin_command, std::string_view tname) :
dbtransaction(c, tname)
{
register_transaction();
direct_exec(begin_command);
}
pqxx::internal::basic_transaction::basic_transaction(
connection &c, zview begin_command, std::string &&tname) :
dbtransaction(c, std::move(tname))
{
register_transaction();
direct_exec(begin_command);
}
pqxx::internal::basic_transaction::basic_transaction(
connection &c, zview begin_command) :
dbtransaction(c)
{
register_transaction();
direct_exec(begin_command);
}
// This should stop the compiler from generating the same vtables and
// destructor in multiple translation units. More importantly, if we don't do
// this, the sanitisers in g++ 7 and clang++ 6 complain about pointers to
// dbtransaction actually pointing to basic_transaction. Which is odd, in that
// any basic_transaction pointer should also be a dbtransaction pointer. But,
// apparently the vtable isn't the right one.
pqxx::internal::basic_transaction::~basic_transaction() noexcept = default;
void pqxx::internal::basic_transaction::do_commit()
{
static auto const commit_q{std::make_shared<std::string>("COMMIT"sv)};
try
{
direct_exec(commit_q);
}
catch (statement_completion_unknown const &e)
{
// Outcome of "commit" is unknown. This is a disaster: we don't know the
// resulting state of the database.
process_notice(internal::concat(e.what(), "\n"));
std::string msg{internal::concat(
"WARNING: Commit of transaction '", name(),
"' is unknown. "
"There is no way to tell whether the transaction succeeded "
"or was aborted except to check manually.\n")};
process_notice(msg);
// Strip newline. It was only needed for process_notice().
msg.pop_back();
throw in_doubt_error{std::move(msg)};
}
catch (std::exception const &e)
{
if (not conn().is_open())
{
// We've lost the connection while committing. There is just no way of
// telling what happened on the other end. >8-O
process_notice(internal::concat(e.what(), "\n"));
auto msg{internal::concat(
"WARNING: Connection lost while committing transaction '", name(),
"'. There is no way to tell whether the transaction succeeded "
"or was aborted except to check manually.\n")};
process_notice(msg);
// Strip newline. It was only needed for process_notice().
msg.pop_back();
throw in_doubt_error{std::move(msg)};
}
else
{
// Commit failed--probably due to a constraint violation or something
// similar.
throw;
}
}
}
+539
View File
@@ -0,0 +1,539 @@
/** Common code and definitions for the transaction classes.
*
* pqxx::transaction_base defines the interface for any abstract class that
* represents a database transaction.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <cstring>
#include <stdexcept>
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/connection.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/encodings.hxx"
#include "pqxx/internal/gates/connection-transaction.hxx"
#include "pqxx/internal/gates/transaction-transaction_focus.hxx"
#include "pqxx/result.hxx"
#include "pqxx/transaction_base.hxx"
#include "pqxx/transaction_focus.hxx"
#include "pqxx/internal/header-post.hxx"
using namespace std::literals;
namespace
{
/// Return a query pointer for the command "ROLLBACK".
/** Concentrates constructions so as to minimise the number of allocations.
* This way, the string gets allocated once and then all subsequent invocations
* copy shared_ptr instances to the same string.
*/
std::shared_ptr<std::string> make_rollback_cmd()
{
static auto const cmd{std::make_shared<std::string>("ROLLBACK")};
return cmd;
}
} // namespace
pqxx::transaction_base::transaction_base(connection &c) :
m_conn{c}, m_rollback_cmd{make_rollback_cmd()}
{}
pqxx::transaction_base::transaction_base(
connection &c, std::string_view tname) :
m_conn{c}, m_name{tname}, m_rollback_cmd{make_rollback_cmd()}
{}
pqxx::transaction_base::~transaction_base()
{
try
{
if (not std::empty(m_pending_error))
PQXX_UNLIKELY
process_notice(
internal::concat("UNPROCESSED ERROR: ", m_pending_error, "\n"));
if (m_registered)
{
m_conn.process_notice(
internal::concat(description(), " was never closed properly!\n"));
pqxx::internal::gate::connection_transaction{conn()}
.unregister_transaction(this);
}
}
catch (std::exception const &e)
{
try
{
process_notice(internal::concat(e.what(), "\n"));
}
catch (std::exception const &)
{
process_notice(e.what());
}
}
}
void pqxx::transaction_base::register_transaction()
{
pqxx::internal::gate::connection_transaction{conn()}.register_transaction(
this);
m_registered = true;
}
void pqxx::transaction_base::commit()
{
check_pending_error();
// Check previous status code. Caller should only call this function if
// we're in "implicit" state, but multiple commits are silently accepted.
switch (m_status)
{
case status::active: // Just fine. This is what we expect.
break;
case status::aborted:
throw usage_error{internal::concat(
"Attempt to commit previously aborted ", description())};
case status::committed:
// Transaction has been committed already. This is not exactly proper
// behaviour, but throwing an exception here would only give the impression
// that an abort is needed--which would only confuse things further at this
// stage.
// Therefore, multiple commits are accepted, though under protest.
m_conn.process_notice(
internal::concat(description(), " committed more than once.\n"));
return;
case status::in_doubt:
// Transaction may or may not have been committed. The only thing we can
// really do is keep telling the caller that the transaction is in doubt.
throw in_doubt_error{internal::concat(
description(), " committed again while in an indeterminate state.")};
default: throw internal_error{"pqxx::transaction: invalid status code."};
}
// Tricky one. If stream is nested in transaction but inside the same scope,
// the commit() will come before the stream is closed. Which means the
// commit is premature. Punish this swiftly and without fail to discourage
// the habit from forming.
if (m_focus != nullptr)
throw failure{internal::concat(
"Attempt to commit ", description(), " with ", m_focus->description(),
" still open.")};
// Check that we're still connected (as far as we know--this is not an
// absolute thing!) before trying to commit. If the connection was broken
// already, the commit would fail anyway but this way at least we don't
// remain in-doubt as to whether the backend got the commit order at all.
if (not m_conn.is_open())
throw broken_connection{
"Broken connection to backend; cannot complete transaction."};
try
{
do_commit();
m_status = status::committed;
}
catch (in_doubt_error const &)
{
m_status = status::in_doubt;
throw;
}
catch (std::exception const &)
{
m_status = status::aborted;
throw;
}
close();
}
void pqxx::transaction_base::do_abort()
{
if (m_rollback_cmd)
direct_exec(m_rollback_cmd);
}
void pqxx::transaction_base::abort()
{
// Check previous status code. Quietly accept multiple aborts to
// simplify emergency bailout code.
switch (m_status)
{
case status::active:
try
{
do_abort();
}
catch (std::exception const &e)
{
m_conn.process_notice(internal::concat(e.what(), "\n"));
}
break;
case status::aborted: return;
case status::committed:
throw usage_error{internal::concat(
"Attempt to abort previously committed ", description())};
case status::in_doubt:
// Aborting an in-doubt transaction is probably a reasonably sane response
// to an insane situation. Log it, but do not fail.
m_conn.process_notice(internal::concat(
"Warning: ", description(),
" aborted after going into indeterminate state; "
"it may have been executed anyway.\n"));
return;
default: throw internal_error{"Invalid transaction status."};
}
m_status = status::aborted;
close();
}
std::string PQXX_COLD pqxx::transaction_base::quote_raw(zview bin) const
{
return conn().quote(binary_cast(bin));
}
namespace
{
/// Guard command execution against clashes with pipelines and such.
/** A transaction can have only one focus at a time. Command execution is the
* most basic example of a transaction focus.
*/
class PQXX_PRIVATE command : pqxx::transaction_focus
{
public:
command(pqxx::transaction_base &tx, std::string_view oname) :
transaction_focus{tx, "command"sv, oname}
{
register_me();
}
~command() { unregister_me(); }
};
} // namespace
pqxx::result
pqxx::transaction_base::exec(std::string_view query, std::string_view desc)
{
check_pending_error();
command cmd{*this, desc};
switch (m_status)
{
case status::active: break;
case status::committed:
case status::aborted:
case status::in_doubt: {
std::string const n{
std::empty(desc) ? "" : internal::concat("'", desc, "' ")};
throw usage_error{internal::concat(
"Could not execute command ", n, ": transaction is already closed.")};
}
default: throw internal_error{"pqxx::transaction: invalid status code."};
}
return direct_exec(query, desc);
}
pqxx::result pqxx::transaction_base::exec_n(
result::size_type rows, zview query, std::string_view desc)
{
#include "pqxx/internal/ignore-deprecated-pre.hxx"
result const r{exec(query, desc)};
#include "pqxx/internal/ignore-deprecated-post.hxx"
if (std::size(r) != rows)
{
std::string const N{
std::empty(desc) ? "" : internal::concat("'", desc, "'")};
throw unexpected_rows{internal::concat(
"Expected ", rows, " row(s) of data from query ", N, ", got ",
std::size(r), ".")};
}
return r;
}
void pqxx::transaction_base::check_rowcount_prepared(
zview statement, result::size_type expected_rows,
result::size_type actual_rows)
{
if (actual_rows != expected_rows)
throw unexpected_rows{internal::concat(
"Expected ", expected_rows, " row(s) of data from prepared statement '",
statement, "', got ", actual_rows, ".")};
}
void pqxx::transaction_base::check_rowcount_params(
std::size_t expected_rows, std::size_t actual_rows)
{
if (actual_rows != expected_rows)
throw unexpected_rows{internal::concat(
"Expected ", expected_rows,
" row(s) of data from parameterised query, got ", actual_rows, ".")};
}
pqxx::result pqxx::transaction_base::internal_exec_prepared(
zview statement, internal::c_params const &args)
{
command cmd{*this, statement};
return pqxx::internal::gate::connection_transaction{conn()}.exec_prepared(
statement, args);
}
pqxx::result pqxx::transaction_base::internal_exec_params(
zview query, internal::c_params const &args)
{
command cmd{*this, query};
return pqxx::internal::gate::connection_transaction{conn()}.exec_params(
query, args);
}
void pqxx::transaction_base::set_variable(
std::string_view var, std::string_view value)
{
#include "pqxx/internal/ignore-deprecated-pre.hxx"
conn().set_variable(var, value);
#include "pqxx/internal/ignore-deprecated-post.hxx"
}
std::string pqxx::transaction_base::get_variable(std::string_view var)
{
#include "pqxx/internal/ignore-deprecated-pre.hxx"
return conn().get_variable(var);
#include "pqxx/internal/ignore-deprecated-post.hxx"
}
void pqxx::transaction_base::close() noexcept
{
try
{
try
{
check_pending_error();
}
catch (std::exception const &e)
{
m_conn.process_notice(e.what());
}
if (m_registered)
{
m_registered = false;
pqxx::internal::gate::connection_transaction{conn()}
.unregister_transaction(this);
}
if (m_status != status::active)
return;
if (m_focus != nullptr)
PQXX_UNLIKELY
m_conn.process_notice(internal::concat(
"Closing ", description(), " with ", m_focus->description(),
" still open.\n"));
try
{
abort();
}
catch (std::exception const &e)
{
m_conn.process_notice(e.what());
}
}
catch (std::exception const &e)
{
try
{
m_conn.process_notice(e.what());
}
catch (std::exception const &)
{}
}
}
namespace
{
[[nodiscard]] std::string_view
get_classname(pqxx::transaction_focus const *focus)
{
return (focus == nullptr) ? ""sv : focus->classname();
}
[[nodiscard]] std::string_view
get_obj_name(pqxx::transaction_focus const *focus)
{
return (focus == nullptr) ? ""sv : focus->name();
}
} // namespace
void pqxx::transaction_base::register_focus(transaction_focus *new_focus)
{
internal::check_unique_register(
m_focus, get_classname(m_focus), get_obj_name(m_focus), new_focus,
get_classname(new_focus), get_obj_name(new_focus));
m_focus = new_focus;
}
void pqxx::transaction_base::unregister_focus(
transaction_focus *new_focus) noexcept
{
try
{
pqxx::internal::check_unique_unregister(
m_focus, get_classname(m_focus), get_obj_name(m_focus), new_focus,
get_classname(new_focus), get_obj_name(new_focus));
m_focus = nullptr;
}
catch (std::exception const &e)
{
m_conn.process_notice(internal::concat(e.what(), "\n"));
}
}
pqxx::result pqxx::transaction_base::direct_exec(
std::string_view cmd, std::string_view desc)
{
check_pending_error();
return pqxx::internal::gate::connection_transaction{conn()}.exec(cmd, desc);
}
pqxx::result pqxx::transaction_base::direct_exec(
std::shared_ptr<std::string> cmd, std::string_view desc)
{
check_pending_error();
return pqxx::internal::gate::connection_transaction{conn()}.exec(cmd, desc);
}
void pqxx::transaction_base::register_pending_error(zview err) noexcept
{
if (std::empty(m_pending_error) and not std::empty(err))
{
try
{
m_pending_error = err;
}
catch (std::exception const &e)
{
try
{
PQXX_UNLIKELY
process_notice("UNABLE TO PROCESS ERROR\n");
process_notice(e.what());
process_notice("ERROR WAS:");
process_notice(err);
}
catch (...)
{}
}
}
}
void pqxx::transaction_base::register_pending_error(std::string &&err) noexcept
{
if (std::empty(m_pending_error) and not std::empty(err))
{
try
{
m_pending_error = std::move(err);
}
catch (std::exception const &e)
{
try
{
PQXX_UNLIKELY
process_notice("UNABLE TO PROCESS ERROR\n");
process_notice(e.what());
process_notice("ERROR WAS:");
process_notice(err);
}
catch (...)
{}
}
}
}
void pqxx::transaction_base::check_pending_error()
{
if (not std::empty(m_pending_error))
{
std::string err;
err.swap(m_pending_error);
throw failure{err};
}
}
std::string pqxx::transaction_base::description() const
{
return internal::describe_object("transaction", name());
}
void pqxx::transaction_focus::register_me()
{
pqxx::internal::gate::transaction_transaction_focus{m_trans}.register_focus(
this);
m_registered = true;
}
void pqxx::transaction_focus::unregister_me() noexcept
{
pqxx::internal::gate::transaction_transaction_focus{m_trans}
.unregister_focus(this);
m_registered = false;
}
void pqxx::transaction_focus::reg_pending_error(
std::string const &err) noexcept
{
pqxx::internal::gate::transaction_transaction_focus{m_trans}
.register_pending_error(err);
}
+194
View File
@@ -0,0 +1,194 @@
/** Various utility functions.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <new>
extern "C"
{
#include <libpq-fe.h>
}
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/except.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/util.hxx"
#include "pqxx/internal/header-post.hxx"
using namespace std::literals;
pqxx::thread_safety_model PQXX_COLD pqxx::describe_thread_safety()
{
thread_safety_model model;
model.safe_libpq = (PQisthreadsafe() != 0);
// Sadly I'm not aware of any way to avoid this just yet.
model.safe_kerberos = false;
model.description = internal::concat(
(model.safe_libpq ? ""sv :
"Using a libpq build that is not thread-safe.\n"sv),
(model.safe_kerberos ?
""sv :
"Kerberos is not thread-safe. If your application uses Kerberos, "
"protect all calls to Kerberos or libpqxx using a global lock.\n"sv));
return model;
}
std::string pqxx::internal::describe_object(
std::string_view class_name, std::string_view obj_name)
{
if (std::empty(obj_name))
return std::string{class_name};
else
return pqxx::internal::concat(class_name, " '", obj_name, "'");
}
void pqxx::internal::check_unique_register(
void const *old_guest, std::string_view old_class, std::string_view old_name,
void const *new_guest, std::string_view new_class, std::string_view new_name)
{
if (new_guest == nullptr)
throw internal_error{"Null pointer registered."};
if (old_guest != nullptr)
throw usage_error{
(old_guest == new_guest) ?
concat("Started twice: ", describe_object(old_class, old_name), ".") :
concat(
"Started new ", describe_object(new_class, new_name), " while ",
describe_object(new_class, new_name), " was still active.")};
}
void pqxx::internal::check_unique_unregister(
void const *old_guest, std::string_view old_class, std::string_view old_name,
void const *new_guest, std::string_view new_class, std::string_view new_name)
{
if (new_guest != old_guest)
{
PQXX_UNLIKELY
if (new_guest == nullptr)
throw usage_error{concat(
"Expected to close ", describe_object(old_class, old_name),
", but got null pointer instead.")};
if (old_guest == nullptr)
throw usage_error{concat(
"Closed while not open: ", describe_object(new_class, new_name))};
else
throw usage_error{concat(
"Closed ", describe_object(new_class, new_name),
"; expected to close ", describe_object(old_class, old_name))};
}
}
namespace
{
constexpr char hex_digits[] = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/// Translate a number (must be between 0 and 16 exclusive) to a hex digit.
constexpr char hex_digit(int c) noexcept
{
return hex_digits[c];
}
/// Translate a hex digit to a nibble. Return -1 if it's not a valid digit.
constexpr int nibble(int c) noexcept
{
if (c >= '0' and c <= '9')
PQXX_LIKELY
return c - '0';
else if (c >= 'a' and c <= 'f') return 10 + (c - 'a');
else if (c >= 'A' and c <= 'F') return 10 + (c - 'A');
else return -1;
}
} // namespace
void pqxx::internal::esc_bin(
std::basic_string_view<std::byte> binary_data, char buffer[]) noexcept
{
auto here{buffer};
*here++ = '\\';
*here++ = 'x';
for (auto const byte : binary_data)
{
auto uc{static_cast<unsigned char>(byte)};
*here++ = hex_digit(uc >> 4);
*here++ = hex_digit(uc & 0x0f);
}
// (No need to increment further. Facebook's "infer" complains if we do.)
*here = '\0';
}
std::string
pqxx::internal::esc_bin(std::basic_string_view<std::byte> binary_data)
{
auto const bytes{size_esc_bin(std::size(binary_data))};
std::string buf;
buf.resize(bytes);
esc_bin(binary_data, buf.data());
// Strip off the trailing zero.
buf.resize(bytes - 1);
return buf;
}
void pqxx::internal::unesc_bin(
std::string_view escaped_data, std::byte buffer[])
{
auto const in_size{std::size(escaped_data)};
if (in_size < 2)
throw pqxx::failure{"Binary data appears truncated."};
if ((in_size % 2) != 0)
throw pqxx::failure{"Invalid escaped binary length."};
char const *in{escaped_data.data()};
char const *const end{in + in_size};
if (*in++ != '\\' or *in++ != 'x')
throw pqxx::failure(
"Escaped binary data did not start with '\\x'`. Is the server or libpq "
"too old?");
auto out{buffer};
while (in != end)
{
int hi{nibble(*in++)};
if (hi < 0)
throw pqxx::failure{"Invalid hex-escaped data."};
int lo{nibble(*in++)};
if (lo < 0)
throw pqxx::failure{"Invalid hex-escaped data."};
*out++ = static_cast<std::byte>((hi << 4) | lo);
}
}
std::basic_string<std::byte>
pqxx::internal::unesc_bin(std::string_view escaped_data)
{
auto const bytes{size_unesc_bin(std::size(escaped_data))};
std::basic_string<std::byte> buf;
buf.resize(bytes);
unesc_bin(escaped_data, buf.data());
return buf;
}
+27
View File
@@ -0,0 +1,27 @@
/** Version check.
*
* Copyright (c) 2000-2022, Jeroen T. Vermeulen.
*
* See COPYING for copyright license. If you did not receive a file called
* COPYING with this source code, please notify the distributor of this
* mistake, or contact the author.
*/
#include "pqxx-source.hxx"
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/version.hxx"
#include "pqxx/internal/header-post.hxx"
namespace pqxx::internal
{
// One, single definition of this function. If a call fails to link, and
// (some) other calls do link, then the libpqxx binary was built against a
// different libpqxx version than the code which is being linked against it.
PQXX_LIBEXPORT int PQXX_VERSION_CHECK() noexcept
{
return 0;
}
} // namespace pqxx::internal
+136
View File
@@ -0,0 +1,136 @@
/** Functions that wait.
*/
#include "pqxx-source.hxx"
// The <thread> header is still broken on MinGW. :-(
#if defined(PQXX_HAVE_SLEEP_FOR)
# include <thread>
#endif
// For WSAPoll():
#if __has_include(<winsock2.h>)
# include <winsock2.h>
# define PQXX_HAVE_SELECT
#endif
#if __has_include(<ws2tcpip.h>)
# include <ws2tcpip.h>
#endif
#if __has_include(<mstcpip.h>)
# include <mstcpip.h>
#endif
// For poll():
#if __has_include(<poll.h>)
# include <poll.h>
#endif
// For select() on recent POSIX systems.
#if __has_include(<sys/select.h>)
# include <sys/select.h>
# define PQXX_HAVE_SELECT
#endif
// For select() on some older POSIX systems.
#if __has_include(<sys/types.h>)
# include <sys/types.h>
# define PQXX_HAVE_SELECT
#endif
#if __has_include(<unistd.h>)
# include <unistd.h>
#endif
#if __has_include(<sys/time.h>)
# include <sys/time.h>
#endif
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/internal/wait.hxx"
#include "pqxx/util.hxx"
#include "pqxx/internal/header-post.hxx"
namespace
{
template<typename T> T to_milli(unsigned seconds, unsigned microseconds)
{
return pqxx::check_cast<T>(
(seconds * 1000) + (microseconds / 1000),
"Wait timeout value out of bounds.");
}
#if defined(PQXX_HAVE_SELECT)
/// Set a bit on an fd_set.
[[maybe_unused]] void set_fdbit(fd_set &bits, int fd)
{
# ifdef _MSC_VER
// Suppress pointless, unfixable warnings in Visual Studio.
# pragma warning(push)
# pragma warning(disable : 4389) // Signed/unsigned mismatch.
# pragma warning(disable : 4127) // Conditional expression is constant.
# endif
FD_SET(fd, &bits);
# ifdef _MSV_VER
// Restore prevalent warning settings.
# pragma warning(pop)
# endif
}
#endif
} // namespace
void pqxx::internal::wait_fd(
int fd, bool for_read, bool for_write, unsigned seconds,
unsigned microseconds)
{
// WSAPoll is available in winsock2.h only for versions of Windows >= 0x0600
#if defined(_WIN32) && (_WIN32_WINNT >= 0x0600)
short const events{static_cast<short>(
(for_read ? POLLRDNORM : 0) | (for_write ? POLLWRNORM : 0))};
WSAPOLLFD fdarray{SOCKET(fd), events, 0};
WSAPoll(&fdarray, 1u, to_milli<unsigned>(seconds, microseconds));
// TODO: Check for errors.
#elif defined(PQXX_HAVE_POLL)
auto const events{static_cast<short>(
POLLERR | POLLHUP | POLLNVAL | (for_read ? POLLIN : 0) |
(for_write ? POLLOUT : 0))};
pollfd pfd{fd, events, 0};
poll(&pfd, 1, to_milli<int>(seconds, microseconds));
// TODO: Check for errors.
#else
// No poll()? Our last option is select().
fd_set read_fds;
FD_ZERO(&read_fds);
if (for_read)
set_fdbit(read_fds, fd);
fd_set write_fds;
FD_ZERO(&write_fds);
if (for_write)
set_fdbit(write_fds, fd);
fd_set except_fds;
FD_ZERO(&except_fds);
set_fdbit(except_fds, fd);
timeval tv = {seconds, microseconds};
select(fd + 1, &read_fds, &write_fds, &except_fds, &tv);
// TODO: Check for errors.
#endif
}
void PQXX_COLD pqxx::internal::wait_for(unsigned int microseconds)
{
#if defined(PQXX_HAVE_SLEEP_FOR)
std::this_thread::sleep_for(std::chrono::microseconds{microseconds});
#else
// MinGW still does not have a functioning <thread> header. Work around this
// using select().
// Not worth optimising for though -- they'll have to fix it at some point.
timeval tv{microseconds / 1'000'000u, microseconds % 1'000'000u};
select(0, nullptr, nullptr, nullptr, &tv);
#endif
}