First Commit
This commit is contained in:
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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};
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
{}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 ¶m : 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 ¶m : 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 ¶m : 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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)};
|
||||
}
|
||||
@@ -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.")};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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} + "'."};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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())));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user