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

This commit is contained in:
2025-11-19 16:23:45 +07:00
commit dbdc5bcc4a
1791 changed files with 489451 additions and 0 deletions
@@ -0,0 +1,13 @@
MAINTAINERCLEANFILES=Makefile.in stamp-h.in
noinst_HEADERS = \
config-internal-autotools.h
nodist_noinst_HEADERS = \
config.h \
config-internal-compiler.h
DISTCLEANFILES = \
config-internal-autotools.h \
config-internal-compiler.h \
config-public-compiler.h
+556
View File
@@ -0,0 +1,556 @@
# 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 = include/pqxx
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 $(noinst_HEADERS) \
$(am__DIST_COMMON)
mkinstalldirs = $(SHELL) $(top_srcdir)/config/mkinstalldirs
CONFIG_HEADER = config.h
CONFIG_CLEAN_FILES =
CONFIG_CLEAN_VPATH_FILES =
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 =
SOURCES =
DIST_SOURCES =
am__can_run_installinfo = \
case $$AM_UPDATE_INFO_DIR in \
n|no|NO) false;; \
*) (install-info --version) >/dev/null 2>&1;; \
esac
HEADERS = $(nodist_noinst_HEADERS) $(noinst_HEADERS)
am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) \
config.h.in
# 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 $(srcdir)/config.h.in \
$(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@
MAINTAINERCLEANFILES = Makefile.in stamp-h.in
noinst_HEADERS = \
config-internal-autotools.h
nodist_noinst_HEADERS = \
config.h \
config-internal-compiler.h
DISTCLEANFILES = \
config-internal-autotools.h \
config-internal-compiler.h \
config-public-compiler.h
all: config.h
$(MAKE) $(AM_MAKEFLAGS) all-am
.SUFFIXES:
$(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 include/pqxx/Makefile'; \
$(am__cd) $(top_srcdir) && \
$(AUTOMAKE) --gnu include/pqxx/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):
config.h: stamp-h1
@test -f $@ || rm -f stamp-h1
@test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1
stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status
@rm -f stamp-h1
cd $(top_builddir) && $(SHELL) ./config.status include/pqxx/config.h
$(srcdir)/config.h.in: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
($(am__cd) $(top_srcdir) && $(AUTOHEADER))
rm -f stamp-h1
touch $@
distclean-hdr:
-rm -f config.h stamp-h1
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 $(HEADERS) config.h
installdirs:
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)
-test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
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-libtool mostlyclean-am
distclean: distclean-am
-rm -f Makefile
distclean-am: clean-am distclean-generic distclean-hdr 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-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 Makefile
maintainer-clean-am: distclean-am maintainer-clean-generic
mostlyclean: mostlyclean-am
mostlyclean-am: mostlyclean-generic mostlyclean-libtool
pdf: pdf-am
pdf-am:
ps: ps-am
ps-am:
uninstall-am:
.MAKE: all install-am install-strip
.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \
clean-libtool cscopelist-am ctags ctags-am distclean \
distclean-generic distclean-hdr distclean-libtool \
distclean-tags distdir dvi dvi-am html html-am info info-am \
install install-am install-data install-data-am install-dvi \
install-dvi-am install-exec install-exec-am install-html \
install-html-am install-info install-info-am install-man \
install-pdf install-pdf-am install-ps install-ps-am \
install-strip installcheck installcheck-am installdirs \
maintainer-clean maintainer-clean-generic mostlyclean \
mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
tags tags-am uninstall uninstall-am
.PRECIOUS: Makefile
# Tell versions [3.59,3.63) of GNU make to not export all variables.
# Otherwise a system limit (for SysV at least) may be exceeded.
.NOEXPORT:
+6
View File
@@ -0,0 +1,6 @@
/** Handling of SQL arrays.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/array.hxx"
#include "pqxx/internal/header-post.hxx"
+103
View File
@@ -0,0 +1,103 @@
/* Handling of SQL arrays.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/field instead.
*
* 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.
*/
#ifndef PQXX_H_ARRAY
#define PQXX_H_ARRAY
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <stdexcept>
#include <string>
#include <utility>
#include "pqxx/internal/encoding_group.hxx"
#include "pqxx/internal/encodings.hxx"
namespace pqxx
{
/// Low-level array parser.
/** Use this to read an array field retrieved from the database.
*
* This parser will only work reliably if your client encoding is UTF-8, ASCII,
* or a single-byte encoding which is a superset of ASCII (such as Latin-1).
*
* Also, the parser only supports array element types which use either a comma
* or a semicolon ("," or ";") as the separator between array elements. All
* built-in types use comma, except for one which uses semicolon, but some
* custom types may not work.
*
* The input is a C-style string containing the textual representation of an
* array, as returned by the database. The parser reads this representation
* on the fly. The string must remain in memory until parsing is done.
*
* Parse the array by making calls to @ref get_next until it returns a
* @ref juncture of "done". The @ref juncture tells you what the parser found
* in that step: did the array "nest" to a deeper level, or "un-nest" back up?
*/
class PQXX_LIBEXPORT array_parser
{
public:
/// What's the latest thing found in the array?
enum class juncture
{
/// Starting a new row.
row_start,
/// Ending the current row.
row_end,
/// Found a NULL value.
null_value,
/// Found a string value.
string_value,
/// Parsing has completed.
done,
};
// TODO: constexpr noexcept. Breaks ABI.
/// Constructor. You don't need this; use @ref field::as_array instead.
/** The parser only remains valid while the data underlying the @ref result
* remains valid. Once all `result` objects referring to that data have been
* destroyed, the parser will no longer refer to valid memory.
*/
explicit array_parser(
std::string_view input,
internal::encoding_group = internal::encoding_group::MONOBYTE);
/// Parse the next step in the array.
/** Returns what it found. If the juncture is @ref juncture::string_value,
* the string will contain the value. Otherwise, it will be empty.
*
* Call this until the @ref array_parser::juncture it returns is
* @ref juncture::done.
*/
std::pair<juncture, std::string> get_next();
private:
std::string_view m_input;
internal::glyph_scanner_func *const m_scan;
/// Current parsing position in the input.
std::string::size_type m_pos = 0u;
std::string::size_type scan_single_quoted_string() const;
std::string parse_single_quoted_string(std::string::size_type end) const;
std::string::size_type scan_double_quoted_string() const;
std::string parse_double_quoted_string(std::string::size_type end) const;
std::string::size_type scan_unquoted_string() const;
std::string parse_unquoted_string(std::string::size_type end) const;
std::string::size_type scan_glyph(std::string::size_type pos) const;
std::string::size_type
scan_glyph(std::string::size_type pos, std::string::size_type end) const;
};
} // namespace pqxx
#endif
@@ -0,0 +1,6 @@
/** BYTEA (binary string) conversions.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/binarystring.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,236 @@
/* Deprecated representation for raw, binary data.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/binarystring instead.
*
* 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.
*/
#ifndef PQXX_H_BINARYSTRING
#define PQXX_H_BINARYSTRING
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <memory>
#include <string>
#include <string_view>
#include "pqxx/result.hxx"
#include "pqxx/strconv.hxx"
namespace pqxx
{
class binarystring;
template<> struct string_traits<binarystring>;
/// Binary data corresponding to PostgreSQL's "BYTEA" binary-string type.
/** @ingroup escaping-functions
* @deprecated Use @c std::basic_string<std::byte> and
* @c std::basic_string_view<std::byte> for binary data. In C++20 or better,
* any @c contiguous_range of @c std::byte will do.
*
* This class represents a binary string as stored in a field of type @c bytea.
*
* Internally a binarystring is zero-terminated, but it may also contain null
* bytes, they're just like any other byte value. So don't assume that it's
* safe to treat the contents as a C-style string.
*
* The binarystring retains its value even if the result it was obtained from
* is destroyed, but it cannot be copied or assigned.
*
* \relatesalso transaction_base::quote_raw
*
* To include a @c binarystring value in an SQL query, escape and quote it
* using the transaction's @c quote_raw function.
*
* @warning This class is implemented as a reference-counting smart pointer.
* Copying, swapping, and destroying binarystring objects that refer to the
* same underlying data block is <em>not thread-safe</em>. If you wish to pass
* binarystrings around between threads, make sure that each of these
* operations is protected against concurrency with similar operations on the
* same object, or other objects pointing to the same data block.
*/
class PQXX_LIBEXPORT binarystring
{
public:
using char_type = unsigned char;
using value_type = std::char_traits<char_type>::char_type;
using size_type = std::size_t;
using difference_type = long;
using const_reference = value_type const &;
using const_pointer = value_type const *;
using const_iterator = const_pointer;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
[[deprecated("Use std::byte for binary data.")]] binarystring(
binarystring const &) = default;
/// Read and unescape bytea field.
/** The field will be zero-terminated, even if the original bytea field
* isn't.
* @param F the field to read; must be a bytea field
*/
[[deprecated("Use std::byte for binary data.")]] explicit binarystring(
field const &);
/// Copy binary data from std::string_view on binary data.
/** This is inefficient in that it copies the data to a buffer allocated on
* the heap.
*/
[[deprecated("Use std::byte for binary data.")]] explicit binarystring(
std::string_view);
/// Copy binary data of given length straight out of memory.
[[deprecated("Use std::byte for binary data.")]] binarystring(
void const *, std::size_t);
/// Efficiently wrap a buffer of binary data in a @c binarystring.
[[deprecated("Use std::byte for binary data.")]] binarystring(
std::shared_ptr<value_type> ptr, size_type size) :
m_buf{std::move(ptr)}, m_size{size}
{}
/// Size of converted string in bytes.
[[nodiscard]] size_type size() const noexcept { return m_size; }
/// Size of converted string in bytes.
[[nodiscard]] size_type length() const noexcept { return size(); }
[[nodiscard]] bool empty() const noexcept { return size() == 0; }
[[nodiscard]] const_iterator begin() const noexcept { return data(); }
[[nodiscard]] const_iterator cbegin() const noexcept { return begin(); }
[[nodiscard]] const_iterator end() const noexcept { return data() + m_size; }
[[nodiscard]] const_iterator cend() const noexcept { return end(); }
[[nodiscard]] const_reference front() const noexcept { return *begin(); }
[[nodiscard]] const_reference back() const noexcept
{
return *(data() + m_size - 1);
}
[[nodiscard]] const_reverse_iterator rbegin() const
{
return const_reverse_iterator{end()};
}
[[nodiscard]] const_reverse_iterator crbegin() const { return rbegin(); }
[[nodiscard]] const_reverse_iterator rend() const
{
return const_reverse_iterator{begin()};
}
[[nodiscard]] const_reverse_iterator crend() const { return rend(); }
/// Unescaped field contents.
[[nodiscard]] value_type const *data() const noexcept { return m_buf.get(); }
[[nodiscard]] const_reference operator[](size_type i) const noexcept
{
return data()[i];
}
[[nodiscard]] PQXX_PURE bool operator==(binarystring const &) const noexcept;
[[nodiscard]] bool operator!=(binarystring const &rhs) const noexcept
{
return not operator==(rhs);
}
binarystring &operator=(binarystring const &);
/// Index contained string, checking for valid index.
const_reference at(size_type) const;
/// Swap contents with other binarystring.
void swap(binarystring &);
/// Raw character buffer (no terminating zero is added).
/** @warning No terminating zero is added! If the binary data did not end in
* a null character, you will not find one here.
*/
[[nodiscard]] char const *get() const noexcept
{
return reinterpret_cast<char const *>(m_buf.get());
}
/// Read contents as a std::string_view.
[[nodiscard]] std::string_view view() const noexcept
{
return std::string_view(get(), size());
}
/// Read as regular C++ string (may include null characters).
/** This creates and returns a new string object. Don't call this
* repeatedly; retrieve your string once and keep it in a local variable.
* Also, do not expect to be able to compare the string's address to that of
* an earlier invocation.
*/
[[nodiscard]] std::string str() const;
/// Access data as a pointer to @c std::byte.
[[nodiscard]] std::byte const *bytes() const
{
return reinterpret_cast<std::byte const *>(get());
}
/// Read data as a @c std::basic_string_view<std::byte>.
[[nodiscard]] std::basic_string_view<std::byte> bytes_view() const
{
return std::basic_string_view<std::byte>{bytes(), size()};
}
private:
std::shared_ptr<value_type> m_buf;
size_type m_size{0};
};
template<> struct nullness<binarystring> : no_null<binarystring>
{};
/// String conversion traits for @c binarystring.
/** Defines the conversions between a @c binarystring and its PostgreSQL
* textual format, for communication with the database.
*
* These conversions rely on the "hex" format which was introduced in
* PostgreSQL 9.0. Both your libpq and the server must be recent enough to
* speak this format.
*/
template<> struct string_traits<binarystring>
{
static std::size_t size_buffer(binarystring const &value) noexcept
{
return internal::size_esc_bin(std::size(value));
}
static zview to_buf(char *begin, char *end, binarystring const &value)
{
return generic_to_buf(begin, end, value);
}
static char *into_buf(char *begin, char *end, binarystring const &value)
{
auto const budget{size_buffer(value)};
if (internal::cmp_less(end - begin, budget))
throw conversion_overrun{
"Not enough buffer space to escape binary data."};
std::string_view text{value.view()};
internal::esc_bin(binary_cast(text), begin);
return begin + budget;
}
static binarystring from_string(std::string_view text)
{
auto const size{pqxx::internal::size_unesc_bin(std::size(text))};
std::shared_ptr<unsigned char> buf{
new unsigned char[size], [](unsigned char const *x) { delete[] x; }};
pqxx::internal::unesc_bin(text, reinterpret_cast<std::byte *>(buf.get()));
#include "pqxx/internal/ignore-deprecated-pre.hxx"
return binarystring{std::move(buf), size};
#include "pqxx/internal/ignore-deprecated-post.hxx"
}
};
} // namespace pqxx
#endif
+6
View File
@@ -0,0 +1,6 @@
/** Binary Large Objects interface.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/blob.hxx"
#include "pqxx/internal/header-post.hxx"
+351
View File
@@ -0,0 +1,351 @@
/* Binary Large Objects interface.
*
* Read or write large objects, stored in their own storage on the server.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/largeobject instead.
*
* 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.
*/
#ifndef PQXX_H_BLOB
#define PQXX_H_BLOB
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <cstdint>
#if defined(PQXX_HAVE_PATH)
# include <filesystem>
#endif
#if defined(PQXX_HAVE_RANGES) && __has_include(<ranges>)
# include <ranges>
#endif
#if defined(PQXX_HAVE_SPAN) && __has_include(<span>)
# include <span>
#endif
#include "pqxx/dbtransaction.hxx"
namespace pqxx
{
/** Binary large object.
*
* This is how you store data that may be too large for the `BYTEA` type.
* Access operations are similar to those for a file: you can read, write,
* query or set the current reading/writing position, and so on.
*
* These large objects live in their own storage on the server, indexed by an
* integer object identifier ("oid").
*
* Two `blob` objects may refer to the same actual large object in the
* database at the same time. Each will have its own reading/writing position,
* but writes to the one will of course affect what the other sees.
*/
class PQXX_LIBEXPORT blob
{
public:
/// Create a new, empty large object.
/** You may optionally specify an oid for the new blob. If you do, then
* the new object will have that oid -- or creation will fail if there
* already is an object with that oid.
*/
[[nodiscard]] static oid create(dbtransaction &, oid = 0);
/// Delete a large object, or fail if it does not exist.
static void remove(dbtransaction &, oid);
/// Open blob for reading. Any attempt to write to it will fail.
[[nodiscard]] static blob open_r(dbtransaction &, oid);
// Open blob for writing. Any attempt to read from it will fail.
[[nodiscard]] static blob open_w(dbtransaction &, oid);
// Open blob for reading and/or writing.
[[nodiscard]] static blob open_rw(dbtransaction &, oid);
/// You can default-construct a blob, but it won't do anything useful.
/** Most operations on a default-constructed blob will throw @ref
* usage_error.
*/
blob() = default;
/// You can move a blob, but not copy it. The original becomes unusable.
blob(blob &&);
/// You can move a blob, but not copy it. The original becomes unusable.
blob &operator=(blob &&);
blob(blob const &) = delete;
blob &operator=(blob const &) = delete;
~blob();
/// Maximum number of bytes that can be read or written at a time.
/** The underlying protocol only supports reads and writes up to 2 GB
* exclusive.
*
* If you need to read or write more data to or from a binary large object,
* you'll have to break it up into chunks.
*/
static constexpr std::size_t chunk_limit = 0x7fffffff;
/// Read up to `size` bytes of the object into `buf`.
/** Uses a buffer that you provide, resizing it as needed. If it suits you,
* this lets you allocate the buffer once and then re-use it multiple times.
*
* Resizes `buf` as needed.
*
* @warning The underlying protocol only supports reads up to 2GB at a time.
* If you need to read more, try making repeated calls to @ref append_to_buf.
*/
std::size_t read(std::basic_string<std::byte> &buf, std::size_t size);
#if defined(PQXX_HAVE_SPAN)
/// Read up to `std::size(buf)` bytes from the object.
/** Retrieves bytes from the blob, at the current position, until `buf` is
* full or there are no more bytes to read, whichever comes first.
*
* Returns the filled portion of `buf`. This may be empty.
*/
template<std::size_t extent = std::dynamic_extent>
std::span<std::byte> read(std::span<std::byte, extent> buf)
{
return buf.subspan(0, raw_read(std::data(buf), std::size(buf)));
}
#endif // PQXX_HAVE_SPAN
#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN)
/// Read up to `std::size(buf)` bytes from the object.
/** Retrieves bytes from the blob, at the current position, until `buf` is
* full or there are no more bytes to read, whichever comes first.
*
* Returns the filled portion of `buf`. This may be empty.
*/
template<binary DATA> std::span<std::byte> read(DATA &buf)
{
return {std::data(buf), raw_read(std::data(buf), std::size(buf))};
}
#else // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN
/// Read up to `std::size(buf)` bytes from the object.
/** @deprecated As libpqxx moves to C++20 as its baseline language version,
* this will take and return `std::span<std::byte>`.
*
* Retrieves bytes from the blob, at the current position, until `buf` is
* full (i.e. its current size is reached), or there are no more bytes to
* read, whichever comes first.
*
* This function will not change either the size or the capacity of `buf`,
* only its contents.
*
* Returns the filled portion of `buf`. This may be empty.
*/
template<typename ALLOC>
std::basic_string_view<std::byte> read(std::vector<std::byte, ALLOC> &buf)
{
return {std::data(buf), raw_read(std::data(buf), std::size(buf))};
}
#endif // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN
#if defined(PQXX_HAVE_CONCEPTS)
/// Write `data` to large object, at the current position.
/** If the writing position is at the end of the object, this will append
* `data` to the object's contents and move the writing position so that
* it's still at the end.
*
* If the writing position was not at the end, writing will overwrite the
* prior data, but it will not remove data that follows the part where you
* wrote your new data.
*
* @warning This is a big difference from writing to a file. You can
* overwrite some data in a large object, but this does not truncate the
* data that was already there. For example, if the object contained binary
* data "abc", and you write "12" at the starting position, the object will
* contain "12c".
*
* @warning The underlying protocol only supports writes up to 2 GB at a
* time. If you need to write more, try making repeated calls to
* @ref append_from_buf.
*/
template<binary DATA> void write(DATA const &data)
{
raw_write(std::data(data), std::size(data));
}
#else
/// Write `data` large object, at the current position.
/** If the writing position is at the end of the object, this will append
* `data` to the object's contents and move the writing position so that
* it's still at the end.
*
* If the writing position was not at the end, writing will overwrite the
* prior data, but it will not remove data that follows the part where you
* wrote your new data.
*
* @warning This is a big difference from writing to a file. You can
* overwrite some data in a large object, but this does not truncate the
* data that was already there. For example, if the object contained binary
* data "abc", and you write "12" at the starting position, the object will
* contain "12c".
*
* @warning The underlying protocol only supports writes up to 2 GB at a
* time. If you need to write more, try making repeated calls to
* @ref append_from_buf.
*/
template<typename DATA> void write(DATA const &data)
{
raw_write(std::data(data), std::size(data));
}
#endif
/// Resize large object to `size` bytes.
/** If the blob is more than `size` bytes long, this removes the end so as
* to make the blob the desired length.
*
* If the blob is less than `size` bytes long, it adds enough zero bytes to
* make it the desired length.
*/
void resize(std::int64_t size);
/// Return the current reading/writing position in the large object.
[[nodiscard]] std::int64_t tell() const;
/// Set the current reading/writing position to an absolute offset.
/** Returns the new file offset. */
std::int64_t seek_abs(std::int64_t offset = 0);
/// Move the current reading/writing position forwards by an offset.
/** To move backwards, pass a negative offset.
*
* Returns the new file offset.
*/
std::int64_t seek_rel(std::int64_t offset = 0);
/// Set the current position to an offset relative to the end of the blob.
/** You'll probably want an offset of zero or less.
*
* Returns the new file offset.
*/
std::int64_t seek_end(std::int64_t offset = 0);
/// Create a binary large object containing given `data`.
/** You may optionally specify an oid for the new object. If you do, and an
* object with that oid already exists, creation will fail.
*/
static oid from_buf(
dbtransaction &tx, std::basic_string_view<std::byte> data, oid id = 0);
/// Append `data` to binary large object.
/** The underlying protocol only supports appending blocks up to 2 GB.
*/
static void append_from_buf(
dbtransaction &tx, std::basic_string_view<std::byte> data, oid id);
/// Read client-side file and store it server-side as a binary large object.
[[nodiscard]] static oid from_file(dbtransaction &, char const path[]);
#if defined(PQXX_HAVE_PATH) && !defined(_WIN32)
/// Read client-side file and store it server-side as a binary large object.
/** This overload is not available on Windows, where `std::filesystem::path`
* converts to a `wchar_t` string rather than a `char` string.
*/
[[nodiscard]] static oid
from_file(dbtransaction &tx, std::filesystem::path const &path)
{
return from_file(tx, path.c_str());
}
#endif
/// Read client-side file and store it server-side as a binary large object.
/** In this version, you specify the binary large object's oid. If that oid
* is already in use, the operation will fail.
*/
static oid from_file(dbtransaction &, char const path[], oid);
#if defined(PQXX_HAVE_PATH) && !defined(_WIN32)
/// Read client-side file and store it server-side as a binary large object.
/** In this version, you specify the binary large object's oid. If that oid
* is already in use, the operation will fail.
*
* This overload is not available on Windows, where `std::filesystem::path`
* converts to a `wchar_t` string rather than a `char` string.
*/
static oid
from_file(dbtransaction &tx, std::filesystem::path const &path, oid id)
{
return from_file(tx, path.c_str(), id);
}
#endif
/// Convenience function: Read up to `max_size` bytes from blob with `id`.
/** You could easily do this yourself using the @ref open_r and @ref read
* functions, but it can save you a bit of code to do it this way.
*/
static void to_buf(
dbtransaction &, oid, std::basic_string<std::byte> &,
std::size_t max_size);
/// Read part of the binary large object with `id`, and append it to `buf`.
/** Use this to break up a large read from one binary large object into one
* massive buffer. Just keep calling this function until it returns zero.
*
* The `offset` is how far into the large object your desired chunk is, and
* `append_max` says how much to try and read in one go.
*/
static std::size_t append_to_buf(
dbtransaction &tx, oid id, std::int64_t offset,
std::basic_string<std::byte> &buf, std::size_t append_max);
/// Write a binary large object's contents to a client-side file.
static void to_file(dbtransaction &, oid, char const path[]);
#if defined(PQXX_HAVE_PATH) && !defined(_WIN32)
/// Write a binary large object's contents to a client-side file.
/** This overload is not available on Windows, where `std::filesystem::path`
* converts to a `wchar_t` string rather than a `char` string.
*/
static void
to_file(dbtransaction &tx, oid id, std::filesystem::path const &path)
{
to_file(tx, id, path.c_str());
}
#endif
/// Close this blob.
/** This does not delete the blob from the database; it only terminates your
* local object for accessing the blob.
*
* Resets the blob to a useless state similar to one that was
* default-constructed.
*
* The destructor will do this for you automatically. Still, there is a
* reason to `close()` objects explicitly where possible: if an error should
* occur while closing, `close()` can throw an exception. A destructor
* cannot.
*/
void close();
private:
PQXX_PRIVATE blob(connection &conn, int fd) noexcept :
m_conn{&conn}, m_fd{fd}
{}
static PQXX_PRIVATE blob open_internal(dbtransaction &, oid, int);
static PQXX_PRIVATE pqxx::internal::pq::PGconn *
raw_conn(pqxx::connection *) noexcept;
static PQXX_PRIVATE pqxx::internal::pq::PGconn *
raw_conn(pqxx::dbtransaction const &) noexcept;
static PQXX_PRIVATE std::string errmsg(connection const *);
static PQXX_PRIVATE std::string errmsg(dbtransaction const &tx)
{
return errmsg(&tx.conn());
}
PQXX_PRIVATE std::string errmsg() const { return errmsg(m_conn); }
PQXX_PRIVATE std::int64_t seek(std::int64_t offset, int whence);
std::size_t raw_read(std::byte buf[], std::size_t size);
void raw_write(std::byte const buf[], std::size_t size);
connection *m_conn = nullptr;
int m_fd = -1;
};
} // namespace pqxx
#endif
+6
View File
@@ -0,0 +1,6 @@
/** Handling of SQL "composite types."
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/composite.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,149 @@
#ifndef PQXX_H_COMPOSITE
#define PQXX_H_COMPOSITE
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include "pqxx/internal/array-composite.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/util.hxx"
namespace pqxx
{
/// Parse a string representation of a value of a composite type.
/** @warning This code is still experimental. Use with care.
*
* You may use this as a helper while implementing your own @ref string_traits
* for a composite type.
*
* This function interprets `text` as the string representation of a value of
* some composite type, and sets each of `fields` to the respective values of
* its fields. The field types must be copy-assignable.
*
* The number of fields must match the number of fields in the composite type,
* and there must not be any other text in the input. The function is meant to
* handle any value string that the backend can produce, but not necessarily
* every valid alternative spelling.
*
* Fields in composite types can be null. When this happens, the C++ type of
* the corresponding field reference must be of a type that can handle nulls.
* If you are working with a type that does not have an inherent null value,
* such as e.g. `int`, consider using `std::optional`.
*/
template<typename... T>
inline void parse_composite(
pqxx::internal::encoding_group enc, std::string_view text, T &...fields)
{
static_assert(sizeof...(fields) > 0);
auto const scan{pqxx::internal::get_glyph_scanner(enc)};
auto const data{std::data(text)};
auto const size{std::size(text)};
if (size == 0)
throw conversion_error{"Cannot parse composite value from empty string."};
std::size_t here{0}, next{scan(data, size, here)};
if (next != 1 or data[here] != '(')
throw conversion_error{
internal::concat("Invalid composite value string: ", text)};
here = next;
constexpr auto num_fields{sizeof...(fields)};
std::size_t index{0};
(pqxx::internal::parse_composite_field(
index, text, here, fields, scan, num_fields - 1),
...);
if (here != std::size(text))
throw conversion_error{internal::concat(
"Composite value did not end at the closing parenthesis: '", text,
"'.")};
if (text[here - 1] != ')')
throw conversion_error{internal::concat(
"Composive value did not end in parenthesis: '", text, "'")};
}
/// Parse a string representation of a value of a composite type.
/** @warning This version only works for UTF-8 and single-byte encodings.
*
* For proper encoding support, use the composite-type support in the
* `field` class.
*/
template<typename... T>
inline void parse_composite(std::string_view text, T &...fields)
{
parse_composite(pqxx::internal::encoding_group::MONOBYTE, text, fields...);
}
} // namespace pqxx
namespace pqxx::internal
{
constexpr char empty_composite_str[]{"()"};
} // namespace pqxx::internal
namespace pqxx
{
/// Estimate the buffer size needed to represent a value of a composite type.
/** Returns a conservative estimate.
*/
template<typename... T>
[[nodiscard]] inline std::size_t
composite_size_buffer(T const &...fields) noexcept
{
constexpr auto num{sizeof...(fields)};
// Size for a multi-field composite includes room for...
// + opening parenthesis
// + field budgets
// + separating comma per field
// - comma after final field
// + closing parenthesis
// + terminating zero
if constexpr (num == 0)
return std::size(pqxx::internal::empty_composite_str);
else
return 1 + (pqxx::internal::size_composite_field_buffer(fields) + ...) +
num + 1;
}
/// Render a series of values as a single composite SQL value.
/** @warning This code is still experimental. Use with care.
*
* You may use this as a helper while implementing your own `string_traits`
* for a composite type.
*/
template<typename... T>
inline char *composite_into_buf(char *begin, char *end, T const &...fields)
{
if (std::size_t(end - begin) < composite_size_buffer(fields...))
throw conversion_error{
"Buffer space may not be enough to represent composite value."};
constexpr auto num_fields{sizeof...(fields)};
if constexpr (num_fields == 0)
{
constexpr char empty[]{"()"};
std::memcpy(begin, empty, std::size(empty));
return begin + std::size(empty);
}
char *pos{begin};
*pos++ = '(';
(pqxx::internal::write_composite_field<T>(pos, end, fields), ...);
// If we've got multiple fields, "backspace" that last comma.
if constexpr (num_fields > 1)
--pos;
*pos++ = ')';
*pos++ = '\0';
return pos;
}
} // namespace pqxx
#endif
+121
View File
@@ -0,0 +1,121 @@
/* include/pqxx/config.h.in. Generated from configure.ac by autoheader. */
/* Define to 1 if you have the <dlfcn.h> header file. */
#undef HAVE_DLFCN_H
/* Define to 1 if you have the <inttypes.h> header file. */
#undef HAVE_INTTYPES_H
/* Define to 1 if you have the `pq' library (-lpq). */
#undef HAVE_LIBPQ
/* Define to 1 if you have the <memory.h> header file. */
#undef HAVE_MEMORY_H
/* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H
/* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H
/* Define to 1 if you have the <strings.h> header file. */
#undef HAVE_STRINGS_H
/* Define to 1 if you have the <string.h> header file. */
#undef HAVE_STRING_H
/* Define to 1 if you have the <sys/stat.h> header file. */
#undef HAVE_SYS_STAT_H
/* Define to 1 if you have the <sys/types.h> header file. */
#undef HAVE_SYS_TYPES_H
/* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H
/* Define to the sub-directory where libtool stores uninstalled libraries. */
#undef LT_OBJDIR
/* Name of package */
#undef PACKAGE
/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT
/* Define to the full name of this package. */
#undef PACKAGE_NAME
/* Define to the full name and version of this package. */
#undef PACKAGE_STRING
/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME
/* Define to the home page for this package. */
#undef PACKAGE_URL
/* Define to the version of this package. */
#undef PACKAGE_VERSION
/* Define if <charconv> supports floating-point conversion. */
#undef PQXX_HAVE_CHARCONV_FLOAT
/* Define if <charconv> supports integer conversion. */
#undef PQXX_HAVE_CHARCONV_INT
/* Define if compiler has C++20 std::cmp_greater etc. */
#undef PQXX_HAVE_CMP
/* Define if compiler supports Concepts and <ranges> header. */
#undef PQXX_HAVE_CONCEPTS
/* Define if compiler supports __cxa_demangle */
#undef PQXX_HAVE_CXA_DEMANGLE
/* Define if g++ supports pure attribute */
#undef PQXX_HAVE_GCC_PURE
/* Define if g++ supports visibility attribute. */
#undef PQXX_HAVE_GCC_VISIBILITY
/* Define if likely & unlikely work. */
#undef PQXX_HAVE_LIKELY
/* Define if operator[] can take multiple arguments. */
#undef PQXX_HAVE_MULTIDIMENSIONAL_SUBSCRIPT
/* Define if compiler has usable std::filesystem::path. */
#undef PQXX_HAVE_PATH
/* Define if poll() is available. */
#undef PQXX_HAVE_POLL
/* Define if libpq has PQencryptPasswordConn (since pg 10). */
#undef PQXX_HAVE_PQENCRYPTPASSWORDCONN
/* Define if libpq has pipeline mode (since pg 14). */
#undef PQXX_HAVE_PQ_PIPELINE
/* Define if std::this_thread::sleep_for works. */
#undef PQXX_HAVE_SLEEP_FOR
/* Define if compiler has std::span. */
#undef PQXX_HAVE_SPAN
/* Define if strerror_r() is available. */
#undef PQXX_HAVE_STRERROR_R
/* Define if strerror_s() is available. */
#undef PQXX_HAVE_STRERROR_S
/* Define if thread_local is fully supported. */
#undef PQXX_HAVE_THREAD_LOCAL
/* Define if std::chrono has year_month_day etc. */
#undef PQXX_HAVE_YEAR_MONTH_DAY
/* Define to 1 if you have the ANSI C header files. */
#undef STDC_HEADERS
/* Version number of package */
#undef VERSION
@@ -0,0 +1,8 @@
/** pqxx::connection class.
*
* pqxx::connection encapsulates a connection to a database.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/connection.hxx"
#include "pqxx/internal/header-post.hxx"
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
/** Definition of the iterator/container-style cursor classes.
*
* C++-style wrappers for SQL cursors
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/cursor.hxx"
#include "pqxx/internal/header-post.hxx"
+483
View File
@@ -0,0 +1,483 @@
/* Definition of the iterator/container-style cursor classes.
*
* C++-style wrappers for SQL cursors.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/cursor instead.
*
* 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.
*/
#ifndef PQXX_H_CURSOR
#define PQXX_H_CURSOR
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <limits>
#include <stdexcept>
#include "pqxx/result.hxx"
#include "pqxx/transaction_base.hxx"
namespace pqxx
{
/// Common definitions for cursor types
/** In C++ terms, fetches are always done in pre-increment or pre-decrement
* fashion--i.e. the result does not include the row the cursor is on at the
* beginning of the fetch, and the cursor ends up being positioned on the last
* row in the result.
*
* There are singular positions akin to `end()` at both the beginning and the
* end of the cursor's range of movement, although these fit in so naturally
* with the semantics that one rarely notices them. The cursor begins at the
* first of these, but any fetch in the forward direction will move the cursor
* off this position and onto the first row before returning anything.
*/
class PQXX_LIBEXPORT cursor_base
{
public:
using size_type = result_size_type;
using difference_type = result_difference_type;
/// Cursor access-pattern policy
/** Allowing a cursor to move forward only can result in better performance,
* so use this access policy whenever possible.
*/
enum access_policy
{
/// Cursor can move forward only
forward_only,
/// Cursor can move back and forth
random_access
};
/// Cursor update policy
/**
* @warning Not all PostgreSQL versions support updatable cursors.
*/
enum update_policy
{
/// Cursor can be used to read data but not to write
read_only,
/// Cursor can be used to update data as well as read it
update
};
/// Cursor destruction policy
/** The normal thing to do is to make a cursor object the owner of the SQL
* cursor it represents. There may be cases, however, where a cursor needs
* to persist beyond the end of the current transaction (and thus also beyond
* the lifetime of the cursor object that created it!), where it can be
* "adopted" into a new cursor object. See the basic_cursor documentation
* for an explanation of cursor adoption.
*
* If a cursor is created with "loose" ownership policy, the object
* representing the underlying SQL cursor will not take the latter with it
* when its own lifetime ends, nor will its originating transaction.
*
* @warning Use this feature with care and moderation. Only one cursor
* object should be responsible for any one underlying SQL cursor at any
* given time.
*/
enum ownership_policy
{
/// Destroy SQL cursor when cursor object is closed at end of transaction
owned,
/// Leave SQL cursor in existence after close of object and transaction
loose
};
cursor_base() = delete;
cursor_base(cursor_base const &) = delete;
cursor_base &operator=(cursor_base const &) = delete;
/**
* @name Special movement distances.
*/
//@{
// TODO: Make constexpr inline (but breaks ABI).
/// Special value: read until end.
/** @return Maximum value for result::difference_type, so the cursor will
* attempt to read the largest possible result set.
*/
[[nodiscard]] static difference_type all() noexcept;
/// Special value: read one row only.
/** @return Unsurprisingly, 1.
*/
[[nodiscard]] static constexpr difference_type next() noexcept { return 1; }
/// Special value: read backwards, one row only.
/** @return Unsurprisingly, -1.
*/
[[nodiscard]] static constexpr difference_type prior() noexcept
{
return -1;
}
// TODO: Make constexpr inline (but breaks ABI).
/// Special value: read backwards from current position back to origin.
/** @return Minimum value for result::difference_type.
*/
[[nodiscard]] static difference_type backward_all() noexcept;
//@}
/// Name of underlying SQL cursor
/**
* @returns Name of SQL cursor, which may differ from original given name.
* @warning Don't use this to access the SQL cursor directly without going
* through the provided wrapper classes!
*/
[[nodiscard]] constexpr std::string const &name() const noexcept
{
return m_name;
}
protected:
cursor_base(connection &, std::string_view Name, bool embellish_name = true);
std::string const m_name;
};
} // namespace pqxx
#include <pqxx/internal/sql_cursor.hxx>
namespace pqxx
{
/// "Stateless cursor" class: easy API for retrieving parts of result sets
/** This is a front-end for SQL cursors, but with a more C++-like API.
*
* Actually, stateless_cursor feels entirely different from SQL cursors. You
* don't keep track of positions, fetches, and moves; you just say which rows
* you want. See the retrieve() member function.
*/
template<cursor_base::update_policy up, cursor_base::ownership_policy op>
class stateless_cursor
{
public:
using size_type = result_size_type;
using difference_type = result_difference_type;
/// Create cursor.
/**
* @param tx The transaction within which you want to create the cursor.
* @param query The SQL query whose results the cursor should traverse.
* @param cname A hint for the cursor's name. The actual SQL cursor's name
* will be based on this (though not necessarily identical).
* @param hold Create a `WITH HOLD` cursor? Such cursors stay alive after
* the transaction has ended, so you can continue to use it.
*/
stateless_cursor(
transaction_base &tx, std::string_view query, std::string_view cname,
bool hold) :
m_cur{tx, query, cname, cursor_base::random_access, up, op, hold}
{}
/// Adopt an existing scrolling SQL cursor.
/** This lets you define a cursor yourself, and then wrap it in a
* libpqxx-managed `stateless_cursor` object.
*
* @param tx The transaction within which you want to manage the cursor.
* @param adopted_cursor Your cursor's SQL name.
*/
stateless_cursor(transaction_base &tx, std::string_view adopted_cursor) :
m_cur{tx, adopted_cursor, op}
{
// Put cursor in known position
m_cur.move(cursor_base::backward_all());
}
/// Close this cursor.
/** The destructor will do this for you automatically.
*
* Closing a cursor is idempotent. Closing a cursor that's already closed
* does nothing.
*/
void close() noexcept { m_cur.close(); }
/// Number of rows in cursor's result set
/** @note This function is not const; it may need to scroll to find the size
* of the result set.
*/
[[nodiscard]] size_type size()
{
return internal::obtain_stateless_cursor_size(m_cur);
}
/// Retrieve rows from begin_pos (inclusive) to end_pos (exclusive)
/** Rows are numbered starting from 0 to size()-1.
*
* @param begin_pos First row to retrieve. May be one row beyond the end of
* the result set, to avoid errors for empty result sets. Otherwise, must be
* a valid row number in the result set.
* @param end_pos Row up to which to fetch. Rows are returned ordered from
* begin_pos to end_pos, i.e. in ascending order if begin_pos < end_pos but
* in descending order if begin_pos > end_pos. The end_pos may be
* arbitrarily inside or outside the result set; only existing rows are
* included in the result.
*/
result retrieve(difference_type begin_pos, difference_type end_pos)
{
return internal::stateless_cursor_retrieve(
m_cur, result::difference_type(size()), begin_pos, end_pos);
}
/// Return this cursor's name.
[[nodiscard]] constexpr std::string const &name() const noexcept
{
return m_cur.name();
}
private:
internal::sql_cursor m_cur;
};
class icursor_iterator;
} // namespace pqxx
namespace pqxx::internal::gate
{
class icursor_iterator_icursorstream;
class icursorstream_icursor_iterator;
} // namespace pqxx::internal::gate
namespace pqxx
{
/// Simple read-only cursor represented as a stream of results
/** SQL cursors can be tricky, especially in C++ since the two languages seem
* to have been designed on different planets. An SQL cursor has two singular
* positions akin to `end()` on either side of the underlying result set.
*
* These cultural differences are hidden from view somewhat by libpqxx, which
* tries to make SQL cursors behave more like familiar C++ entities such as
* iterators, sequences, streams, and containers.
*
* Data is fetched from the cursor as a sequence of result objects. Each of
* these will contain the number of rows defined as the stream's stride, except
* of course the last block of data which may contain fewer rows.
*
* This class can create or adopt cursors that live outside any backend
* transaction, which your backend version may not support.
*/
class PQXX_LIBEXPORT icursorstream
{
public:
using size_type = cursor_base::size_type;
using difference_type = cursor_base::difference_type;
/// Set up a read-only, forward-only cursor.
/** Roughly equivalent to a C++ Standard Library istream, this cursor type
* supports only two operations: reading a block of rows while moving
* forward, and moving forward without reading any data.
*
* @param context Transaction context in which this cursor will be active.
* @param query SQL query whose results this cursor shall iterate.
* @param basename Suggested name for the SQL cursor; the library will append
* a unique code to ensure its uniqueness.
* @param sstride Number of rows to fetch per read operation; must be a
* positive number.
*/
icursorstream(
transaction_base &context, std::string_view query,
std::string_view basename, difference_type sstride = 1);
/// Adopt existing SQL cursor. Use with care.
/** Forms a cursor stream around an existing SQL cursor, as returned by e.g.
* a server-side function. The SQL cursor will be cleaned up by the stream's
* destructor as if it had been created by the stream; cleaning it up by hand
* or adopting the same cursor twice is an error.
*
* Passing the name of the cursor as a string is not allowed, both to avoid
* confusion with the other constructor and to discourage unnecessary use of
* adopted cursors.
*
* @warning It is technically possible to adopt a "WITH HOLD" cursor, i.e. a
* cursor that stays alive outside its creating transaction. However, any
* cursor stream (including the underlying SQL cursor, naturally) must be
* destroyed before its transaction context object is destroyed. Therefore
* the only way to use SQL's WITH HOLD feature is to adopt the cursor, but
* defer doing so until after entering the transaction context that will
* eventually destroy it.
*
* @param context Transaction context in which this cursor will be active.
* @param cname Result field containing the name of the SQL cursor to adopt.
* @param sstride Number of rows to fetch per read operation; must be a
* positive number.
* @param op Ownership policy. Determines whether the cursor underlying this
* stream will be destroyed when the stream is closed.
*/
icursorstream(
transaction_base &context, field const &cname, difference_type sstride = 1,
cursor_base::ownership_policy op = cursor_base::owned);
/// Return `true` if this stream may still return more data.
constexpr operator bool() const &noexcept { return not m_done; }
/// Read new value into given result object; same as operator `>>`.
/** The result set may continue any number of rows from zero to the chosen
* stride, inclusive. An empty result will only be returned if there are no
* more rows to retrieve.
*
* @param res Write the retrieved data into this result object.
* @return Reference to this very stream, to facilitate "chained" invocations
* ("C.get(r1).get(r2);")
*/
icursorstream &get(result &res)
{
res = fetchblock();
return *this;
}
/// Read new value into given result object; same as `get(result&)`.
/** The result set may continue any number of rows from zero to the chosen
* stride, inclusive. An empty result will only be returned if there are no
* more rows to retrieve.
*
* @param res Write the retrieved data into this result object.
* @return Reference to this very stream, to facilitate "chained" invocations
* ("C >> r1 >> r2;")
*/
icursorstream &operator>>(result &res) { return get(res); }
/// Move given number of rows forward without reading data.
/** Ignores any stride that you may have set. It moves by a given number of
* rows, not a number of strides.
*
* @return Reference to this stream itself, to facilitate "chained"
* invocations.
*/
icursorstream &ignore(std::streamsize n = 1) &;
/// Change stride, i.e. the number of rows to fetch per read operation.
/**
* @param stride Must be a positive number.
*/
void set_stride(difference_type stride) &;
[[nodiscard]] constexpr difference_type stride() const noexcept
{
return m_stride;
}
private:
result fetchblock();
friend class internal::gate::icursorstream_icursor_iterator;
size_type forward(size_type n = 1);
void insert_iterator(icursor_iterator *) noexcept;
void remove_iterator(icursor_iterator *) const noexcept;
void service_iterators(difference_type);
internal::sql_cursor m_cur;
difference_type m_stride;
difference_type m_realpos, m_reqpos;
mutable icursor_iterator *m_iterators;
bool m_done;
};
/// Approximate istream_iterator for icursorstream.
/** Intended as an implementation of an input_iterator (as defined by the C++
* Standard Library), this class supports only two basic operations: reading
* the current element, and moving forward. In addition to the minimal
* guarantees for istream_iterators, this class supports multiple successive
* reads of the same position (the current result set is cached in the
* iterator) even after copying and even after new data have been read from the
* stream. This appears to be a requirement for input_iterators. Comparisons
* are also supported in the general case.
*
* The iterator does not care about its own position, however. Moving an
* iterator forward moves the underlying stream forward and reads the data from
* the new stream position, regardless of the iterator's old position in the
* stream.
*
* The stream's stride defines the granularity for all iterator movement or
* access operations, i.e. "ici += 1" advances the stream by one stride's worth
* of rows, and "*ici++" reads one stride's worth of rows from the stream.
*
* @warning Do not read from the underlying stream or its cursor, move its read
* position, or change its stride, between the time the first icursor_iterator
* on it is created and the time its last icursor_iterator is destroyed.
*
* @warning Manipulating these iterators within the context of a single cursor
* stream is <em>not thread-safe</em>. Creating a new iterator, copying one,
* or destroying one affects the stream as a whole.
*/
class PQXX_LIBEXPORT icursor_iterator
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = result;
using pointer = result const *;
using reference = result const &;
using istream_type = icursorstream;
using size_type = istream_type::size_type;
using difference_type = istream_type::difference_type;
icursor_iterator() noexcept;
explicit icursor_iterator(istream_type &) noexcept;
icursor_iterator(icursor_iterator const &) noexcept;
~icursor_iterator() noexcept;
result const &operator*() const
{
refresh();
return m_here;
}
result const *operator->() const
{
refresh();
return &m_here;
}
icursor_iterator &operator++();
icursor_iterator operator++(int);
icursor_iterator &operator+=(difference_type);
icursor_iterator &operator=(icursor_iterator const &) noexcept;
[[nodiscard]] bool operator==(icursor_iterator const &rhs) const;
[[nodiscard]] bool operator!=(icursor_iterator const &rhs) const noexcept
{
return not operator==(rhs);
}
[[nodiscard]] bool operator<(icursor_iterator const &rhs) const;
[[nodiscard]] bool operator>(icursor_iterator const &rhs) const
{
return rhs < *this;
}
[[nodiscard]] bool operator<=(icursor_iterator const &rhs) const
{
return not(*this > rhs);
}
[[nodiscard]] bool operator>=(icursor_iterator const &rhs) const
{
return not(*this < rhs);
}
private:
void refresh() const;
friend class internal::gate::icursor_iterator_icursorstream;
difference_type pos() const noexcept { return m_pos; }
void fill(result const &);
icursorstream *m_stream{nullptr};
result m_here;
difference_type m_pos;
icursor_iterator *m_prev{nullptr}, *m_next{nullptr};
};
} // namespace pqxx
#endif
@@ -0,0 +1,8 @@
/** pqxx::dbtransaction abstract base class.
*
* pqxx::dbransaction defines a real transaction on the database.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/dbtransaction.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,70 @@
/* Definition of the pqxx::dbtransaction abstract base class.
*
* pqxx::dbransaction defines a real transaction on the database.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/dbtransaction instead.
*
* 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.
*/
#ifndef PQXX_H_DBTRANSACTION
#define PQXX_H_DBTRANSACTION
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include "pqxx/transaction_base.hxx"
namespace pqxx
{
/// Abstract transaction base class: bracket transactions on the database.
/**
* @ingroup transactions
*
* Use a dbtransaction-derived object such as "work" (transaction<>) to enclose
* operations on a database in a single "unit of work." This ensures that the
* whole series of operations either succeeds as a whole or fails completely.
* In no case will it leave half-finished work behind in the database.
*
* Once processing on a transaction has succeeded and any changes should be
* allowed to become permanent in the database, call commit(). If something
* has gone wrong and the changes should be forgotten, call abort() instead.
* If you do neither, an implicit abort() is executed at destruction time.
*
* It is an error to abort a transaction that has already been committed, or to
* commit a transaction that has already been aborted. Aborting an already
* aborted transaction or committing an already committed one is allowed, to
* make error handling easier. Repeated aborts or commits have no effect after
* the first one.
*
* Database transactions are not suitable for guarding long-running processes.
* If your transaction code becomes too long or too complex, consider ways to
* break it up into smaller ones. Unfortunately there is no universal recipe
* for this.
*
* The actual operations for committing/aborting the backend transaction are
* implemented by a derived class. The implementing concrete class must also
* call @ref close from its destructor.
*/
class PQXX_LIBEXPORT PQXX_NOVTABLE dbtransaction : public transaction_base
{
protected:
/// Begin transaction.
explicit dbtransaction(connection &c) : transaction_base{c} {}
/// Begin transaction.
dbtransaction(connection &c, std::string_view tname) :
transaction_base{c, tname}
{}
/// Begin transaction.
dbtransaction(
connection &c, std::string_view tname,
std::shared_ptr<std::string> rollback_cmd) :
transaction_base{c, tname, rollback_cmd}
{}
};
} // namespace pqxx
#endif
@@ -0,0 +1,157 @@
Accessing results and result rows {#accessing-results}
---------------------------------
When you execute a query using one of the transaction `exec` functions, you
normally get a `result` object back. A `result` is a container of `row`s.
(There are exceptions. The `exec1` functions expect exactly one row of data,
so they return just a `row`, not a full `result`.)
Result objects are an all-or-nothing affair. The `exec` function waits until
it's received all the result data, and then gives it to you in the form of the
`result`. _(There is a faster, easier way of executing simple queries, so see
"streaming rows" below as well.)_
For example, your code might do:
```cxx
pqxx::result r = tx.exec("SELECT * FROM mytable");
```
Now, how do you access the data inside `r`?
Result sets act as standard C++ containers of rows. Rows act as standard
C++ containers of fields. So the easiest way to go through them is:
```cxx
for (auto const &row: r)
{
for (auto const &field: row) std::cout << field.c_str() << '\t';
std::cout << '\n';
}
```
But results and rows also support other kinds of access. Array-style
indexing, for instance, such as `r[rownum]`:
```cxx
std::size_t const num_rows = std::size(r);
for (std::size_t rownum=0u; rownum < num_rows; ++rownum)
{
pqxx::row const row = r[rownum];
std::size_t const num_cols = std::size(row);
for (std::size_t colnum=0u; colnum < num_cols; ++colnum)
{
pqxx::field const field = row[colnum];
std::cout << field.c_str() << '\t';
}
std::cout << '\n';
}
```
Every row in the result has the same number of columns, so you don't need to
look up the number of fields again for each one:
```cxx
std::size_t const num_rows = std::size(r);
std::size_t const num_cols = r.columns();
for (std::size_t rownum=0u; rownum < num_rows; ++rownum)
{
pqxx::row const row = r[rownum];
for (std::size_t colnum=0u; colnum < num_cols; ++colnum)
{
pqxx::field const field = row[colnum];
std::cout << field.c_str() << '\t';
}
std::cout << '\n';
}
```
You can even address a field by indexing the `row` using the field's _name:_
```cxx
std::cout << row["salary"] << '\n';
```
But try not to do that if speed matters, because looking up the column by name
takes time. At least you'd want to look up the column index before your loop
and then use numerical indexes inside the loop.
For C++23 or better, there's also a two-dimensional array access operator:
```cxx
for (std::size_t rownum=0u; rownum < num_rows; ++rownum)
{
for (std::size_t colnum=0u; colnum < num_cols; ++colnum)
std::cout result[rownum, colnum].c_str() << '\t';
std::cout << '\n';
}
```
And of course you can use classic "begin/end" loops:
```cxx
for (auto row = std::begin(r); row != std::end(r); row++)
{
for (auto field = std::begin(row); field != std::end(row); field++)
std::cout << field->c_str() << '\t';
std::cout << '\n';
}
```
Result sets are immutable, so all iterators on results and rows are actually
`const_iterator`s. There are also `const_reverse_iterator` types, which
iterate backwards from `rbegin()` to `rend()` exclusive.
All these iterator types provide one extra bit of convenience that you won't
normally find in C++ iterators: referential transparency. You don't need to
dereference them to get to the row or field they refer to. That is, instead
of `row->end()` you can also choose to say `row.end()`. Similarly, you
may prefer `field.c_str()` over `field->c_str()`.
This becomes really helpful with the array-indexing operator. With regular
C++ iterators you would need ugly expressions like `(*row)[0]` or
`row->operator[](0)`. With the iterator types defined by the result and
row classes you can simply say `row[0]`.
Streaming rows
--------------
There's another way to go through the rows coming out of a query. It's
usually easier and faster, but there are drawbacks.
**One,** you start getting rows before all the data has come in from the
database. That speeds things up, but what happens if you lose your network
connection while transferring the data? Your application may already have
processed some of the data before finding out that the rest isn't coming. If
that is a problem for your application, streaming may not be the right choice.
**Two,** streaming only works for some types of query. The `stream()` function
wraps your query in a PostgreSQL `COPY` command, and `COPY` only supports a few
commands: `SELECT`, `VALUES`, `or an `INSERT`, `UPDATE`, or `DELETE` with a
`RETURNING` clause. See the `COPY` documentation here:
https://www.postgresql.org/docs/current/sql-copy.html
**Three,** when you convert a field to a "view" type (such as
`std::string_view` or `std::basic_string_view<std::byte>`), the view points to
underlying data which only stays valid until you iterate to the next row or
exit the loop. So if you want to use that data for longer than a single
iteration of the streaming loop, you'll have to store it somewhere yourself.
Now for the good news. Streaming does make it very easy to query data and loop
over it:
```cxx
for (auto [id, name, x, y] :
tx.stream<int, std::string_view, float, float>(
"SELECT id, name, x, y FROM point"))
process(id + 1, "point-" + name, x * 10.0, y * 10.0);
```
The conversion to C++ types (here `int`, `std::string_view`, and two `float`s)
is built into the function. You never even see `row` objects, `field` objects,
iterators, or conversion methods. You just put in your query and you receive
your data.
@@ -0,0 +1,56 @@
Binary data {#binary}
===========
The database has two ways of storing binary data: `BYTEA` is like a string, but
containing bytes rather than text characters. And _large objects_ are more
like a separate table containing binary objects.
Generally you'll want to use `BYTEA` for reasonably-sized values, and large
objects for very large values.
That's the database side. On the C++ side, in libpqxx, all binary data must be
either `std::basic_string<std::byte>` or `std::basic_string_view<std::byte>`;
or if you're building in C++20 or better, anything that's a block of
contiguous `std::byte` in memory.
So for example, if you want to write a large object, you'd create a
`pqxx::blob` object. And you might use that to write data in the form of
`std::basic_string_view<std::byte>`.
Your particular binary data may look different though. You may have it in a
`std::string`, or a `std::vector<unsigned char>`, or a pointer to `char`
accompanied by a size (which could be signed or unsigned, and of any of a few
different widths). Sometimes that's your choice, or sometimes some other
library will dictate what form it takes.
So long as it's _basically_ still a block of bytes though, you can use
`pqxx::binary_cast` to construct a `std::basic_string_view<std::byte>` from it.
There are two forms of `binary_cast`. One takes a single argument that must
support `std::data()` and `std::size()`:
std::string hi{"Hello binary world"};
my_blob.write(pqxx::binary_cast(hi);
The other takes a pointer and a size:
char const greeting[] = "Hello binary world";
char const *hi = greeting;
my_blob.write(pqxx::binary_cast(hi, sizeof(greeting)));
Caveats
-------
There are some restrictions on `binary_cast` that you must be aware of.
First, your data must of a type that gives us _bytes._ So: `char`,
`unsigned char`, `signed char`, `int8_t`, `uint8_t`, or of course `std::byte`.
You can't feed in a vector of `double`, or anything like that.
Second, the data must be laid out as a contiguous block in memory. If there's
no `std::data()` implementation for your type, it's not suitable.
Third, `binary_cast` only constructs something like a `std::string_view`. It
does not make a copy of your actual data. So, make sure that your data remains
alive and in the same place while you're using it.
@@ -0,0 +1,373 @@
Supporting additional data types {#datatypes}
================================
Communication with the database mostly happens in a text format. When you
include an integer value in a query, you use `to_string` to convert it to that
text format. When you get a query result field "as a float," it converts from
the text format to a floating-point type. These conversions are everywhere in
libpqxx.
The conversion sydstem supports many built-in types, but it is also extensible.
You can "teach" libpqxx (in the scope of your own application) to convert
additional types of values to and from PostgreSQL's string format.
This is massively useful, but it's not for the faint of heart. You'll need to
specialise some templates. And, **the API for doing this can change with any
major libpqxx release.**
Converting types
----------------
In your application, a conversion is driven entirely by a C++ type you specify.
The value's SQL type has nothing to do with it, nor is there anything in the
string that would identify its type.
So, if you've SELECTed a 64-bit integer from the database, and you try to
convert it to a C++ "short," one of two things will happen: either the number
is small enough to fit in your `short` (and it just works), or else it throws a
conversion exception.
Or, your database table might have a text column, but a given field may contain
a string that _looks_ just like a number. You can convert that value to an
integer type just fine. Or to a floating-point type. All that matters to the
conversion is the actual value, and the type.
In some cases the templates for these conversions can tell the type from the
arguments you pass them:
auto x = to_string(99);
In other cases you may need to instantiate template explicitly:
auto y = from_string<int>("99");
Supporting a new type
---------------------
Let's say you have some other SQL type which you want to be able to store in,
or retrieve from, the database. What would it take to support that?
Sometimes you do not need _complete_ support. You might need a conversion _to_
a string but not _from_ a string, for example. The conversion is defined at
compile time, so don't be too afraid to be incomplete. If you leave out one of
these steps, it's not going to crash at run time or mess up your data. The
worst that can happen is that your code won't build.
So what do you need for a complete conversion?
First off, of course, you need a C++ type. It may be your own, but it
doesn't have to be. It could be a type from a third-party library, or even one
from the standard library that libpqxx does not yet support.
You also specialise the `pqxx::type_name` variable to specify the type's name.
This is important for all code which mentions your type in human-readable text,
such as error messages.
Then, does your type have a built-in null value? You specialise the
`pqxx::nullness` template to specify the details.
Finally, you specialise the `pqxx::string_traits` template. This is where you
define the actual conversions.
Let's go through these steps one by one.
Your type
---------
You'll need a type for which the conversions are not yet defined, because the
C++ type is what determines the right conversion. One type, one set of
conversions.
The type doesn't have to be one that you create. The conversion logic was
designed such that you can build it around any type. So you can just as
easily build a conversion for a type that's defined somewhere else. There's
no need to include any special methods or other members inside it. That's also
how libpqxx can support converting built-in types like `int`.
By the way, if the type is an enum, you don't need to do any of this. Just
invoke the preprocessor macro `PQXX_DECLARE_ENUM_CONVERSION`, from the global
namespace near the top of your translation unit, and pass the type as an
argument.
The library also provides specialisations for `std::optional<T>`,
`std::shared_ptr<T>`, and `std::unique_ptr<T>`. If you have conversions for
`T`, you'll also have conversions for those.
Specialise `type_name`
----------------------
When errors happen during conversion, libpqxx will compose error messages for
the user. Sometimes these will include the name of the type that's being
converted.
To tell libpqxx the name of each type, there's a template variable called
`pqxx::type_name`. For any given type `T`, it should have a specialisation
that provides that `T`'s human-readable name:
namespace pqxx
{
template<> std::string const type_name<T>{"T"};
}
(Yes, this means that you need to define something inside the pqxx namespace.
Future versions of libpqxx may move this into a separate namespace.)
Define this early on in your translation unit, before any code that might cause
libpqxx to need the name. That way, the libpqxx code which needs to know the
type's name can see your definition.
Specialise `nullness`
---------------------
A struct template `pqxx::nullness` defines whether your type has a natural
"null value" built in. If so, it also provides member functions for producing
and recognising null values.
The simplest scenario is also the most common: most types don't have a null
value built in. In that case, derive your nullness traits from
`pqxx::no_null`:
namespace pqxx
{
template<> struct nullness<T> : pqxx::no_null<T> {};
}
(Here again you're defining this in the pqxx namespace.)
If your type does have a natural null value, the definition gets a little more
complex:
namespace pqxx
{
template<> struct nullness<T>
{
static constexpr bool has_null{true};
static constexpr bool always_null{false};
static bool is_null(T const &value)
{
// Return whether "value" is null.
return ...;
}
[[nodiscard]] static T null()
{
// Return a null value.
return ...;
}
};
}
You may be wondering why there's a function to produce a null value, but also a
function to check whether a value is null. Why not just compare the value to
the result of `null()`? Because two null values may not be equal. `T` may
have several different null values. Or it may override the comparison
operator, similar to SQL where NULL is not equal to NULL.
As a third case, your type may be one that _always_ represents a null value.
This is the case for `std::nullptr_t` and `std::nullopt_t`. In that case, you
set `nullness<TYPE>::always_null` to `true` (as well as `has_null` of course),
and you won't need to define any actual conversions.
Specialise `string_traits`
-------------------------
This part is more work. (You can skip it for types that are _always_ null,
but those will be rare.) Specialise the `pqxx::string_traits` template:
namespace pqxx
{
template<> struct string_traits<T>
{
static T from_string(std::string_view text);
static zview to_buf(char *begin, char *end, T const &value);
static char *into_buf(char *begin, char *end, T const &value);
static std::size_t size_buffer(T const &value) noexcept;
};
}
You'll also need to write those member functions, or as many of them as needed
to get your code to build.
### `from_string`
We start off simple: `from_string` parses a string as a value of `T`, and
returns that value.
The string may not be zero-terminated; it's just the `string_view` from
beginning to end (exclusive). In your tests, cover cases where the string
does not end in a zero byte.
It's perfectly possible that the string isn't actually a `T` value. Mistakes
happen. In that case, throw a `pqxx::conversion_error`.
(Of course it's also possible that you run into some other error, so it's fine
to throw different exceptions as well. But when it's definitely "this is not
the right format for a `T`," throw `conversion_error`.)
### `to_buf`
In this function, you convert a value of `T` into a string that the postgres
server will understand.
The caller will provide you with a buffer where you can write the string, if
you need it: from `begin` to `end` exclusive. It's a half-open interval, so
don't access `*end`.
If the buffer is insufficient for you to do the conversion, throw a
`pqxx::conversion_overrun`. It doesn't have to be exact: you can be a little
pessimistic and demand a bit more space than you need. Just be sure to throw
the exception if there's any risk of overrunning the buffer.
You don't _have_ to use the buffer for this function though. For example,
`pqxx::string_traits<bool>::to_buf` returns a compile-time constant string and
ignores the buffer.
Even if you do use the buffer, your string does not _have_ to start at the
beginning of the buffer. For example, the integer conversions start by writing
the _least_ significant digit to the _end_ of the buffer, and then writes the
more significant digits before it. It was just more convenient.
Return a `pqxx::zview`. This is basically a `std::string_view`, but with one
difference: a `zview` guarantees that there will be a valid zero byte right
after the `string_view`. The zero byte is not counted as part of its size, but
it will be there.
Expressed in code, this rule must hold:
void invariant(zview z)
{
assert(z[std::size(z)] == 0);
}
Make sure you write your trailing zero _before_ the `end`. If the trailing
zero doesn't fit in the buffer, then there's just not enough room to perform
the conversion.
Beware of locales when converting. If you use standard library features like
`sprintf`, they may obey whatever locale is currently set on the system. That
means that a simple integer like 1000000 may come out as "1000000" on your
system, but as "1,000,000" on mine, or as "1.000.000" for somebody else, and on
an Indian system it may be "1,00,000". Values coming from or going to the
database should be in non-localised formats. You can use libpqxx functions for
those conversions: `pqxx::from_string`, `pqxx::to_string`, `pqxx::to_buf`.
### `into_buf`
This is a stricter version of `to_buf`. All the same requirements apply, but
in addition you must write your string into the buffer provided, starting
_exactly_ at `begin`.
That's why this function returns just a simple pointer: the address right
behind the trailing zero. If the caller wants to use the string, they can
find it at `begin`. If they want to write a different value into the rest of
the buffer, they can start at the location you returned.
### `size_buffer`
Here you estimate how much buffer space you need for converting a `T` to a
string. Be precise if you can, but pessimistic if you must. It's usually
better to waste a few unnecessary bytes than to spend a lot of time computing
the exact buffer space you need. And failing the conversion because you
under-budgeted the buffer is worst of all.
Include the trailing zero in the buffer size. If your `to_buf` takes more
space than just what's needed to store the result, include that too.
Make `size_buffer` a `constexpr` function if you can. It can allow the caller
to allocate the buffer on the stack, with a size known at compile time.
Optional: Specialise `is_unquoted_safe`
---------------------------------------
When converting arrays or composite values to strings, libpqxx may need to
quote values and escape any special characters. This takes time.
Some types though, such as integral or floating-point types, can never have
any special characters such as quotes, commas, or backslashes in their string
representations. In such cases, there's no need to quote or escape such values
in arrays or composite types.
If your type is like that, you can tell libpqxx about this by defining:
namespace pqxx
{
template<> inline constexpr bool is_unquoted_safe<MY_TYPE>{true};
}
The code that converts this type of field to strings in an array or a composite
type can then use a simpler, more efficient variant of the code. It's always
safe to leave this out; it's _just_ an optimisation for when you're completely
sure that it's safe.
Do not do this if a string representation of your type may contain a comma;
semicolon; parenthesis; brace; quote; backslash; newline; or any other
character that might need escaping.
Optional: Specialise `param_format`
-----------------------------------
This one you don't generally need to worry about. Read on if you're writing a
type which represents raw binary data, or if you're writing a template where
_some specialisations_ may contain raw binary data.
When you call parameterised statements, or prepared statements with parameters,
libpqxx needs to your parameters on to libpq, the underlying C-level PostgreSQL
client library.
There are two formats for doing that: _text_ and _binary._ In the first, we
represent all values as strings, and the server then converts them into its own
internal binary representation. That's what the string conversions are all
about, and it's what we do for almost all types of parameters.
But we do it differently when the parameter is a contiguous series of raw bytes
and the corresponding SQL type is `BYTEA`. There is a text format for those,
but we bypass it for efficiency. The server can use the binary data in the
exact same form, without any conversion or extra processing. The binary data
is also twice as compact during transport.
(People sometimes ask why we can't just treat all types as binary. However the
general case isn't so clear-cut. The binary formats are not documented, there
are no guarantees that they will be platform-independent or that they will
remain stable, and there's no really solid way to detect when we might get the
format wrong. But also, the conversions aren't necessarily as straightforward
and efficient as they sound. So, for the general case, libpqxx sticks with the
text formats. Raw binary data alone stands out as a clear win.)
Long story short, the machinery for passing parameters needs to know: is this
parameter a binary string, or not? In the normal case it can assume "no," and
that's what it does. The text format is always a safe choice; we just try to
use the binary format where it's faster.
The `param_format` function template is what makes the decision. We specialise
it for types which may be binary strings, and use the default for all other
types.
"Types which _may_ be binary"? You might think we know whether a type is a
binary type or not. But there are some complications with generic types.
Templates like `std::shared_ptr`, `std::optional`, and so on act like
"wrappers" for another type. A `std::optional<T>` is binary if `T` is binary.
Otherwise, it's not. If you're building support for a template of this nature,
you'll probably want to implement `param_format` for it.
The decision to use binary format is made based on a given object, not
necessarily based on the type in general. Look at `std::variant`. If you have
a `std::variant` type which can hold an `int` or a binary string, is that a
binary parameter? We can't decide without knowing the individual object.
Containers are another hard case. Should we pass `std::vector<T>` in binary?
Even when `T` is a binary type, we don't currently have any way to pass an
array in binary format, so we always pass it as text.
@@ -0,0 +1,74 @@
String escaping {#escaping}
===============
Writing queries as strings is easy. But sometimes you need a variable in
there: `"SELECT id FROM user WHERE name = '" + name + "'"`.
This is dangerous. See the bug? If `name` can contain quotes, you may have
an SQL injection vulnerability there, where users can enter nasty stuff like
"`.'; DROP TABLE user`". Or if you're lucky, it's just a nasty bug that you
discover when `name` happens to be "d'Arcy".
So, you'll need to _escape_ the `name` before you insert it. This is where
quotes and other problematic characters are marked as "this is just a character
in the string, not the end of the string." There are
[several functions](@ref escaping-functions) in libpqxx to do this for you.
SQL injection
-------------
To understand what SQL injection vulnerabilities are and why they should be
prevented, imagine you use the following SQL statement somewhere in your
program:
TX.exec(
"SELECT number,amount "
"FROM accounts "
"WHERE allowed_to_see('" + userid + "','" + password + "')");
This shows a logged-in user important information on all accounts he is
authorized to view. The userid and password strings are variables entered
by the user himself.
Now, if the user is actually an attacker who knows (or can guess) the
general shape of this SQL statement, imagine he enters the following
password:
x') OR ('x' = 'x
Does that make sense to you? Probably not. But if this is inserted into
the SQL string by the C++ code above, the query becomes:
SELECT number,amount
FROM accounts
WHERE allowed_to_see('user','x') OR ('x' = 'x')
Is this what you wanted to happen? Probably not! The neat `allowed_to_see()`
clause is completely circumvented by the "`OR ('x' = 'x')`" clause, which is
always `true`. Therefore, the attacker will get to see all accounts in the
database!
Using the esc functions
-----------------------
Here's how you can fix the problem in the example above:
TX.exec(
"SELECT number,amount "
"FROM accounts "
"WHERE allowed_to_see('" + TX.esc(userid) + "', "
"'" + TX.esc(password) + "')");
Now, the quotes embedded in the attacker's string will be neatly escaped so
they can't "break out" of the quoted SQL string they were meant to go into:
SELECT number,amount
FROM accounts
WHERE allowed_to_see('user', 'x'') OR (''x'' = ''x')
If you look carefully, you'll see that thanks to the added escape characters
(a single-quote is escaped in SQL by doubling it) all we get is a very
strange-looking password string--but not a change in the SQL statement.
@@ -0,0 +1,142 @@
Getting started {#getting-started}
===============
The most basic three types in libpqxx are the _connection_, the _transaction_,
and the _result_.
They fit together as follows:
* You connect to the database by creating a `pqxx::connection` object (see
@ref connections).
* You create a transaction object (see @ref transactions) operating on that
connection. You'll usually want the `pqxx::work` variety.
Once you're done you call the transaction's `commit` function to make its
work final. If you don't call this, the work will be rolled back when the
transaction object is destroyed.
* Until then, use the transaction's `exec`, `query_value`, and `stream`
functions (and variants) to execute SQL statements. You pass the statements
themselves in as simple strings. (See @ref streams for more about data
streaming).
* Most of the `exec` functions return a `pqxx::result` object, which acts
as a standard container of rows: `pqxx::row`.
Each row in a result, in turn, acts as a container of fields: `pqxx::field`.
See @ref accessing-results for more about results, rows, and fields.
* Each field's data is stored internally as a text string, in a format defined
by PostgreSQL. You can convert field or row values using their `as()` and
`to()` member functions.
* After you've closed the transaction, the connection is free to run a next
transaction.
Here's a very basic example. It connects to the default database (you'll
need to have one set up), queries it for a very simple result, converts it to
an `int`, and prints it out. It also contains some basic error handling.
#include <iostream>
#include <pqxx/pqxx>
int main()
{
try
{
// Connect to the database. In practice we may have to pass some
// arguments to say where the database server is, and so on.
// The constructor parses options exactly like libpq's
// PQconnectdb/PQconnect, see:
// https://www.postgresql.org/docs/10/static/libpq-connect.html
pqxx::connection c;
// Start a transaction. In libpqxx, you always work in one.
pqxx::work w(c);
// work::exec1() executes a query returning a single row of data.
// We'll just ask the database to return the number 1 to us.
pqxx::row r = w.exec1("SELECT 1");
// Commit your transaction. If an exception occurred before this
// point, execution will have left the block, and the transaction will
// have been destroyed along the way. In that case, the failed
// transaction would implicitly abort instead of getting to this point.
w.commit();
// Look at the first and only field in the row, parse it as an integer,
// and print it.
//
// "r[0]" returns the first field, which has an "as<...>()" member
// function template to convert its contents from their string format
// to a type of your choice.
std::cout << r[0].as<int>() << std::endl;
}
catch (std::exception const &e)
{
std::cerr << e.what() << std::endl;
return 1;
}
}
This prints the number 1. Notice that you can keep the result object around
after you've closed the transaction or even the connection. There are
situations where you can't do it, but generally it's fine. If you're
interested: you can install your own callbacks for receiving error messages
from the database, and in that case you'll have to keep the connection object
alive. But otherwise, it's nice to be able to "fire and forget" your
connection and deal with the data.
You can also convert an entire row to a series of C++-side types in one go,
using the @c as member function on the row:
pqxx::connection c;
pqxx::work w(c);
pqxx::row r = w.exec1("SELECT 1, 2, 'Hello'");
auto [one, two, hello] = r.as<int, int, std::string>();
std::cout << (one + two) << ' ' << std::strlen(hello) << std::endl;
Here's a slightly more complicated example. It takes an argument from the
command line and retrieves a string with that value. The interesting part is
that it uses the escaping-and-quoting function `quote` to embed this
string value in SQL safely. It also reads the result field's value as a
plain C-style string using its `c_str` function.
#include <iostream>
#include <stdexcept>
#include <pqxx/pqxx>
int main(int argc, char *argv[])
{
try
{
if (!argv[1]) throw std::runtime_error("Give me a string!");
pqxx::connection c;
pqxx::work w(c);
// work::exec() returns a full result set, which can consist of any
// number of rows.
pqxx::result r = w.exec("SELECT " + w.quote(argv[1]));
// End our transaction here. We can still use the result afterwards.
w.commit();
// Print the first field of the first row. Read it as a C string,
// just like std::string::c_str() does.
std::cout << r[0][0].c_str() << std::endl;
}
catch (std::exception const &e)
{
std::cerr << e.what() << std::endl;
return 1;
}
}
You can find more about converting field values to native types, or
converting values to strings for use with libpqxx, under
@ref stringconversion. More about getting to the rows and fields of a
result is under @ref accessing-results.
If you want to handle exceptions thrown by libpqxx in more detail, for
example to print the SQL contents of a query that failed, see @ref exception.
@@ -0,0 +1,28 @@
libpqxx {#mainpage}
=======
@version 7.7.3
@author Jeroen T. Vermeulen
@see http://pqxx.org
@see https://github.com/jtv/libpqxx
Welcome to libpqxx, the C++ API to the PostgreSQL database management system.
Compiling this package requires PostgreSQL to be installed -- including the
C headers for client development. The library builds on top of PostgreSQL's
standard C API, libpq. The libpq headers are not needed to compile client
programs, however.
For a quick introduction to installing and using libpqxx, see the README.md
file. The latest information can be found at http://pqxx.org/
Some links that should help you find your bearings:
* @ref getting-started
* @ref thread-safety
* @ref connections
* @ref transactions
* @ref escaping
* @ref performance
* @ref transactor
* @ref datatypes
@@ -0,0 +1,28 @@
libpqxx {#mainpage}
=======
@version @PQXXVERSION@
@author Jeroen T. Vermeulen
@see http://pqxx.org
@see https://github.com/jtv/libpqxx
Welcome to libpqxx, the C++ API to the PostgreSQL database management system.
Compiling this package requires PostgreSQL to be installed -- including the
C headers for client development. The library builds on top of PostgreSQL's
standard C API, libpq. The libpq headers are not needed to compile client
programs, however.
For a quick introduction to installing and using libpqxx, see the README.md
file. The latest information can be found at http://pqxx.org/
Some links that should help you find your bearings:
* @ref getting-started
* @ref thread-safety
* @ref connections
* @ref transactions
* @ref escaping
* @ref performance
* @ref transactor
* @ref datatypes
@@ -0,0 +1,90 @@
Statement parameters {#parameters}
====================
When you execute a prepared statement (see @ref prepared), or a parameterised
statement (using functions like `pqxx::connection::exec_params`), you may write
special _placeholders_ in the query text. They look like `$1`, `$2`, and so
on.
If you execute the query and pass parameter values, the call will respectively
substitute the first where it finds `$1`, the second where it finds `$2`, et
cetera.
Doing this saves you work. If you don't use statement parameters, you'll need
to quote and escape your values (see `connection::quote()` and friends) as you
insert them into your query as literal values.
Or if you forget to do that, you leave yourself open to horrible
[SQL injection attacks](https://xkcd.com/327/). Trust me, I was born in a town
whose name started with an apostrophe!
Statement parameters save you this work. With these parameters you can pass
your values as-is, and they will go across the wire to the database in a safe
format.
In some cases it may even be faster! When a parameter represents binary data
(as in the SQL `BYTEA` type), libpqxx will send it directly as binary, which is
a bit more efficient. If you insert the binary data directly in your query
text, your CPU will have some extra work to do, converting the data into a text
format, escaping it, and adding quotes.
Dynamic parameter lists
-----------------------
In rare cases you may just not know how many parameters you'll pass into your
statement when you call it.
For these situations, have a look at `params`. It lets you compose your
parameters list on the fly, even add whole ranges of parameters at a time.
You can pass a `params` into your statement as a normal parameter. It will
fill in all the parameter values it contains into that position of the
statement's overall parameter list.
So if you call your statement passing a regular parameter `a`, a
`params` containing just a parameter `b`, and another regular parameter `c`,
then your call will pass parameters `a`, `b`, and `c`. Or if the params object
is empty, it will pass just `a` and `c`. If the params object contains `x` and
`y`, your call will pass `a, x, y, c`.
You can mix static and dynamic parameters freely. Don't go overboard though:
complexity is where bugs happen!
Generating placeholders
-----------------------
If your code gets particularly complex, it may sometimes happen that it becomes
hard to track which parameter value belongs with which placeholder. Did you
intend to pass this numeric value as `$7`, or as `$8`? The answer may depend
on an `if` that happened earlier in a different function.
(Generally if things get that complex, it's a good idea to look for simpler
solutions. But especially when performance matters, sometimes you can't avoid
complexity like that.)
There's a little helper class called `placeholders`. You can use it as a
counter which produces those placeholder strings, `$1`, `$2`, `$3`, et cetera.
When you start generating a complex statement, you can create both a `params`
and a `placeholders`:
pqxx::params values;
pqxx::placeholders name;
Let's say you've got some complex code to generate the conditions for an SQL
"WHERE" clause. You'll generally want to do these things close together in
your, so that you don't accidentally update one part and forget another:
if (extra_clause)
{
// Extend the query text, using the current placeholder.
query += " AND x = " + name.get();
// Add the parameter value.
values.append(my_x);
// Move on to the next placeholder value.
name.next();
}
Depending on the starting value of `name`, this might add to `query` a fragment
like "` AND x = $3`" or "` AND x = $5`".
@@ -0,0 +1,24 @@
Performance features {#performance}
====================
If your program's database interaction is not as efficient as it needs to be,
the first place to look is usually the SQL you're executing. But libpqxx
has a few specialized features to help you squeeze more performance out
of how you issue commands and retrieve data:
* @ref streams. Use these as a faster way to transfer data between your
code and the database.
* `std::string_view` and `pqxx::zview`. In places where traditional C++ worked
with `std::string`, see whether `std::string_view` or `pqxx::zview` will
do. Of course that means that you'll have to look at the data's lifetime
more carefully, but it'll save the computer a lot of copying.
* @ref prepared. These can be executed many times without the server
parsing and planning them anew each time. They also save you having to
escape string parameters.
* `pqxx::pipeline` lets you send queries to the database in batches, and
continue other processing while they are executing.
* `pqxx::connecting` lets you start setting up a database connection, but
without blocking the thread.
As always of course, don't risk the quality of your code for optimizations
that you don't need!
@@ -0,0 +1,125 @@
Prepared statements {#prepared}
===================
Prepared statements are SQL queries that you define once and then invoke
as many times as you like, typically with varying parameters. It's basically
a function that you can define ad hoc.
If you have an SQL statement that you're going to execute many times in
quick succession, it may be more efficient to prepare it once and reuse it.
This saves the database backend the effort of parsing complex SQL and
figuring out an efficient execution plan. Another nice side effect is that
you don't need to worry about escaping parameters. Some corporate coding
standards require all SQL parameters to be passed in this way, to reduce the
risk of programmer mistakes leaving room for SQL injections.
Preparing a statement
---------------------
You create a prepared statement by preparing it on the connection (using the
`pqxx::connection::prepare` functions), passing an identifier and its SQL text.
The identifier is the name by which the prepared statement will be known; it
should consist of ASCII letters, digits, and underscores only, and start with
an ASCII letter. The name is case-sensitive.
```cxx
void prepare_my_statement(pqxx::connection &c)
{
c.prepare(
"my_statement",
"SELECT * FROM Employee WHERE name = 'Xavier'");
}
```
Once you've done this, you'll be able to call `my_statement` from any
transaction you execute on the same connection. For this, use the
`pqxx::transaction_base::exec_prepared` functions.
```cxx
pqxx::result execute_my_statement(pqxx::transaction_base &t)
{
return t.exec_prepared("my_statement");
}
```
Parameters
----------
Did I mention that prepared statements can have parameters? The query text
can contain `$1`, `$2` etc. as placeholders for parameter values that you
will provide when you invoke the prepared satement.
See @ref parameters for more about this. And here's a simple example of
preparing a statement and invoking it with parameters:
```cxx
void prepare_find(pqxx::connection &c)
{
// Prepare a statement called "find" that looks for employees with a
// given name (parameter 1) whose salary exceeds a given number
// (parameter 2).
c.prepare(
"find",
"SELECT * FROM Employee WHERE name = $1 AND salary > $2");
}
```
This example looks up the prepared statement "find," passes `name` and
`min_salary` as parameters, and invokes the statement with those values:
```cxx
pqxx::result execute_find(
pqxx::transaction_base &t, std::string name, int min_salary)
{
return t.exec_prepared("find", name, min_salary);
}
```
A special prepared statement
----------------------------
There is one special case: the _nameless_ prepared statement. You may prepare
a statement without a name, i.e. whose name is an empty string. The unnamed
statement can be redefined at any time, without un-preparing it first.
Performance note
----------------
Don't assume that using prepared statements will speed up your application.
There are cases where prepared statements are actually slower than plain SQL.
The reason is that the backend can often produce a better execution plan when
it knows the statement's actual parameter values.
For example, say you've got a web application and you're querying for users
with status "inactive" who have email addresses in a given domain name X. If
X is a very popular provider, the best way for the database engine to plan the
query may be to list the inactive users first and then filter for the email
addresses you're looking for. But in other cases, it may be much faster to
find matching email addresses first and then see which of their owners are
"inactive." A prepared statement must be planned to fit either case, but a
direct query will be optimised based on table statistics, partial indexes, etc.
Zero bytes
----------
@warning Beware of "nul" bytes!
Any string you pass as a parameter will end at the _first char with value
zero._ If you pass a string that contains a zero byte, the last byte in the
value will be the one just before the zero.
So, if you need a zero byte in a string, consider that it's really a _binary
string,_ which is not the same thing as a text string. SQL represents binary
data as the `BYTEA` type, or in binary large objects ("blobs").
In libpqxx, you represent binary data as a range of `std::byte`. They must be
contiguous in memory, so that libpqxx can pass pointers to the underlying C
library. So you might use `std::basic_string<std::byte>`, or
`std::basic_string_view<std::byte>`, or `std::vector<std::byte>`.
@@ -0,0 +1,107 @@
Streams {#streams}
=======
Most of the time it's fine to retrieve data from the database using `SELECT`
queries, and store data using `INSERT`. But for those cases where efficiency
matters, there are two classes to help you do this better: `stream_from` and
`stream_to`. They're less flexible than SQL queries, and there's the risk of
losing your connection while you're in mid-stream, but you get some speed and
memory efficiencies in return.
Both stream classes do data conversion for you: `stream_from` receives values
from the database in PostgreSQL's text format, and converts them to the C++
types you specify. Likewise, `stream_to` converts C++ values you provide to
PostgreSQL's text format for transfer. (On its end, the database of course
converts values to and from their SQL types.)
Null values
-----------
So how do you deal with nulls? It depends on the C++ type you're using. Some
types may have a built-in null value. For instance, if you have a
`char const *` value and you convert it to an SQL string, then converting a
`nullptr` will produce a NULL SQL value.
But what do you do about C++ types which don't have a built-in null value, such
as `int`? The trick is to wrap it in `std::optional`. The difference between
`int` and `std::optional<int>` is that the former always has an `int` value,
and the latter doesn't have to.
Actually it's not just `std::optional`. You can do the same thing with
`std::unique_ptr` or `std::shared_ptr`. A smart pointer is less efficient than
`std::optional` in most situations because they allocate their value on the
heap, but sometimes that's what you want in order to save moving or copying
large values around.
This part is not generic though. It won't work with just any smart-pointer
type, just the ones which are explicitly supported: `shared_ptr` and
`unique_ptr`. If you really need to, you can build support for additional
wrappers and smart pointers by copying the implementation patterns from the
existing smart-pointer support.
stream\_from
------------
Use `stream_from` to read data directly from the database. It's faster than
the transaction's `exec` functions if the result contains enough rows. But
also, you won't need to keep your full result set in memory. That can really
matter with larger data sets.
And, you can start processing your data right after the first row of data comes
in from the server. With `exec()` you need to wait to receive all data, and
then you begin processing. With `stream_from` you can be processing data on
the client side while the server is still sending you the rest.
You don't actually need to create a `stream_from` object yourself, though you
can. Two shorthand functions, @ref pqxx::transaction_base::stream
and @ref pqxx::transaction_base::for_each, can create the streams for you with
a minimum of overhead.
Not all kinds of queries will work in a stream. Internally the streams make
use of PostgreSQL's `COPY` command, so see the PostgreSQL documentation for
`COPY` for the exact limitations. Basic `SELECT` and `UPDATE ... RETURNING`
queries should just work.
As you read a row, the stream converts its fields to a tuple type containing
the value types you ask for:
auto stream pqxx::stream_from::query(
tx, "SELECT name, points FROM score");
std::tuple<std::string, int> row;
while (stream >> row)
process(row);
stream.complete();
As the stream reads each row, it converts that row's data into your tuple,
goes through your loop body, and then promptly forgets that row's data. This
means you can easily process more data than will fit in memory.
stream\_to
----------
Use `stream_to` to write data directly to a database table. This saves you
having to perform an `INSERT` for every row, and so it can be significantly
faster if you want to insert more than just one or two rows at a time.
As with `stream_from`, you can specify the table and the columns, and not much
else. You insert tuple-like objects of your choice:
pqxx::stream_to stream{
tx,
"score",
std::vector<std::string>{"name", "points"}};
for (auto const &entry: scores)
stream << entry;
stream.complete();
Each row is processed as you provide it, and not retained in memory after that.
The call to `complete()` is more important here than it is for `stream_from`.
It's a lot like a "commit" or "abort" at the end of a transaction. If you omit
it, it will be done automatically during the stream's destructor. But since
destructors can't throw exceptions, any failures at that stage won't be visible
in your code. So, always call `complete()` on a `stream_to` to close it off
properly!
@@ -0,0 +1,29 @@
Thread safety {#thread-safety}
=============
This library does not contain any locking code to protect objects against
simultaneous modification in multi-threaded programs. Therefore it is up
to you, the user of the library, to ensure that your threaded client
programs perform no conflicting operations concurrently.
Most of the time this isn't hard. Result sets are immutable, so you can
share them between threads without problem. The main rule is:
@li Treat a connection, together with any and all objects related to it, as
a "world" of its own. You should generally make sure that the same "world"
is never accessed by another thread while you're doing anything non-const
in there.
That means: don't issue a query on a transaction while you're also opening
a subtransaction, don't access a cursor while you may also be committing,
and so on.
In particular, cursors are tricky. It's easy to perform a non-const
operation without noticing. So, if you're going to share cursors or
cursor-related objects between threads, lock very conservatively!
Use `pqxx::describe_thread_safety` to find out at runtime what level of
thread safety is implemented in your build and version of libpqxx. It
returns a `pqxx::thread_safety_model` describing what you can and cannot rely
on. A command-line utility `tools/pqxxthreadsafety` prints out the same
information.
@@ -0,0 +1,8 @@
/** pqxx::errorhandler class.
*
* Callbacks for handling errors and warnings.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/errorhandler.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,92 @@
/* Definition of the pqxx::errorhandler class.
*
* pqxx::errorhandler handlers errors and warnings in a database session.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/errorhandler instead.
*
* 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.
*/
#ifndef PQXX_H_ERRORHANDLER
#define PQXX_H_ERRORHANDLER
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include "pqxx/types.hxx"
namespace pqxx::internal::gate
{
class errorhandler_connection;
}
namespace pqxx
{
/**
* @addtogroup errorhandler
*/
//@{
/// Base class for error-handler callbacks.
/** To receive errors and warnings from a connection, subclass this with your
* own error-handler functor, and instantiate it for the connection. Destroying
* the handler un-registers it.
*
* A connection can have multiple error handlers at the same time. When the
* database connection emits an error or warning message, it passes the message
* to each error handler, starting with the most recently registered one and
* progressing towards the oldest one. However an error handler may also
* instruct the connection not to pass the message to further handlers by
* returning "false."
*
* @warning Strange things happen when a result object outlives its parent
* connection. If you register an error handler on a connection, then you must
* not access the result after destroying the connection. This applies even if
* you destroy the error handler first!
*/
class PQXX_LIBEXPORT errorhandler
{
public:
explicit errorhandler(connection &);
virtual ~errorhandler();
/// Define in subclass: receive an error or warning message from the
/// database.
/**
* @return Whether the same error message should also be passed to the
* remaining, older errorhandlers.
*/
virtual bool operator()(char const msg[]) noexcept = 0;
errorhandler() = delete;
errorhandler(errorhandler const &) = delete;
errorhandler &operator=(errorhandler const &) = delete;
private:
connection *m_home;
friend class internal::gate::errorhandler_connection;
void unregister() noexcept;
};
/// An error handler that suppresses any previously registered error handlers.
class quiet_errorhandler : public errorhandler
{
public:
/// Suppress error notices.
quiet_errorhandler(connection &conn) : errorhandler{conn} {}
/// Revert to previous handling of error notices.
virtual bool operator()(char const[]) noexcept override { return false; }
};
//@}
} // namespace pqxx
#endif
+8
View File
@@ -0,0 +1,8 @@
/** libpqxx exception classes.
*
* pqxx::sql_error, pqxx::broken_connection, pqxx::in_doubt_error, ...
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/except.hxx"
#include "pqxx/internal/header-post.hxx"
+447
View File
@@ -0,0 +1,447 @@
/* Definition of libpqxx exception classes.
*
* pqxx::sql_error, pqxx::broken_connection, pqxx::in_doubt_error, ...
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/except instead.
*
* 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.
*/
#ifndef PQXX_H_EXCEPT
#define PQXX_H_EXCEPT
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <stdexcept>
#include <string>
namespace pqxx
{
/**
* @addtogroup exception Exception classes
*
* These exception classes follow, roughly, the two-level hierarchy defined by
* the PostgreSQL SQLSTATE error codes (see Appendix A of the PostgreSQL
* documentation corresponding to your server version). This is not a complete
* mapping though. There are other differences as well, e.g. the error code
* for `statement_completion_unknown` has a separate status in libpqxx as
* @ref in_doubt_error, and `too_many_connections` is classified as a
* `broken_connection` rather than a subtype of `insufficient_resources`.
*
* @see http://www.postgresql.org/docs/9.4/interactive/errcodes-appendix.html
*
* @{
*/
/// Run-time failure encountered by libpqxx, similar to std::runtime_error.
struct PQXX_LIBEXPORT failure : std::runtime_error
{
explicit failure(std::string const &);
};
/// Exception class for lost or failed backend connection.
/**
* @warning When this happens on Unix-like systems, you may also get a SIGPIPE
* signal. That signal aborts the program by default, so if you wish to be
* able to continue after a connection breaks, be sure to disarm this signal.
*
* If you're working on a Unix-like system, see the manual page for
* `signal` (2) on how to deal with SIGPIPE. The easiest way to make this
* signal harmless is to make your program ignore it:
*
* ```cxx
* #include <signal.h>
*
* int main()
* {
* signal(SIGPIPE, SIG_IGN);
* // ...
* ```
*/
struct PQXX_LIBEXPORT broken_connection : failure
{
broken_connection();
explicit broken_connection(std::string const &);
};
/// The caller attempted to set a variable to null, which is not allowed.
struct PQXX_LIBEXPORT variable_set_to_null : failure
{
variable_set_to_null();
explicit variable_set_to_null(std::string const &);
};
/// Exception class for failed queries.
/** Carries, in addition to a regular error message, a copy of the failed query
* and (if available) the SQLSTATE value accompanying the error.
*/
class PQXX_LIBEXPORT sql_error : public failure
{
/// Query string. Empty if unknown.
std::string const m_query;
/// SQLSTATE string describing the error type, if known; or empty string.
std::string const m_sqlstate;
public:
explicit sql_error(
std::string const &whatarg = "", std::string const &Q = "",
char const sqlstate[] = nullptr);
virtual ~sql_error() noexcept override;
/// The query whose execution triggered the exception
[[nodiscard]] PQXX_PURE std::string const &query() const noexcept;
/// SQLSTATE error code if known, or empty string otherwise.
[[nodiscard]] PQXX_PURE std::string const &sqlstate() const noexcept;
};
/// "Help, I don't know whether transaction was committed successfully!"
/** Exception that might be thrown in rare cases where the connection to the
* database is lost while finishing a database transaction, and there's no way
* of telling whether it was actually executed by the backend. In this case
* the database is left in an indeterminate (but consistent) state, and only
* manual inspection will tell which is the case.
*/
struct PQXX_LIBEXPORT in_doubt_error : failure
{
explicit in_doubt_error(std::string const &);
};
/// The backend saw itself forced to roll back the ongoing transaction.
struct PQXX_LIBEXPORT transaction_rollback : sql_error
{
explicit transaction_rollback(
std::string const &whatarg, std::string const &q = "",
char const sqlstate[] = nullptr);
};
/// Transaction failed to serialize. Please retry it.
/** Can only happen at transaction isolation levels REPEATABLE READ and
* SERIALIZABLE.
*
* The current transaction cannot be committed without violating the guarantees
* made by its isolation level. This is the effect of a conflict with another
* ongoing transaction. The transaction may still succeed if you try to
* perform it again.
*/
struct PQXX_LIBEXPORT serialization_failure : transaction_rollback
{
explicit serialization_failure(
std::string const &whatarg, std::string const &q,
char const sqlstate[] = nullptr);
};
/// We can't tell whether our last statement succeeded.
struct PQXX_LIBEXPORT statement_completion_unknown : transaction_rollback
{
explicit statement_completion_unknown(
std::string const &whatarg, std::string const &q,
char const sqlstate[] = nullptr);
};
/// The ongoing transaction has deadlocked. Retrying it may help.
struct PQXX_LIBEXPORT deadlock_detected : transaction_rollback
{
explicit deadlock_detected(
std::string const &whatarg, std::string const &q,
char const sqlstate[] = nullptr);
};
/// Internal error in libpqxx library
struct PQXX_LIBEXPORT internal_error : std::logic_error
{
explicit internal_error(std::string const &);
};
/// Error in usage of libpqxx library, similar to std::logic_error
struct PQXX_LIBEXPORT usage_error : std::logic_error
{
explicit usage_error(std::string const &);
};
/// Invalid argument passed to libpqxx, similar to std::invalid_argument
struct PQXX_LIBEXPORT argument_error : std::invalid_argument
{
explicit argument_error(std::string const &);
};
/// Value conversion failed, e.g. when converting "Hello" to int.
struct PQXX_LIBEXPORT conversion_error : std::domain_error
{
explicit conversion_error(std::string const &);
};
/// Could not convert value to string: not enough buffer space.
struct PQXX_LIBEXPORT conversion_overrun : conversion_error
{
explicit conversion_overrun(std::string const &);
};
/// Something is out of range, similar to std::out_of_range
struct PQXX_LIBEXPORT range_error : std::out_of_range
{
explicit range_error(std::string const &);
};
/// Query returned an unexpected number of rows.
struct PQXX_LIBEXPORT unexpected_rows : public range_error
{
explicit unexpected_rows(std::string const &msg) : range_error{msg} {}
};
/// Database feature not supported in current setup.
struct PQXX_LIBEXPORT feature_not_supported : sql_error
{
explicit feature_not_supported(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
sql_error{err, Q, sqlstate}
{}
};
/// Error in data provided to SQL statement.
struct PQXX_LIBEXPORT data_exception : sql_error
{
explicit data_exception(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
sql_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT integrity_constraint_violation : sql_error
{
explicit integrity_constraint_violation(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
sql_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT restrict_violation : integrity_constraint_violation
{
explicit restrict_violation(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
integrity_constraint_violation{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT not_null_violation : integrity_constraint_violation
{
explicit not_null_violation(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
integrity_constraint_violation{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT foreign_key_violation : integrity_constraint_violation
{
explicit foreign_key_violation(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
integrity_constraint_violation{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT unique_violation : integrity_constraint_violation
{
explicit unique_violation(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
integrity_constraint_violation{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT check_violation : integrity_constraint_violation
{
explicit check_violation(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
integrity_constraint_violation{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT invalid_cursor_state : sql_error
{
explicit invalid_cursor_state(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
sql_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT invalid_sql_statement_name : sql_error
{
explicit invalid_sql_statement_name(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
sql_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT invalid_cursor_name : sql_error
{
explicit invalid_cursor_name(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
sql_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT syntax_error : sql_error
{
/// Approximate position in string where error occurred, or -1 if unknown.
int const error_position;
explicit syntax_error(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr, int pos = -1) :
sql_error{err, Q, sqlstate}, error_position{pos}
{}
};
struct PQXX_LIBEXPORT undefined_column : syntax_error
{
explicit undefined_column(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
syntax_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT undefined_function : syntax_error
{
explicit undefined_function(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
syntax_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT undefined_table : syntax_error
{
explicit undefined_table(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
syntax_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT insufficient_privilege : sql_error
{
explicit insufficient_privilege(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
sql_error{err, Q, sqlstate}
{}
};
/// Resource shortage on the server
struct PQXX_LIBEXPORT insufficient_resources : sql_error
{
explicit insufficient_resources(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
sql_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT disk_full : insufficient_resources
{
explicit disk_full(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
insufficient_resources{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT out_of_memory : insufficient_resources
{
explicit out_of_memory(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
insufficient_resources{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT too_many_connections : broken_connection
{
explicit too_many_connections(std::string const &err) :
broken_connection{err}
{}
};
/// PL/pgSQL error
/** Exceptions derived from this class are errors from PL/pgSQL procedures.
*/
struct PQXX_LIBEXPORT plpgsql_error : sql_error
{
explicit plpgsql_error(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
sql_error{err, Q, sqlstate}
{}
};
/// Exception raised in PL/pgSQL procedure
struct PQXX_LIBEXPORT plpgsql_raise : plpgsql_error
{
explicit plpgsql_raise(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
plpgsql_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT plpgsql_no_data_found : plpgsql_error
{
explicit plpgsql_no_data_found(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
plpgsql_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT plpgsql_too_many_rows : plpgsql_error
{
explicit plpgsql_too_many_rows(
std::string const &err, std::string const &Q = "",
char const sqlstate[] = nullptr) :
plpgsql_error{err, Q, sqlstate}
{}
};
struct PQXX_LIBEXPORT blob_already_exists : failure
{
explicit blob_already_exists(std::string const &);
};
/**
* @}
*/
} // namespace pqxx
#endif
+8
View File
@@ -0,0 +1,8 @@
/** pqxx::field class.
*
* pqxx::field refers to a field in a query result.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/field.hxx"
#include "pqxx/internal/header-post.hxx"
+542
View File
@@ -0,0 +1,542 @@
/* Definitions for the pqxx::field class.
*
* pqxx::field refers to a field in a query result.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/field instead.
*
* 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.
*/
#ifndef PQXX_H_FIELD
#define PQXX_H_FIELD
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <optional>
#include "pqxx/array.hxx"
#include "pqxx/composite.hxx"
#include "pqxx/result.hxx"
#include "pqxx/strconv.hxx"
#include "pqxx/types.hxx"
namespace pqxx
{
/// Reference to a field in a result set.
/** A field represents one entry in a row. It represents an actual value
* in the result set, and can be converted to various types.
*/
class PQXX_LIBEXPORT field
{
public:
using size_type = field_size_type;
/// Constructor. Do not call this yourself; libpqxx will do it for you.
/** Create field as reference to a field in a result set.
* @param r Row that this field is part of.
* @param c Column number of this field.
*/
[[deprecated(
"Do not construct fields yourself. Get them from the row.")]] field(row const &r, row_size_type c) noexcept;
/// Constructor. Do not call this yourself; libpqxx will do it for you.
[[deprecated(
"Do not construct fields yourself. Get them from the "
"row.")]] field() noexcept = default;
/**
* @name Comparison
*/
//@{
// TODO: noexcept. Breaks ABI.
/// Byte-by-byte comparison of two fields (all nulls are considered equal)
/** @warning null handling is still open to discussion and change!
*
* Handling of null values differs from that in SQL where a comparison
* involving a null value yields null, so nulls are never considered equal
* to one another or even to themselves.
*
* Null handling also probably differs from the closest equivalent in C++,
* which is the NaN (Not-a-Number) value, a singularity comparable to
* SQL's null. This is because the builtin == operator demands that a == a.
*
* The usefulness of this operator is questionable. No interpretation
* whatsoever is imposed on the data; 0 and 0.0 are considered different,
* as are null vs. the empty string, or even different (but possibly
* equivalent and equally valid) encodings of the same Unicode character
* etc.
*/
[[nodiscard]] PQXX_PURE bool operator==(field const &) const;
/// Byte-by-byte comparison (all nulls are considered equal)
/** @warning See operator==() for important information about this operator
*/
[[nodiscard]] PQXX_PURE bool operator!=(field const &rhs) const noexcept
{
return not operator==(rhs);
}
//@}
/**
* @name Column information
*/
//@{
/// Column name.
[[nodiscard]] PQXX_PURE char const *name() const &;
/// Column type.
[[nodiscard]] oid PQXX_PURE type() const;
/// What table did this column come from?
[[nodiscard]] PQXX_PURE oid table() const;
/// Return row number. The first row is row 0, the second is row 1, etc.
PQXX_PURE constexpr row_size_type num() const noexcept { return col(); }
/// What column number in its originating table did this column come from?
[[nodiscard]] PQXX_PURE row_size_type table_column() const;
//@}
/**
* @name Content access
*/
//@{
/// Read as `string_view`, or an empty one if null.
/** The result only remains usable while the data for the underlying
* @ref result exists. Once all `result` objects referring to that data have
* been destroyed, the `string_view` will no longer point to valid memory.
*/
[[nodiscard]] PQXX_PURE std::string_view view() const &
{
return std::string_view(c_str(), size());
}
/// Read as plain C string.
/** Since the field's data is stored internally in the form of a
* zero-terminated C string, this is the fastest way to read it. Use the
* to() or as() functions to convert the string to other types such as
* `int`, or to C++ strings.
*
* Do not use this for BYTEA values, or other binary values. To read those,
* convert the value to your desired type using `to()` or `as()`. For
* example: `f.as<std::basic_string<std::byte>>()`.
*/
[[nodiscard]] PQXX_PURE char const *c_str() const &;
/// Is this field's value null?
[[nodiscard]] PQXX_PURE bool is_null() const noexcept;
/// Return number of bytes taken up by the field's value.
[[nodiscard]] PQXX_PURE size_type size() const noexcept;
/// Read value into obj; or if null, leave obj untouched and return `false`.
/** This can be used with optional types (except pointers other than C-style
* strings).
*/
template<typename T>
auto to(T &obj) const -> typename std::enable_if_t<
(not std::is_pointer<T>::value or std::is_same<T, char const *>::value),
bool>
{
if (is_null())
{
return false;
}
else
{
auto const bytes{c_str()};
from_string(bytes, obj);
return true;
}
}
/// Read field as a composite value, write its components into `fields`.
/** @warning This is still experimental. It may change or be replaced.
*
* Returns whether the field was null. If it was, it will not touch the
* values in `fields`.
*/
template<typename... T> bool composite_to(T &...fields) const
{
if (is_null())
{
return false;
}
else
{
parse_composite(m_home.m_encoding, view(), fields...);
return true;
}
}
/// Read value into obj; or leave obj untouched and return `false` if null.
template<typename T> bool operator>>(T &obj) const { return to(obj); }
/// Read value into obj; or if null, use default value and return `false`.
/** This can be used with `std::optional`, as well as with standard smart
* pointer types, but not with raw pointers. If the conversion from a
* PostgreSQL string representation allocates a pointer (e.g. using `new`),
* then the object's later deallocation should be baked in as well, right
* from the point where the object is created. So if you want a pointer, use
* a smart pointer, not a raw pointer.
*
* There is one exception, of course: C-style strings. Those are just
* pointers to the field's internal text data.
*/
template<typename T>
auto to(T &obj, T const &default_value) const -> typename std::enable_if_t<
(not std::is_pointer<T>::value or std::is_same<T, char const *>::value),
bool>
{
bool const null{is_null()};
if (null)
obj = default_value;
else
obj = from_string<T>(this->view());
return not null;
}
/// Return value as object of given type, or default value if null.
/** Note that unless the function is instantiated with an explicit template
* argument, the Default value's type also determines the result type.
*/
template<typename T> T as(T const &default_value) const
{
if (is_null())
return default_value;
else
return from_string<T>(this->view());
}
/// Return value as object of given type, or throw exception if null.
/** Use as `as<std::optional<int>>()` or `as<my_untemplated_optional_t>()` as
* an alternative to `get<int>()`; this is disabled for use with raw pointers
* (other than C-strings) because storage for the value can't safely be
* allocated here
*/
template<typename T> T as() const
{
if (is_null())
{
if constexpr (not nullness<T>::has_null)
internal::throw_null_conversion(type_name<T>);
else
return nullness<T>::null();
}
else
{
return from_string<T>(this->view());
}
}
/// Return value wrapped in some optional type (empty for nulls).
/** Use as `get<int>()` as before to obtain previous behavior, or specify
* container type with `get<int, std::optional>()`
*/
template<typename T, template<typename> class O = std::optional>
constexpr O<T> get() const
{
return as<O<T>>();
}
// TODO: constexpr noexcept, once array_parser constructor gets those.
/// Parse the field as an SQL array.
/** Call the parser to retrieve values (and structure) from the array.
*
* Make sure the @ref result object stays alive until parsing is finished. If
* you keep the @ref row of `field` object alive, it will keep the @ref
* result object alive as well.
*/
array_parser as_array() const &
{
return array_parser{c_str(), m_home.m_encoding};
}
//@}
protected:
constexpr result const &home() const noexcept { return m_home; }
constexpr result::size_type idx() const noexcept { return m_row; }
constexpr row_size_type col() const noexcept { return m_col; }
// TODO: Create gates.
friend class pqxx::result;
friend class pqxx::row;
field(
result const &r, result_size_type row_num, row_size_type col_num) noexcept
:
m_col{col_num}, m_home{r}, m_row{row_num}
{}
/**
* You'd expect this to be unsigned, but due to the way reverse iterators
* are related to regular iterators, it must be allowed to underflow to -1.
*/
row_size_type m_col;
private:
result m_home;
result::size_type m_row;
};
template<> inline bool field::to<std::string>(std::string &obj) const
{
bool const null{is_null()};
if (not null)
obj = std::string{view()};
return not null;
}
template<>
inline bool field::to<std::string>(
std::string &obj, std::string const &default_value) const
{
bool const null{is_null()};
if (null)
obj = default_value;
else
obj = std::string{view()};
return not null;
}
/// Specialization: `to(char const *&)`.
/** The buffer has the same lifetime as the data in this result (i.e. of this
* result object, or the last remaining one copied from it etc.), so take care
* not to use it after the last result object referring to this query result is
* destroyed.
*/
template<> inline bool field::to<char const *>(char const *&obj) const
{
bool const null{is_null()};
if (not null)
obj = c_str();
return not null;
}
template<> inline bool field::to<std::string_view>(std::string_view &obj) const
{
bool const null{is_null()};
if (not null)
obj = view();
return not null;
}
template<>
inline bool field::to<std::string_view>(
std::string_view &obj, std::string_view const &default_value) const
{
bool const null{is_null()};
if (null)
obj = default_value;
else
obj = view();
return not null;
}
template<> inline std::string_view field::as<std::string_view>() const
{
if (is_null())
PQXX_UNLIKELY
internal::throw_null_conversion(type_name<std::string_view>);
return view();
}
template<>
inline std::string_view
field::as<std::string_view>(std::string_view const &default_value) const
{
return is_null() ? default_value : view();
}
template<> inline bool field::to<zview>(zview &obj) const
{
bool const null{is_null()};
if (not null)
obj = zview{c_str(), size()};
return not null;
}
template<>
inline bool field::to<zview>(zview &obj, zview const &default_value) const
{
bool const null{is_null()};
if (null)
obj = default_value;
else
obj = zview{c_str(), size()};
return not null;
}
template<> inline zview field::as<zview>() const
{
if (is_null())
PQXX_UNLIKELY
internal::throw_null_conversion(type_name<zview>);
return zview{c_str(), size()};
}
template<> inline zview field::as<zview>(zview const &default_value) const
{
return is_null() ? default_value : zview{c_str(), size()};
}
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
class field_streambuf : public std::basic_streambuf<CHAR, TRAITS>
{
public:
using char_type = CHAR;
using traits_type = TRAITS;
using int_type = typename traits_type::int_type;
using pos_type = typename traits_type::pos_type;
using off_type = typename traits_type::off_type;
using openmode = std::ios::openmode;
using seekdir = std::ios::seekdir;
explicit field_streambuf(field const &f) : m_field{f} { initialize(); }
protected:
virtual int sync() override { return traits_type::eof(); }
virtual pos_type seekoff(off_type, seekdir, openmode) override
{
return traits_type::eof();
}
virtual pos_type seekpos(pos_type, openmode) override
{
return traits_type::eof();
}
virtual int_type overflow(int_type) override { return traits_type::eof(); }
virtual int_type underflow() override { return traits_type::eof(); }
private:
field const &m_field;
int_type initialize()
{
auto g{static_cast<char_type *>(const_cast<char *>(m_field.c_str()))};
this->setg(g, g, g + std::size(m_field));
return int_type(std::size(m_field));
}
};
/// Input stream that gets its data from a result field
/** Use this class exactly as you would any other istream to read data from a
* field. All formatting and streaming operations of `std::istream` are
* supported. What you'll typically want to use, however, is the fieldstream
* alias (which defines a @ref basic_fieldstream for `char`). This is similar
* to how e.g. `std::ifstream` relates to `std::basic_ifstream`.
*
* This class has only been tested for the char type (and its default traits).
*/
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
class basic_fieldstream : public std::basic_istream<CHAR, TRAITS>
{
using super = std::basic_istream<CHAR, TRAITS>;
public:
using char_type = CHAR;
using traits_type = TRAITS;
using int_type = typename traits_type::int_type;
using pos_type = typename traits_type::pos_type;
using off_type = typename traits_type::off_type;
basic_fieldstream(field const &f) : super{nullptr}, m_buf{f}
{
super::init(&m_buf);
}
private:
field_streambuf<CHAR, TRAITS> m_buf;
};
using fieldstream = basic_fieldstream<char>;
/// Write a result field to any type of stream
/** This can be convenient when writing a field to an output stream. More
* importantly, it lets you write a field to e.g. a `stringstream` which you
* can then use to read, format and convert the field in ways that to() does
* not support.
*
* Example: parse a field into a variable of the nonstandard
* "<tt>long long</tt>" type.
*
* ```cxx
* extern result R;
* long long L;
* stringstream S;
*
* // Write field's string into S
* S << R[0][0];
*
* // Parse contents of S into L
* S >> L;
* ```
*/
template<typename CHAR>
inline std::basic_ostream<CHAR> &
operator<<(std::basic_ostream<CHAR> &s, field const &value)
{
s.write(value.c_str(), std::streamsize(std::size(value)));
return s;
}
/// Convert a field's value to type `T`.
/** Unlike the "regular" `from_string`, this knows how to deal with null
* values.
*/
template<typename T> inline T from_string(field const &value)
{
if (value.is_null())
{
if constexpr (nullness<T>::has_null)
return nullness<T>::null();
else
internal::throw_null_conversion(type_name<T>);
}
else
{
return from_string<T>(value.view());
}
}
/// Convert a field's value to `nullptr_t`.
/** Yes, you read that right. This conversion does nothing useful. It always
* returns `nullptr`.
*
* Except... what if the field is not null? In that case, this throws
* @ref conversion_error.
*/
template<>
inline std::nullptr_t from_string<std::nullptr_t>(field const &value)
{
if (not value.is_null())
throw conversion_error{
"Extracting non-null field into nullptr_t variable."};
return nullptr;
}
/// Convert a field to a string.
template<> PQXX_LIBEXPORT std::string to_string(field const &value);
} // namespace pqxx
#endif
@@ -0,0 +1,305 @@
#if !defined(PQXX_ARRAY_COMPOSITE_HXX)
# define PQXX_ARRAY_COMPOSITE_HXX
# include <cassert>
# include "pqxx/strconv.hxx"
namespace pqxx::internal
{
// Find the end of a double-quoted string.
/** `input[pos]` must be the opening double quote.
*
* Returns the offset of the first position after the closing quote.
*/
inline std::size_t scan_double_quoted_string(
char const input[], std::size_t size, std::size_t pos,
pqxx::internal::glyph_scanner_func *scan)
{
// XXX: find_char<'"', '\\'>().
auto next{scan(input, size, pos)};
bool at_quote{false};
for (pos = next, next = scan(input, size, pos); pos < size;
pos = next, next = scan(input, size, pos))
{
if (at_quote)
{
if (next - pos == 1 and input[pos] == '"')
{
// We just read a pair of double quotes. Carry on.
at_quote = false;
}
else
{
// We just read one double quote, and now we're at a character that's
// not a second double quote. Ergo, that last character was the
// closing double quote and this is the position right after it.
return pos;
}
}
else if (next - pos == 1)
{
switch (input[pos])
{
case '\\':
// Backslash escape. Skip ahead by one more character.
pos = next;
next = scan(input, size, pos);
break;
case '"':
// This is either the closing double quote, or the first of a pair of
// double quotes.
at_quote = true;
break;
}
}
else
{
// Multibyte character. Carry on.
}
}
if (not at_quote)
throw argument_error{
"Missing closing double-quote: " + std::string{input}};
return pos;
}
/// Un-quote and un-escape a double-quoted SQL string.
inline std::string parse_double_quoted_string(
char const input[], std::size_t end, std::size_t pos,
pqxx::internal::glyph_scanner_func *scan)
{
std::string output;
// Maximum output size is same as the input size, minus the opening and
// closing quotes. Or in the extreme opposite case, the real number could be
// half that. Usually it'll be a pretty close estimate.
output.reserve(std::size_t(end - pos - 2));
for (auto here{scan(input, end, pos)}, next{scan(input, end, here)};
here < end - 1; here = next, next = scan(input, end, here))
{
// A backslash here is always an escape. So is a double-quote, since we're
// inside the double-quoted string. In either case, we can just ignore the
// escape character and use the next character. This is the one redeeming
// feature of SQL's escaping system.
if ((next - here == 1) and (input[here] == '\\' or input[here] == '"'))
{
// Skip escape.
here = next;
next = scan(input, end, here);
}
output.append(input + here, input + next);
}
return output;
}
/// Find the end of an unquoted string in an array or composite-type value.
/** Stops when it gets to the end of the input; or when it sees any of the
* characters in STOP which has not been escaped.
*
* For array values, STOP is a comma, a semicolon, or a closing brace. For
* a value of a composite type, STOP is a comma or a closing parenthesis.
*/
template<char... STOP>
inline std::size_t scan_unquoted_string(
char const input[], std::size_t size, std::size_t pos,
pqxx::internal::glyph_scanner_func *scan)
{
bool at_backslash{false};
auto next{scan(input, size, pos)};
while ((pos < size) and
((next - pos) > 1 or at_backslash or ((input[pos] != STOP) and ...)))
{
pos = next;
next = scan(input, size, pos);
at_backslash =
((not at_backslash) and ((next - pos) == 1) and (input[pos] == '\\'));
}
return pos;
}
/// Parse an unquoted array entry or cfield of a composite-type field.
inline std::string parse_unquoted_string(
char const input[], std::size_t end, std::size_t pos,
pqxx::internal::glyph_scanner_func *scan)
{
std::string output;
bool at_backslash{false};
output.reserve(end - pos);
for (auto next{scan(input, end, pos)}; pos < end;
pos = next, next = scan(input, end, pos))
{
at_backslash =
((not at_backslash) and ((next - pos) == 1) and (input[pos] == '\\'));
if (not at_backslash)
output.append(input + pos, next - pos);
}
return output;
}
/// Parse a field of a composite-type value.
/** `T` is the C++ type of the field we're parsing, and `index` is its
* zero-based number.
*
* Strip off the leading parenthesis or bracket yourself before parsing.
* However, this function will parse the lcosing parenthesis or bracket.
*
* After a successful parse, `pos` will point at `std::end(text)`.
*
* For the purposes of parsing, ranges and arrays count as compositve values,
* so this function supports parsing those. If you specifically need a closing
* parenthesis, check afterwards that `text` did not end in a bracket instead.
*
* @param index Index of the current field, zero-based. It will increment for
* the next field.
* @param input Full input text for the entire composite-type value.
* @param pos Starting position (in `input`) of the field that we're parsing.
* After parsing, this will point at the beginning of the next field if
* there is one, or one position past the last character otherwise.
* @param field Destination for the parsed value.
* @param scan Glyph scanning function for the relevant encoding type.
* @param last_field Number of the last field in the value (zero-based). When
* parsing the last field, this will equal `index`.
*/
template<typename T>
inline void parse_composite_field(
std::size_t &index, std::string_view input, std::size_t &pos, T &field,
glyph_scanner_func *scan, std::size_t last_field)
{
assert(index <= last_field);
auto next{scan(std::data(input), std::size(input), pos)};
if ((next - pos) != 1)
throw conversion_error{"Non-ASCII character in composite-type syntax."};
// Expect a field.
switch (input[pos])
{
case ',':
case ')':
case ']':
// The field is empty, i.e, null.
if constexpr (nullness<T>::has_null)
field = nullness<T>::null();
else
throw conversion_error{
"Can't read composite field " + to_string(index) + ": C++ type " +
type_name<T> + " does not support nulls."};
break;
case '"': {
auto const stop{scan_double_quoted_string(
std::data(input), std::size(input), pos, scan)};
auto const text{
parse_double_quoted_string(std::data(input), stop, pos, scan)};
field = from_string<T>(text);
pos = stop;
}
break;
default: {
auto const stop{scan_unquoted_string<',', ')', ']'>(
std::data(input), std::size(input), pos, scan)};
auto const text{parse_unquoted_string(std::data(input), stop, pos, scan)};
field = from_string<T>(text);
pos = stop;
}
break;
}
// Expect a comma or a closing parenthesis.
next = scan(std::data(input), std::size(input), pos);
if ((next - pos) != 1)
throw conversion_error{
"Unexpected non-ASCII character after composite field: " +
std::string{input}};
if (index < last_field)
{
if (input[pos] != ',')
throw conversion_error{
"Found '" + std::string{input[pos]} +
"' in composite value where comma was expected: " + std::data(input)};
}
else
{
if (input[pos] == ',')
throw conversion_error{
"Composite value contained more fields than the expected " +
to_string(last_field) + ": " + std::data(input)};
if (input[pos] != ')' and input[pos] != ']')
throw conversion_error{
"Composite value has unexpected characters where closing parenthesis "
"was expected: " +
std::string{input}};
if (next != std::size(input))
throw conversion_error{
"Composite value has unexpected text after closing parenthesis: " +
std::string{input}};
}
pos = next;
++index;
}
/// Conservatively estimate buffer size needed for a composite field.
template<typename T>
inline std::size_t size_composite_field_buffer(T const &field)
{
if constexpr (is_unquoted_safe<T>)
{
// Safe to copy, without quotes or escaping. Drop the terminating zero.
return size_buffer(field) - 1;
}
else
{
// + Opening quote.
// + Field budget.
// - Terminating zero.
// + Escaping for each byte in the field's string representation.
// - Escaping for terminating zero.
// + Closing quote.
return 1 + 2 * (size_buffer(field) - 1) + 1;
}
}
template<typename T>
inline void write_composite_field(char *&pos, char *end, T const &field)
{
if constexpr (is_unquoted_safe<T>)
{
// No need for quoting or escaping. Convert it straight into its final
// place in the buffer, and "backspace" the trailing zero.
pos = string_traits<T>::into_buf(pos, end, field) - 1;
}
else
{
// The field may need escaping, which means we need an intermediate buffer.
// To avoid allocating that at run time, we use the end of the buffer that
// we have.
auto const budget{size_buffer(field)};
*pos++ = '"';
// Now escape buf into its final position.
for (char const c : string_traits<T>::to_buf(end - budget, end, field))
{
if ((c == '"') or (c == '\\'))
*pos++ = '\\';
*pos++ = c;
}
*pos++ = '"';
}
*pos++ = ',';
}
} // namespace pqxx::internal
#endif
@@ -0,0 +1,70 @@
#ifndef PQXX_H_CALLGATE
#define PQXX_H_CALLGATE
/*
Here's what a typical gate class definition looks like:
#include <pqxx/internal/callgate.hxx>
namespace pqxx::internal::gate
{
class PQXX_PRIVATE @gateclass@ : callgate<@host@>
{
friend class @client@;
@gateclass@(reference x) : super(x) {}
// Methods here. Use home() to access the host-class object.
};
} // namespace pqxx::internal::gate
*/
namespace pqxx::internal
{
/// Base class for call gates.
/**
* A call gate defines a limited, private interface on the host class that
* specified client classes can access.
*
* The metaphor works as follows: the gate stands in front of a "home," which
* is really a class, and only lets specific friends in.
*
* To implement a call gate that gives client C access to host H,
* * derive a gate class from callgate<H>;
* * make the gate class a friend of H;
* * make C a friend of the gate class; and
* * implement "stuff C can do with H" as private members in the gate class.
*
* This special kind of "gated" friendship gives C private access to H, but
* only through an expressly limited interface. The gate class can access its
* host object as home().
*
* Keep gate classes entirely stateless. They should be ultra-lightweight
* wrappers for their host classes, and be optimized away as much as possible
* by the compiler. Once you start adding state, you're on a slippery slope
* away from the pure, clean, limited interface pattern that gate classes are
* meant to implement.
*
* Ideally, all member functions of the gate class should be one-liners passing
* calls straight on to the host class. It can be useful however to break this
* rule temporarily during inter-class refactoring.
*/
template<typename HOME> class PQXX_PRIVATE callgate
{
protected:
/// This class, to keep constructors easy.
using super = callgate<HOME>;
/// A reference to the host class. Helps keep constructors easy.
using reference = HOME &;
callgate(reference x) : m_home(x) {}
/// The home object. The gate class has full "private" access.
reference home() const noexcept { return m_home; }
private:
reference m_home;
};
} // namespace pqxx::internal
#endif
@@ -0,0 +1,45 @@
#if !defined(PQXX_CONCAT_HXX)
# define PQXX_CONCAT_HXX
# include <string>
# include <string_view>
# include "pqxx/strconv.hxx"
namespace pqxx::internal
{
/// Convert item to a string, write it into [here, end).
template<typename TYPE>
void render_item(TYPE const &item, char *&here, char *end)
{
here = string_traits<TYPE>::into_buf(here, end, item) - 1;
}
// C++20: Support non-random_access_range ranges.
/// Efficiently combine a bunch of items into one big string.
/** Use this as an optimised version of string concatentation. It takes just
* about any type; it will represent each item as a string according to its
* @ref string_traits.
*
* This is a simpler, more specialised version of @ref separated_list for a
* statically known series of items, possibly of different types.
*/
template<typename... TYPE>
[[nodiscard]] inline std::string concat(TYPE... item)
{
std::string buf;
// Size to accommodate string representations of all inputs, minus their
// terminating zero bytes.
buf.resize(size_buffer(item...));
char *const data{buf.data()};
char *here = data;
char *end = data + std::size(buf);
(render_item(item, here, end), ...);
buf.resize(static_cast<std::size_t>(here - data));
return buf;
}
} // namespace pqxx::internal
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,60 @@
/** Enum type for supporting encodings in libpqxx
*
* 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.
*/
#ifndef PQXX_H_ENCODING_GROUP
#define PQXX_H_ENCODING_GROUP
#include <cstddef>
namespace pqxx::internal
{
// Types of encodings supported by PostgreSQL, see
// https://www.postgresql.org/docs/current/static/multibyte.html#CHARSET-TABLE
enum class encoding_group
{
// Handles all single-byte fixed-width encodings
MONOBYTE,
// Multibyte encodings.
// Many of these can embed ASCII-like bytes inside multibyte characters,
// notably Big5, SJIS, SHIFT_JIS_2004, GP18030, GBK, JOHAB, UHC.
BIG5,
EUC_CN,
// TODO: Merge EUC_JP and EUC_JIS_2004?
EUC_JP,
EUC_JIS_2004,
EUC_KR,
EUC_TW,
GB18030,
GBK,
JOHAB,
MULE_INTERNAL,
// TODO: Merge SJIS and SHIFT_JIS_2004?
SJIS,
SHIFT_JIS_2004,
UHC,
UTF8,
};
// TODO:: Can we just use string_view now?
/// Function type: "find the end of the current glyph."
/** This type of function takes a text buffer, and a location in that buffer,
* and returns the location one byte past the end of the current glyph.
*
* The start offset marks the beginning of the current glyph. It must fall
* within the buffer.
*
* There are multiple different glyph scanner implementations, for different
* kinds of encodings.
*/
using glyph_scanner_func =
std::size_t(char const buffer[], std::size_t buffer_len, std::size_t start);
} // namespace pqxx::internal
#endif
@@ -0,0 +1,90 @@
/** Internal string encodings support for libpqxx
*
* 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.
*/
#ifndef PQXX_H_ENCODINGS
#define PQXX_H_ENCODINGS
#include "pqxx/internal/encoding_group.hxx"
#include <string>
#include <string_view>
namespace pqxx::internal
{
char const *name_encoding(int encoding_id);
/// Convert libpq encoding enum or encoding name to its libpqxx group.
encoding_group enc_group(int /* libpq encoding ID */);
encoding_group enc_group(std::string_view);
/// Look up the glyph scanner function for a given encoding group.
/** To identify the glyph boundaries in a buffer, call this to obtain the
* scanner function appropriate for the buffer's encoding. Then, repeatedly
* call the scanner function to find the glyphs.
*/
PQXX_LIBEXPORT glyph_scanner_func *get_glyph_scanner(encoding_group);
// TODO: For ASCII search, treat UTF8/EUC_*/MULE_INTERNAL as MONOBYTE.
/// Find any of the ASCII characters `NEEDLE` in `haystack`.
/** Scans through `haystack` until it finds a single-byte character that
* matches any value in `NEEDLE`.
*
* If it finds one, returns its offset. If not, returns the end of the
* haystack.
*/
template<char... NEEDLE>
inline std::size_t find_char(
glyph_scanner_func *scanner, std::string_view haystack,
std::size_t here = 0u)
{
auto const sz{std::size(haystack)};
auto const data{std::data(haystack)};
while (here < sz)
{
auto next{scanner(data, sz, here)};
// (For some reason gcc had a problem with a right-fold here. But clang
// was fine.)
if ((... or (data[here] == NEEDLE)))
{
// Also check against a multibyte character starting with a bytes which
// just happens to match one of the ASCII bytes we're looking for. It'd
// be cleaner to check that first, but either works. So, let's apply the
// most selective filter first and skip this check in almost all cases.
if (next == here + 1)
return here;
}
// Nope, no hit. Move on.
here = next;
}
return sz;
}
/// Iterate over the glyphs in a buffer.
/** Scans the glyphs in the buffer, and for each, passes its begin and its
* one-past-end pointers to `callback`.
*/
template<typename CALLABLE>
inline void for_glyphs(
encoding_group enc, CALLABLE callback, char const buffer[],
std::size_t buffer_len, std::size_t start = 0)
{
auto const scan{get_glyph_scanner(enc)};
for (std::size_t here = start, next; here < buffer_len; here = next)
{
next = scan(buffer, buffer_len, here);
callback(buffer + here, buffer + next);
}
}
} // namespace pqxx::internal
#endif
@@ -0,0 +1,26 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx
{
class connection;
class errorhandler;
} // namespace pqxx
namespace pqxx::internal::gate
{
class PQXX_PRIVATE connection_errorhandler : callgate<connection>
{
friend class pqxx::errorhandler;
connection_errorhandler(reference x) : super(x) {}
void register_errorhandler(errorhandler *h)
{
home().register_errorhandler(h);
}
void unregister_errorhandler(errorhandler *h)
{
home().unregister_errorhandler(h);
}
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,35 @@
#include <string>
#include <pqxx/internal/callgate.hxx>
#include <pqxx/internal/libpq-forward.hxx>
namespace pqxx
{
class blob;
class largeobject;
} // namespace pqxx
namespace pqxx::internal::gate
{
class PQXX_PRIVATE connection_largeobject : callgate<connection>
{
friend class pqxx::blob;
friend class pqxx::largeobject;
connection_largeobject(reference x) : super(x) {}
pq::PGconn *raw_connection() const { return home().raw_connection(); }
};
class PQXX_PRIVATE const_connection_largeobject : callgate<connection const>
{
friend class pqxx::blob;
friend class pqxx::largeobject;
const_connection_largeobject(reference x) : super(x) {}
std::string error_message() const { return home().err_msg(); }
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,29 @@
#include <pqxx/internal/callgate.hxx>
#include "pqxx/connection.hxx"
namespace pqxx
{
class notification_receiver;
}
namespace pqxx::internal::gate
{
class PQXX_PRIVATE connection_notification_receiver : callgate<connection>
{
friend class pqxx::notification_receiver;
connection_notification_receiver(reference x) : super(x) {}
void add_receiver(notification_receiver *receiver)
{
home().add_receiver(receiver);
}
void remove_receiver(notification_receiver *receiver) noexcept
{
home().remove_receiver(receiver);
}
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,23 @@
#include "pqxx/internal/libpq-forward.hxx"
#include <pqxx/internal/callgate.hxx>
#include "pqxx/pipeline.hxx"
namespace pqxx::internal::gate
{
class PQXX_PRIVATE connection_pipeline : callgate<connection>
{
friend class pqxx::pipeline;
connection_pipeline(reference x) : super(x) {}
void start_exec(char const query[]) { home().start_exec(query); }
pqxx::internal::pq::PGresult *get_result() { return home().get_result(); }
void cancel_query() { home().cancel_query(); }
bool consume_input() noexcept { return home().consume_input(); }
bool is_busy() const noexcept { return home().is_busy(); }
int encoding_id() { return home().encoding_id(); }
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,19 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx::internal
{
class sql_cursor;
}
namespace pqxx::internal::gate
{
class PQXX_PRIVATE connection_sql_cursor : callgate<connection>
{
friend class pqxx::internal::sql_cursor;
connection_sql_cursor(reference x) : super(x) {}
result exec(char const query[]) { return home().exec(query); }
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,15 @@
#include <pqxx/internal/callgate.hxx>
#include "pqxx/connection.hxx"
namespace pqxx::internal::gate
{
class PQXX_PRIVATE connection_stream_from : callgate<connection>
{
friend class pqxx::stream_from;
connection_stream_from(reference x) : super{x} {}
auto read_copy_line() { return home().read_copy_line(); }
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,17 @@
#include <pqxx/internal/callgate.hxx>
#include "pqxx/stream_to.hxx"
namespace pqxx::internal::gate
{
class PQXX_PRIVATE connection_stream_to : callgate<connection>
{
friend class pqxx::stream_to;
connection_stream_to(reference x) : super(x) {}
void write_copy_line(std::string_view line) { home().write_copy_line(line); }
void end_copy_write() { home().end_copy_write(); }
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,44 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx
{
class connection;
}
namespace pqxx::internal::gate
{
class PQXX_PRIVATE connection_transaction : callgate<connection>
{
friend class pqxx::transaction_base;
connection_transaction(reference x) : super(x) {}
template<typename STRING> result exec(STRING query, std::string_view desc)
{
return home().exec(query, desc);
}
void register_transaction(transaction_base *t)
{
home().register_transaction(t);
}
void unregister_transaction(transaction_base *t) noexcept
{
home().unregister_transaction(t);
}
auto read_copy_line() { return home().read_copy_line(); }
void write_copy_line(std::string_view line) { home().write_copy_line(line); }
void end_copy_write() { home().end_copy_write(); }
result exec_prepared(zview statement, internal::c_params const &args)
{
return home().exec_prepared(statement, args);
}
result exec_params(zview query, internal::c_params const &args)
{
return home().exec_params(query, args);
}
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,13 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx::internal::gate
{
class PQXX_PRIVATE errorhandler_connection : callgate<errorhandler>
{
friend class pqxx::connection;
errorhandler_connection(reference x) : super(x) {}
void unregister() noexcept { home().unregister(); }
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,24 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx::internal::gate
{
class PQXX_PRIVATE icursor_iterator_icursorstream : callgate<icursor_iterator>
{
friend class pqxx::icursorstream;
icursor_iterator_icursorstream(reference x) : super(x) {}
icursor_iterator::difference_type pos() const noexcept
{
return home().pos();
}
icursor_iterator *get_prev() { return home().m_prev; }
void set_prev(icursor_iterator *i) { home().m_prev = i; }
icursor_iterator *get_next() { return home().m_next; }
void set_next(icursor_iterator *i) { home().m_next = i; }
void fill(result const &r) { home().fill(r); }
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,32 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx::internal::gate
{
class PQXX_PRIVATE icursorstream_icursor_iterator : callgate<icursorstream>
{
friend class pqxx::icursor_iterator;
icursorstream_icursor_iterator(reference x) : super(x) {}
void insert_iterator(icursor_iterator *i) noexcept
{
home().insert_iterator(i);
}
void remove_iterator(icursor_iterator *i) const noexcept
{
home().remove_iterator(i);
}
icursorstream::size_type forward() { return home().forward(); }
icursorstream::size_type forward(icursorstream::size_type n)
{
return home().forward(n);
}
void service_iterators(icursorstream::difference_type p)
{
home().service_iterators(p);
}
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,14 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx::internal::gate
{
class PQXX_PRIVATE result_connection : callgate<result const>
{
friend class pqxx::connection;
result_connection(reference x) : super(x) {}
operator bool() const { return bool(home()); }
bool operator!() const { return not home(); }
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,24 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx::internal::gate
{
class PQXX_PRIVATE result_creation : callgate<result const>
{
friend class pqxx::connection;
friend class pqxx::pipeline;
result_creation(reference x) : super(x) {}
static result create(
internal::pq::PGresult *rhs, std::shared_ptr<std::string> const &query,
encoding_group enc)
{
return result(rhs, query, enc);
}
void check_status(std::string_view desc = ""sv) const
{
return home().check_status(desc);
}
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,16 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx::internal::gate
{
class PQXX_PRIVATE result_pipeline : callgate<result const>
{
friend class pqxx::pipeline;
result_pipeline(reference x) : super(x) {}
std::shared_ptr<std::string const> query_ptr() const
{
return home().query_ptr();
}
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,13 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx::internal::gate
{
class PQXX_PRIVATE result_sql_cursor : callgate<result const>
{
friend class pqxx::internal::sql_cursor;
result_sql_cursor(reference x) : super(x) {}
char const *cmd_status() const noexcept { return home().cmd_status(); }
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,10 @@
#include <pqxx/internal/callgate.hxx>
namespace pqxx::internal::gate
{
class PQXX_PRIVATE transaction_sql_cursor : callgate<transaction_base>
{
friend class pqxx::internal::sql_cursor;
transaction_sql_cursor(reference x) : super(x) {}
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,30 @@
#include <pqxx/internal/callgate.hxx>
#include "pqxx/transaction_base.hxx"
namespace pqxx::internal::gate
{
class PQXX_PRIVATE transaction_transaction_focus : callgate<transaction_base>
{
friend class pqxx::transaction_focus;
transaction_transaction_focus(reference x) : super(x) {}
void register_focus(transaction_focus *focus)
{
home().register_focus(focus);
}
void unregister_focus(transaction_focus *focus) noexcept
{
home().unregister_focus(focus);
}
void register_pending_error(zview error)
{
home().register_pending_error(error);
}
void register_pending_error(std::string &&error)
{
home().register_pending_error(std::move(error));
}
};
} // namespace pqxx::internal::gate
@@ -0,0 +1,22 @@
/* Compiler deficiency workarounds for compiling libpqxx headers.
*
* To be included at the end of each libpqxx header, in order to restore the
* client program's settings.
*
* 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.
*/
// NO GUARDS HERE! This code should be executed every time!
#if defined(_MSC_VER)
# pragma warning(pop) // Restore compiler's warning state
#endif
#if !defined(PQXX_HEADER_PRE)
# error "Include pqxx/internal/header-post.hxx AFTER its 'pre' counterpart."
#endif
#undef PQXX_HEADER_PRE
@@ -0,0 +1,169 @@
/* Compiler settings for compiling libpqxx headers, and workarounds for all.
*
* Include this before including any other libpqxx headers from within libpqxx.
* And to balance it out, also include header-post.hxx at the end of the batch
* of headers.
*
* The public libpqxx headers (e.g. `<pqxx/connection>`) include this already;
* there's no need to do this from within an application.
*
* Include this file at the highest aggregation level possible to avoid nesting
* and to keep things simple.
*
* 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.
*/
// NO GUARD HERE! This part should be included every time this file is.
#if defined(_MSC_VER)
// Save compiler's warning state, and set warning level 4 for maximum
// sensitivity to warnings.
# pragma warning(push, 4)
// Visual C++ generates some entirely unreasonable warnings. Disable them.
// Copy constructor could not be generated.
# pragma warning(disable : 4511)
// Assignment operator could not be generated.
# pragma warning(disable : 4512)
// Can't expose outside classes without exporting them. Except the MSVC docs
// say please ignore the warning if it's a standard library class.
# pragma warning(disable : 4251)
// Can't derive library classes from outside classes without exporting them.
// Except the MSVC docs say please ignore the warning if the parent class is
// in the standard library.
# pragma warning(disable : 4275)
// Can't inherit from non-exported class.
# pragma warning(disable : 4275)
#endif // _MSC_VER
#if defined(PQXX_HEADER_PRE)
# error "Avoid nesting #include of pqxx/internal/header-pre.hxx."
#endif
#define PQXX_HEADER_PRE
// Workarounds & definitions that need to be included even in library's headers
#include "pqxx/config-public-compiler.h"
// Enable ISO-646 alternative operaotr representations: "and" instead of "&&"
// etc. on older compilers. C++20 removes this header.
#if __has_include(<ciso646>)
# include <ciso646>
#endif
#if defined(PQXX_HAVE_GCC_PURE)
/// Declare function "pure": no side effects, only reads globals and its args.
# define PQXX_PURE __attribute__((pure))
#else
# define PQXX_PURE /* pure */
#endif
#if defined(__GNUC__)
/// Tell the compiler to optimise a function for size, not speed.
# define PQXX_COLD __attribute__((cold))
#else
# define PQXX_COLD /* cold */
#endif
// Workarounds for Windows
#ifdef _WIN32
/* For now, export DLL symbols if _DLL is defined. This is done automatically
* by the compiler when linking to the dynamic version of the runtime library,
* according to "gzh"
*/
# if defined(PQXX_SHARED) && !defined(PQXX_LIBEXPORT)
# define PQXX_LIBEXPORT __declspec(dllimport)
# endif // PQXX_SHARED && !PQXX_LIBEXPORT
// Workarounds for Microsoft Visual C++
# ifdef _MSC_VER
// Suppress vtables on abstract classes.
# define PQXX_NOVTABLE __declspec(novtable)
// Automatically link with the appropriate libpq (static or dynamic, debug or
// release). The default is to use the release DLL. Define PQXX_PQ_STATIC to
// link to a static version of libpq, and _DEBUG to link to a debug version.
// The two may be combined.
# if defined(PQXX_AUTOLINK)
# if defined(PQXX_PQ_STATIC)
# ifdef _DEBUG
# pragma comment(lib, "libpqd")
# else
# pragma comment(lib, "libpq")
# endif
# else
# ifdef _DEBUG
# pragma comment(lib, "libpqddll")
# else
# pragma comment(lib, "libpqdll")
# endif
# endif
# endif
// If we're not compiling libpqxx itself, automatically link with the
// appropriate libpqxx library. To link with the libpqxx DLL, define
// PQXX_SHARED; the default is to link with the static library. A static link
// is the recommended practice.
//
// The preprocessor macro PQXX_INTERNAL is used to detect whether we
// are compiling the libpqxx library itself. When you compile the library
// yourself using your own project file, make sure to include this macro.
# if defined(PQXX_AUTOLINK) && !defined(PQXX_INTERNAL)
# ifdef PQXX_SHARED
# ifdef _DEBUG
# pragma comment(lib, "libpqxxD")
# else
# pragma comment(lib, "libpqxx")
# endif
# else // !PQXX_SHARED
# ifdef _DEBUG
# pragma comment(lib, "libpqxx_staticD")
# else
# pragma comment(lib, "libpqxx_static")
# endif
# endif
# endif
# endif // _MSC_VER
#elif defined(PQXX_HAVE_GCC_VISIBILITY) // !_WIN32
# define PQXX_LIBEXPORT __attribute__((visibility("default")))
# define PQXX_PRIVATE __attribute__((visibility("hidden")))
#endif // PQXX_HAVE_GCC_VISIBILITY
#ifndef PQXX_LIBEXPORT
# define PQXX_LIBEXPORT /* libexport */
#endif
#ifndef PQXX_PRIVATE
# define PQXX_PRIVATE /* private */
#endif
#ifndef PQXX_NOVTABLE
# define PQXX_NOVTABLE /* novtable */
#endif
// C++20: Assume support.
#if defined(PQXX_HAVE_LIKELY)
# define PQXX_LIKELY [[likely]]
# define PQXX_UNLIKELY [[unlikely]]
#else
# define PQXX_LIKELY /* [[likely]] */
# define PQXX_UNLIKELY /* [[unlikely]] */
#endif
@@ -0,0 +1,15 @@
/// End a code block started by "ignore-deprecated-pre.hxx".
#if !defined(PQXX_IGNORING_DEPRECATED)
# error "Ended an 'ignore-deprecated' block while none was active."
#endif
#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif // __GNUC__
#ifdef _MSC_VER
# pragma warning(pop)
#endif
#undef PQXX_IGNORING_DEPRECATED
@@ -0,0 +1,28 @@
/** Start a block of deprecated code which may call other deprecated code.
*
* Most compilers will emit warnings when deprecated code is invoked from
* non-deprecated code. But some compilers (notably gcc) will always emit the
* warning even when the calling code is also deprecated.
*
* This header starts a block where those warnings are suppressed. It can be
* included inside a code block.
*
* Always match the #include with a closing #include of
* "ignore-deprecated-post.hxx". To avoid mistakes, keep the enclosed area as
* small as possible.
*/
#if defined(PQXX_IGNORING_DEPRECATED)
# error "Started an 'ignore-deprecated' block inside another."
#endif
#define PQXX_IGNORING_DEPRECATED
#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif // __GNUC__
#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable : 4996)
#endif
@@ -0,0 +1,31 @@
/** Minimal forward declarations of libpq types needed in libpqxx headers.
*
* 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.
*/
extern "C"
{
struct pg_conn;
struct pg_result;
struct pgNotify;
}
/// Forward declarations of libpq types as needed in libpqxx headers.
namespace pqxx::internal::pq
{
using PGconn = pg_conn;
using PGresult = pg_result;
using PGnotify = pgNotify;
using PQnoticeProcessor = void (*)(void *, char const *);
} // namespace pqxx::internal::pq
namespace pqxx
{
/// PostgreSQL database row identifier.
using oid = unsigned int;
} // namespace pqxx
@@ -0,0 +1,124 @@
/** Result loops.
*
* 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.
*/
#ifndef PQXX_H_RESULT_ITER
#define PQXX_H_RESULT_ITER
#include <memory>
#include "pqxx/strconv.hxx"
namespace pqxx
{
class result;
} // namespace pqxx
namespace pqxx::internal
{
// C++20: Replace with generator?
/// Iterator for looped unpacking of a result.
template<typename... TYPE> class result_iter
{
public:
using value_type = std::tuple<TYPE...>;
/// Construct an "end" iterator.
result_iter() = default;
explicit result_iter(result const &home) :
m_home{&home}, m_size{std::size(home)}
{
if (not std::empty(home))
read();
}
result_iter(result_iter const &) = default;
result_iter &operator++()
{
m_index++;
if (m_index >= m_size)
m_home = nullptr;
else
read();
return *this;
}
/// Comparison only works for comparing to end().
bool operator==(result_iter const &rhs) const
{
return m_home == rhs.m_home;
}
bool operator!=(result_iter const &rhs) const { return not(*this == rhs); }
value_type const &operator*() const { return m_value; }
private:
void read() { (*m_home)[m_index].convert(m_value); }
result const *m_home{nullptr};
result::size_type m_index{0};
result::size_type m_size;
value_type m_value;
};
template<typename... TYPE> class result_iteration
{
public:
using iterator = result_iter<TYPE...>;
explicit result_iteration(result const &home) : m_home{home}
{
constexpr auto tup_size{sizeof...(TYPE)};
if (home.columns() != tup_size)
throw usage_error{internal::concat(
"Tried to extract ", to_string(tup_size),
" field(s) from a result with ", to_string(home.columns()),
" column(s).")};
}
iterator begin() const
{
if (std::size(m_home) == 0)
return end();
else
return iterator{m_home};
}
iterator end() const { return {}; }
private:
pqxx::result const &m_home;
};
} // namespace pqxx::internal
template<typename... TYPE> inline auto pqxx::result::iter() const
{
return pqxx::internal::result_iteration<TYPE...>{*this};
}
template<typename CALLABLE>
inline void pqxx::result::for_each(CALLABLE &&func) const
{
using args_tuple = internal::args_t<decltype(func)>;
constexpr auto sz{std::tuple_size_v<args_tuple>};
static_assert(
sz > 0,
"Callback for for_each must take parameters, one for each column in the "
"result.");
auto const cols{this->columns()};
if (sz != cols)
throw usage_error{internal::concat(
"Callback to for_each takes ", sz, "parameter", (sz == 1) ? "" : "s",
", but result set has ", cols, "field", (cols == 1) ? "" : "s", ".")};
using pass_tuple = pqxx::internal::strip_types_t<args_tuple>;
for (auto const r : *this) std::apply(func, r.as_tuple<pass_tuple>());
}
#endif
@@ -0,0 +1,389 @@
/* Definitions for the pqxx::result class and support classes.
*
* pqxx::result represents the set of result rows from a database query.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result instead.
*
* 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.
*/
#ifndef PQXX_H_RESULT_ITERATOR
#define PQXX_H_RESULT_ITERATOR
#include "pqxx/row.hxx"
/* Result iterator.
*
* Don't include this header from your own application; it is included for you
* by other libpqxx headers.
*/
namespace pqxx
{
/// Iterator for rows in a result. Use as result::const_iterator.
/** A result, once obtained, cannot be modified. Therefore there is no
* plain iterator type for result. However its const_iterator type can be
* used to inspect its rows without changing them.
*/
class PQXX_LIBEXPORT const_result_iterator : public row
{
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = row const;
using pointer = row const *;
using reference = row;
using size_type = result_size_type;
using difference_type = result_difference_type;
#include "pqxx/internal/ignore-deprecated-pre.hxx"
/// Create an iterator, but in an unusable state.
const_result_iterator() noexcept = default;
/// Copy an iterator.
const_result_iterator(const_result_iterator const &) noexcept = default;
/// Move an iterator.
const_result_iterator(const_result_iterator &&) noexcept = default;
/// Begin iterating a @ref row.
const_result_iterator(row const &t) noexcept : row{t} {}
#include "pqxx/internal/ignore-deprecated-post.hxx"
/**
* @name Dereferencing operators
*
* An iterator "points to" its own row, which is also itself. This makes it
* easy to address a @ref result as a two-dimensional container, without
* going through the intermediate step of dereferencing the iterator. It
* makes the interface similar to C pointer/array semantics.
*
* IIRC Alex Stepanov, the inventor of the STL, once remarked that having
* this as standard behaviour for pointers would be useful in some
* algorithms. So even if this makes me look foolish, I would seem to be in
* distinguished company.
*/
//@{
/// Dereference the iterator.
[[nodiscard]] pointer operator->() const { return this; }
#include "pqxx/internal/ignore-deprecated-pre.hxx"
/// Dereference the iterator.
[[nodiscard]] reference operator*() const { return *this; }
#include "pqxx/internal/ignore-deprecated-post.hxx"
//@}
/**
* @name Field access
*/
//@{
using row::back;
using row::front;
using row::operator[];
using row::at;
using row::rownumber;
//@}
/**
* @name Manipulations
*/
//@{
const_result_iterator &operator=(const_result_iterator const &rhs)
{
#include "pqxx/internal/ignore-deprecated-pre.hxx"
row::operator=(rhs);
#include "pqxx/internal/ignore-deprecated-post.hxx"
return *this;
}
const_result_iterator &operator=(const_result_iterator &&rhs)
{
#include "pqxx/internal/ignore-deprecated-pre.hxx"
row::operator=(std::move(rhs));
#include "pqxx/internal/ignore-deprecated-post.hxx"
return *this;
}
const_result_iterator operator++(int);
const_result_iterator &operator++()
{
++m_index;
return *this;
}
const_result_iterator operator--(int);
const_result_iterator &operator--()
{
--m_index;
return *this;
}
const_result_iterator &operator+=(difference_type i)
{
m_index += i;
return *this;
}
const_result_iterator &operator-=(difference_type i)
{
m_index -= i;
return *this;
}
/// Interchange two iterators in an exception-safe manner.
void swap(const_result_iterator &other) noexcept
{
#include "pqxx/internal/ignore-deprecated-pre.hxx"
row::swap(other);
#include "pqxx/internal/ignore-deprecated-post.hxx"
}
//@}
/**
* @name Comparisons
*/
//@{
[[nodiscard]] bool operator==(const_result_iterator const &i) const
{
return m_index == i.m_index;
}
[[nodiscard]] bool operator!=(const_result_iterator const &i) const
{
return m_index != i.m_index;
}
[[nodiscard]] bool operator<(const_result_iterator const &i) const
{
return m_index < i.m_index;
}
[[nodiscard]] bool operator<=(const_result_iterator const &i) const
{
return m_index <= i.m_index;
}
[[nodiscard]] bool operator>(const_result_iterator const &i) const
{
return m_index > i.m_index;
}
[[nodiscard]] bool operator>=(const_result_iterator const &i) const
{
return m_index >= i.m_index;
}
//@}
/**
* @name Arithmetic operators
*/
//@{
[[nodiscard]] inline const_result_iterator operator+(difference_type) const;
friend const_result_iterator
operator+(difference_type, const_result_iterator const &);
[[nodiscard]] inline const_result_iterator operator-(difference_type) const;
[[nodiscard]] inline difference_type
operator-(const_result_iterator const &) const;
//@}
private:
friend class pqxx::result;
const_result_iterator(pqxx::result const *r, result_size_type i) noexcept :
row{*r, i, r->columns()}
{}
};
/// Reverse iterator for result. Use as result::const_reverse_iterator.
class PQXX_LIBEXPORT const_reverse_result_iterator
: private const_result_iterator
{
public:
using super = const_result_iterator;
using iterator_type = const_result_iterator;
using iterator_type::difference_type;
using iterator_type::iterator_category;
using iterator_type::pointer;
using value_type = iterator_type::value_type;
using reference = iterator_type::reference;
/// Create an iterator, but in an unusable state.
const_reverse_result_iterator() = default;
/// Copy an iterator.
const_reverse_result_iterator(const_reverse_result_iterator const &rhs) =
default;
/// Copy a reverse iterator from a regular iterator.
explicit const_reverse_result_iterator(const_result_iterator const &rhs) :
const_result_iterator{rhs}
{
super::operator--();
}
/// Move a regular iterator into a reverse iterator.
explicit const_reverse_result_iterator(const_result_iterator const &&rhs) :
const_result_iterator{std::move(rhs)}
{
super::operator--();
}
/// Return the underlying "regular" iterator (as per standard library).
[[nodiscard]] PQXX_PURE const_result_iterator base() const noexcept;
/**
* @name Dereferencing operators
*/
//@{
/// Dereference iterator.
using const_result_iterator::operator->;
/// Dereference iterator.
using const_result_iterator::operator*;
//@}
/**
* @name Field access
*/
//@{
using const_result_iterator::back;
using const_result_iterator::front;
using const_result_iterator::operator[];
using const_result_iterator::at;
using const_result_iterator::rownumber;
//@}
/**
* @name Manipulations
*/
//@{
const_reverse_result_iterator &
operator=(const_reverse_result_iterator const &r)
{
iterator_type::operator=(r);
return *this;
}
const_reverse_result_iterator &operator=(const_reverse_result_iterator &&r)
{
iterator_type::operator=(std::move(r));
return *this;
}
const_reverse_result_iterator &operator++()
{
iterator_type::operator--();
return *this;
}
const_reverse_result_iterator operator++(int);
const_reverse_result_iterator &operator--()
{
iterator_type::operator++();
return *this;
}
const_reverse_result_iterator operator--(int);
const_reverse_result_iterator &operator+=(difference_type i)
{
iterator_type::operator-=(i);
return *this;
}
const_reverse_result_iterator &operator-=(difference_type i)
{
iterator_type::operator+=(i);
return *this;
}
void swap(const_reverse_result_iterator &other) noexcept
{
const_result_iterator::swap(other);
}
//@}
/**
* @name Arithmetic operators
*/
//@{
[[nodiscard]] const_reverse_result_iterator
operator+(difference_type i) const
{
return const_reverse_result_iterator(base() - i);
}
[[nodiscard]] const_reverse_result_iterator operator-(difference_type i)
{
return const_reverse_result_iterator(base() + i);
}
[[nodiscard]] difference_type
operator-(const_reverse_result_iterator const &rhs) const
{
return rhs.const_result_iterator::operator-(*this);
}
//@}
/**
* @name Comparisons
*/
//@{
[[nodiscard]] bool
operator==(const_reverse_result_iterator const &rhs) const noexcept
{
return iterator_type::operator==(rhs);
}
[[nodiscard]] bool
operator!=(const_reverse_result_iterator const &rhs) const noexcept
{
return not operator==(rhs);
}
[[nodiscard]] bool operator<(const_reverse_result_iterator const &rhs) const
{
return iterator_type::operator>(rhs);
}
[[nodiscard]] bool operator<=(const_reverse_result_iterator const &rhs) const
{
return iterator_type::operator>=(rhs);
}
[[nodiscard]] bool operator>(const_reverse_result_iterator const &rhs) const
{
return iterator_type::operator<(rhs);
}
[[nodiscard]] bool operator>=(const_reverse_result_iterator const &rhs) const
{
return iterator_type::operator<=(rhs);
}
//@}
};
inline const_result_iterator
const_result_iterator::operator+(result::difference_type o) const
{
return {&m_result, size_type(result::difference_type(m_index) + o)};
}
inline const_result_iterator
operator+(result::difference_type o, const_result_iterator const &i)
{
return i + o;
}
inline const_result_iterator
const_result_iterator::operator-(result::difference_type o) const
{
return {&m_result, result_size_type(result::difference_type(m_index) - o)};
}
inline result::difference_type
const_result_iterator::operator-(const const_result_iterator &i) const
{
return result::difference_type(num() - i.num());
}
inline const_result_iterator result::end() const noexcept
{
return {this, size()};
}
inline const_result_iterator result::cend() const noexcept
{
return end();
}
inline const_reverse_result_iterator
operator+(result::difference_type n, const_reverse_result_iterator const &i)
{
return const_reverse_result_iterator{i.base() - n};
}
} // namespace pqxx
#endif
@@ -0,0 +1,118 @@
/** Internal wrapper for SQL cursors. Supports higher-level cursor classes.
*
* DO NOT INCLUDE THIS FILE DIRECTLY. Other headers include it for you.
*
* 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.
*/
#ifndef PQXX_H_SQL_CURSOR
#define PQXX_H_SQL_CURSOR
namespace pqxx::internal
{
/// Cursor with SQL positioning semantics.
/** Thin wrapper around an SQL cursor, with SQL's ideas of positioning.
*
* SQL cursors have pre-increment/pre-decrement semantics, with on either end
* of the result set a special position that does not repesent a row. This
* class models SQL cursors for the purpose of implementing more C++-like
* semantics on top.
*
* Positions of actual rows are numbered starting at 1. Position 0 exists but
* does not refer to a row. There is a similar non-row position at the end of
* the result set.
*
* Don't use this at home. You deserve better. Use the stateles_cursor
* instead.
*/
class PQXX_LIBEXPORT sql_cursor : public cursor_base
{
public:
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);
sql_cursor(
transaction_base &t, std::string_view cname,
cursor_base::ownership_policy op);
~sql_cursor() noexcept { close(); }
result fetch(difference_type rows, difference_type &displacement);
result fetch(difference_type rows)
{
difference_type d = 0;
return fetch(rows, d);
}
difference_type move(difference_type rows, difference_type &displacement);
difference_type move(difference_type rows)
{
difference_type d = 0;
return move(rows, d);
}
/// Current position, or -1 for unknown
/**
* The starting position, just before the first row, counts as position zero.
*
* Position may be unknown if (and only if) this cursor was adopted, and has
* never hit its starting position (position zero).
*/
difference_type pos() const noexcept { return m_pos; }
/// End position, or -1 for unknown
/**
* Returns the final position, just after the last row in the result set. The
* starting position, just before the first row, counts as position zero.
*
* End position is unknown until it is encountered during use.
*/
difference_type endpos() const noexcept { return m_endpos; }
/// Return zero-row result for this cursor.
result const &empty_result() const noexcept { return m_empty_result; }
void close() noexcept;
private:
difference_type adjust(difference_type hoped, difference_type actual);
static std::string stridestring(difference_type);
/// Initialize cached empty result. Call only at beginning or end!
void init_empty_result(transaction_base &);
/// Connection in which this cursor lives.
connection &m_home;
/// Zero-row result from this cursor (or plain empty one if cursor is
/// adopted)
result m_empty_result;
result m_cached_current_row;
/// Is this cursor adopted (as opposed to created by this cursor object)?
bool m_adopted;
/// Will this cursor object destroy its SQL cursor when it dies?
cursor_base::ownership_policy m_ownership;
/// At starting position (-1), somewhere in the middle (0), or past end (1)
int m_at_end;
/// Position, or -1 for unknown
difference_type m_pos;
/// End position, or -1 for unknown
difference_type m_endpos = -1;
};
PQXX_LIBEXPORT result_size_type obtain_stateless_cursor_size(sql_cursor &);
PQXX_LIBEXPORT result stateless_cursor_retrieve(
sql_cursor &, result::difference_type size,
result::difference_type begin_pos, result::difference_type end_pos);
} // namespace pqxx::internal
#endif
@@ -0,0 +1,131 @@
/** Common implementation for statement parameter lists.
*
* These are used for both prepared statements and parameterized statements.
*
* DO NOT INCLUDE THIS FILE DIRECTLY. Other headers include it for you.
*
* 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.
*/
#ifndef PQXX_H_STATEMENT_PARAMETER
#define PQXX_H_STATEMENT_PARAMETER
#include <cstring>
#include <functional>
#include <iterator>
#include <string>
#include <vector>
#include "pqxx/binarystring.hxx"
#include "pqxx/strconv.hxx"
#include "pqxx/util.hxx"
namespace pqxx::internal
{
template<typename ITERATOR>
constexpr inline auto const iterator_identity{
[](decltype(*std::declval<ITERATOR>()) x) { return x; }};
/// Marker type: pass a dynamically-determined number of statement parameters.
/** @deprecated Use @ref params instead.
*
* Normally when invoking a prepared or parameterised statement, the number
* of parameters is known at compile time. For instance,
* `t.exec_prepared("foo", 1, "x");` executes statement `foo` with two
* parameters, an `int` and a C string.
*
* But sometimes you may want to pass a number of parameters known only at run
* time. In those cases, a @ref dynamic_params encodes a dynamically
* determined number of parameters. You can mix these with regular, static
* parameter lists, and you can re-use them for multiple statement invocations.
*
* A dynamic_params object does not store copies of its parameters, so make
* sure they remain accessible until you've executed the statement.
*
* The ACCESSOR is an optional callable (such as a lambda). If you pass an
* accessor `a`, then each parameter `p` goes into your statement as `a(p)`.
*/
template<typename IT, typename ACCESSOR = decltype(iterator_identity<IT>)>
class dynamic_params
{
public:
/// Wrap a sequence of pointers or iterators.
constexpr dynamic_params(IT begin, IT end) :
m_begin(begin), m_end(end), m_accessor(iterator_identity<IT>)
{}
/// Wrap a sequence of pointers or iterators.
/** This version takes an accessor callable. If you pass an accessor `acc`,
* then any parameter `p` will go into the statement's parameter list as
* `acc(p)`.
*/
constexpr dynamic_params(IT begin, IT end, ACCESSOR &acc) :
m_begin(begin), m_end(end), m_accessor(acc)
{}
/// Wrap a container.
template<typename C>
explicit constexpr dynamic_params(C &container) :
dynamic_params(std::begin(container), std::end(container))
{}
/// Wrap a container.
/** This version takes an accessor callable. If you pass an accessor `acc`,
* then any parameter `p` will go into the statement's parameter list as
* `acc(p)`.
*/
template<typename C>
explicit constexpr dynamic_params(C &container, ACCESSOR &acc) :
dynamic_params(std::begin(container), std::end(container), acc)
{}
constexpr IT begin() const noexcept { return m_begin; }
constexpr IT end() const noexcept { return m_end; }
constexpr auto access(decltype(*std::declval<IT>()) value) const
-> decltype(std::declval<ACCESSOR>()(value))
{
return m_accessor(value);
}
private:
IT const m_begin, m_end;
ACCESSOR m_accessor = iterator_identity<IT>;
};
/// Internal type: encode statement parameters.
/** Compiles arguments for prepared statements and parameterised queries into
* a format that can be passed into libpq.
*
* Objects of this type are meant to be short-lived: a `c_params` lives and
* dies entirely within the call to execute. So, for example, if you pass in a
* non-null pointer as a parameter, @ref params may simply use that pointer as
* a parameter value, without arranging longer-term storage for the data to
* which it points. All values referenced by parameters must remain "live"
* until the parameterised or prepared statement has been executed.
*/
struct PQXX_LIBEXPORT c_params
{
c_params() = default;
/// Copying these objects is pointless and expensive. Don't do it.
c_params(c_params const &) = delete;
c_params(c_params &&) = default;
/// Pre-allocate storage for `n` parameters.
void reserve(std::size_t n) &;
/// As used by libpq: pointers to parameter values.
std::vector<char const *> values;
/// As used by libpq: lengths of non-null arguments, in bytes.
std::vector<int> lengths;
/// As used by libpq: effectively boolean "is this a binary parameter?"
std::vector<format> formats;
};
} // namespace pqxx::internal
#endif
@@ -0,0 +1,105 @@
/** Stream iterators.
*
* 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.
*/
#ifndef PQXX_H_STREAM_ITERATOR
#define PQXX_H_STREAM_ITERATOR
#include <memory>
namespace pqxx
{
class stream_from;
}
namespace pqxx::internal
{
// C++20: Replace with generator?
/// Input iterator for stream_from.
/** Just barely enough to support range-based "for" loops. Don't assume that
* any of the usual behaviour works beyond that.
*/
template<typename... TYPE> class stream_input_iterator
{
public:
using value_type = std::tuple<TYPE...>;
/// Construct an "end" iterator.
stream_input_iterator() = default;
explicit stream_input_iterator(stream_from &home) : m_home(&home)
{
advance();
}
stream_input_iterator(stream_input_iterator const &) = default;
stream_input_iterator &operator++()
{
advance();
return *this;
}
value_type const &operator*() const { return m_value; }
/// Comparison only works for comparing to end().
bool operator==(stream_input_iterator const &rhs) const
{
return m_home == rhs.m_home;
}
/// Comparison only works for comparing to end().
bool operator!=(stream_input_iterator const &rhs) const
{
return not(*this == rhs);
}
private:
void advance()
{
if (m_home == nullptr)
throw usage_error{"Moving stream_from iterator beyond end()."};
if (not((*m_home) >> m_value))
m_home = nullptr;
}
stream_from *m_home{nullptr};
value_type m_value;
};
// C++20: Replace with generator?
/// Iteration over a @ref stream_from.
template<typename... TYPE> class stream_input_iteration
{
public:
using iterator = stream_input_iterator<TYPE...>;
explicit stream_input_iteration(stream_from &home) : m_home{home} {}
iterator begin() const { return iterator{m_home}; }
iterator end() const { return {}; }
private:
stream_from &m_home;
};
// C++20: Replace with generator?
/// Iteration over a @ref stream_from, deleting it once done.
template<typename... TYPE> class owning_stream_input_iteration
{
public:
using iterator = stream_input_iterator<TYPE...>;
explicit owning_stream_input_iteration(std::unique_ptr<stream_from> &&home) :
m_home{std::move(home)}
{}
iterator begin() const { return iterator{*m_home.get()}; }
iterator end() const { return {}; }
private:
std::unique_ptr<stream_from> m_home;
};
} // namespace pqxx::internal
#endif
@@ -0,0 +1,18 @@
#if !defined(PQXX_WAIT_HXX)
# define PQXX_WAIT_HXX
namespace pqxx::internal
{
/// Wait.
/** This is normally `std::this_thread::sleep_for()`. But MinGW's `thread`
* header doesn't work, so we must be careful about including it.
*/
void PQXX_LIBEXPORT wait_for(unsigned int microseconds);
/// Wait for a socket to be ready for reading/writing, or timeout.
PQXX_LIBEXPORT void wait_fd(
int fd, bool for_read, bool for_write, unsigned seconds = 1,
unsigned microseconds = 0);
} // namespace pqxx::internal
#endif
+8
View File
@@ -0,0 +1,8 @@
/** Transaction isolation levels.
*
* Policies and traits describing SQL transaction isolation levels
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/isolation.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,75 @@
/* Definitions for transaction isolation levels, and such.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/isolation instead.
*
* 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.
*/
#ifndef PQXX_H_ISOLATION
#define PQXX_H_ISOLATION
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include "pqxx/util.hxx"
namespace pqxx
{
/// Should a transaction be read-only, or read-write?
/** No, this is not an isolation level. So it really doesn't belong here.
* But it's not really worth a separate header.
*/
enum class write_policy
{
read_only,
read_write
};
/// Transaction isolation levels.
/** These are as defined in the SQL standard. But there are a few notes
* specific to PostgreSQL.
*
* First, postgres does not support "read uncommitted." The lowest level you
* can get is "read committed," which is better. PostgreSQL is built on the
* MVCC paradigm, which guarantees "read committed" isolation without any
* additional performance overhead, so there was no point in providing the
* lower level.
*
* Second, "repeatable read" also makes more isolation guarantees than the
* standard requires. According to the standard, this level prevents "dirty
* reads" and "nonrepeatable reads," but not "phantom reads." In postgres,
* it actually prevents all three.
*
* Third, "serializable" is only properly supported starting at postgres 9.1.
* If you request "serializable" isolation on an older backend, you will get
* the same isolation as in "repeatable read." It's better than the
* "repeatable read" defined in the SQL standard, but not a complete
* implementation of the standard's "serializable" isolation level.
*
* In general, a lower isolation level will allow more surprising interactions
* between ongoing transactions, but improve performance. A higher level
* gives you more protection from subtle concurrency bugs, but sometimes it
* may not be possible to complete your transaction without avoiding paradoxes
* in the data. In that case a transaction may fail, and the application will
* have to re-do the whole thing based on the latest state of the database.
* (If you want to retry your code in that situation, have a look at the
* transactor framework.)
*
* Study the levels and design your application with the right level in mind.
*/
enum isolation_level
{
// PostgreSQL only has the better isolation levels.
// read_uncommitted,
read_committed,
repeatable_read,
serializable,
};
} // namespace pqxx
#endif
@@ -0,0 +1,8 @@
/** Large Objects interface.
*
* Supports direct access to large objects, as well as through I/O streams
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/largeobject.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,735 @@
/* Large Objects interface. Deprecated; use blob instead.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/largeobject instead.
*
* 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.
*/
#ifndef PQXX_H_LARGEOBJECT
#define PQXX_H_LARGEOBJECT
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <streambuf>
#include "pqxx/dbtransaction.hxx"
namespace pqxx
{
/// Identity of a large object.
/** @deprecated Use the @ref blob class instead.
*
* Encapsulates the identity of a large object.
*
* A largeobject must be accessed only from within a backend transaction, but
* the object's identity remains valid as long as the object exists.
*/
class PQXX_LIBEXPORT largeobject
{
public:
using size_type = large_object_size_type;
/// Refer to a nonexistent large object (similar to what a null pointer
/// does).
[[deprecated("Use blob instead.")]] largeobject() noexcept = default;
/// Create new large object.
/** @param t Backend transaction in which the object is to be created.
*/
[[deprecated("Use blob instead.")]] explicit largeobject(dbtransaction &t);
/// Wrap object with given oid.
/** Convert combination of a transaction and object identifier into a
* large object identity. Does not affect the database.
* @param o Object identifier for the given object.
*/
[[deprecated("Use blob instead.")]] explicit largeobject(oid o) noexcept :
m_id{o}
{}
/// Import large object from a local file.
/** Creates a large object containing the data found in the given file.
* @param t Backend transaction in which the large object is to be created.
* @param file A filename on the client program's filesystem.
*/
[[deprecated("Use blob instead.")]] largeobject(
dbtransaction &t, std::string_view file);
/// Take identity of an opened large object.
/** Copy identity of already opened large object. Note that this may be done
* as an implicit conversion.
* @param o Already opened large object to copy identity from.
*/
[[deprecated("Use blob instead.")]] largeobject(
largeobjectaccess const &o) noexcept;
/// Object identifier.
/** The number returned by this function identifies the large object in the
* database we're connected to (or oid_none is returned if we refer to the
* null object).
*/
[[nodiscard]] oid id() const noexcept { return m_id; }
/**
* @name Identity comparisons
*
* These operators compare the object identifiers of large objects. This has
* nothing to do with the objects' actual contents; use them only for keeping
* track of containers of references to large objects and such.
*/
//@{
/// Compare object identities
/** @warning Only valid between large objects in the same database. */
[[nodiscard]] bool operator==(largeobject const &other) const
{
return m_id == other.m_id;
}
/// Compare object identities
/** @warning Only valid between large objects in the same database. */
[[nodiscard]] bool operator!=(largeobject const &other) const
{
return m_id != other.m_id;
}
/// Compare object identities
/** @warning Only valid between large objects in the same database. */
[[nodiscard]] bool operator<=(largeobject const &other) const
{
return m_id <= other.m_id;
}
/// Compare object identities
/** @warning Only valid between large objects in the same database. */
[[nodiscard]] bool operator>=(largeobject const &other) const
{
return m_id >= other.m_id;
}
/// Compare object identities
/** @warning Only valid between large objects in the same database. */
[[nodiscard]] bool operator<(largeobject const &other) const
{
return m_id < other.m_id;
}
/// Compare object identities
/** @warning Only valid between large objects in the same database. */
[[nodiscard]] bool operator>(largeobject const &other) const
{
return m_id > other.m_id;
}
//@}
/// Export large object's contents to a local file
/** Writes the data stored in the large object to the given file.
* @param t Transaction in which the object is to be accessed
* @param file A filename on the client's filesystem
*/
void to_file(dbtransaction &t, std::string_view file) const;
/// Delete large object from database
/** Unlike its low-level equivalent cunlink, this will throw an exception if
* deletion fails.
* @param t Transaction in which the object is to be deleted
*/
void remove(dbtransaction &t) const;
protected:
PQXX_PURE static internal::pq::PGconn *
raw_connection(dbtransaction const &T);
PQXX_PRIVATE std::string reason(connection const &, int err) const;
private:
oid m_id = oid_none;
};
/// Accessor for large object's contents.
/** @deprecated Use the `blob` class instead.
*/
class PQXX_LIBEXPORT largeobjectaccess : private largeobject
{
public:
using largeobject::size_type;
using off_type = size_type;
using pos_type = size_type;
/// Open mode: `in`, `out` (can be combined using "bitwise or").
/** According to the C++ standard, these should be in `std::ios_base`. We
* take them from derived class `std::ios` instead, which is easier on the
* eyes.
*
* Historical note: taking it from std::ios was originally a workaround for a
* problem with gcc 2.95.
*/
using openmode = std::ios::openmode;
/// Default open mode: in, out, binary.
static constexpr auto default_mode{
std::ios::in | std::ios::out | std::ios::binary};
/// Seek direction: `beg`, `cur`, `end`.
using seekdir = std::ios::seekdir;
/// Create new large object and open it.
/**
* @param t Backend transaction in which the object is to be created.
* @param mode Access mode, defaults to ios_base::in | ios_base::out |
* ios_base::binary.
*/
[[deprecated("Use blob instead.")]] explicit largeobjectaccess(
dbtransaction &t, openmode mode = default_mode);
/// Open large object with given oid.
/** Convert combination of a transaction and object identifier into a
* large object identity. Does not affect the database.
* @param t Transaction in which the object is to be accessed.
* @param o Object identifier for the given object.
* @param mode Access mode, defaults to ios_base::in | ios_base::out |
* ios_base::binary.
*/
[[deprecated("Use blob instead.")]] largeobjectaccess(
dbtransaction &t, oid o, openmode mode = default_mode);
/// Open given large object.
/** Open a large object with the given identity for reading and/or writing.
* @param t Transaction in which the object is to be accessed.
* @param o Identity for the large object to be accessed.
* @param mode Access mode, defaults to ios_base::in | ios_base::out |
* ios_base::binary.
*/
[[deprecated("Use blob instead.")]] largeobjectaccess(
dbtransaction &t, largeobject o, openmode mode = default_mode);
/// Import large object from a local file and open it.
/** Creates a large object containing the data found in the given file.
* @param t Backend transaction in which the large object is to be created.
* @param file A filename on the client program's filesystem.
* @param mode Access mode, defaults to ios_base::in | ios_base::out.
*/
[[deprecated("Use blob instead.")]] largeobjectaccess(
dbtransaction &t, std::string_view file, openmode mode = default_mode);
~largeobjectaccess() noexcept { close(); }
/// Object identifier.
/** The number returned by this function uniquely identifies the large object
* in the context of the database we're connected to.
*/
using largeobject::id;
/// Export large object's contents to a local file.
/** Writes the data stored in the large object to the given file.
* @param file A filename on the client's filesystem.
*/
void to_file(std::string_view file) const
{
largeobject::to_file(m_trans, file);
}
using largeobject::to_file;
/**
* @name High-level access to object contents.
*/
//@{
/// Write data to large object.
/** @warning The size of a write is currently limited to 2GB.
*
* @param buf Data to write.
* @param len Number of bytes from Buf to write.
*/
void write(char const buf[], std::size_t len);
/// Write string to large object.
/** If not all bytes could be written, an exception is thrown.
* @param buf Data to write; no terminating zero is written.
*/
void write(std::string_view buf) { write(std::data(buf), std::size(buf)); }
/// Read data from large object.
/** Throws an exception if an error occurs while reading.
* @param buf Location to store the read data in.
* @param len Number of bytes to try and read.
* @return Number of bytes read, which may be less than the number requested
* if the end of the large object is reached.
*/
size_type read(char buf[], std::size_t len);
/// Seek in large object's data stream.
/** Throws an exception if an error occurs.
* @return The new position in the large object
*/
size_type seek(size_type dest, seekdir dir);
/// Report current position in large object's data stream.
/** Throws an exception if an error occurs.
* @return The current position in the large object.
*/
[[nodiscard]] size_type tell() const;
//@}
/**
* @name Low-level access to object contents.
*
* These functions provide a more "C-like" access interface, returning
* special values instead of throwing exceptions on error. These functions
* are generally best avoided in favour of the high-level access functions,
* which behave more like C++ functions should.
*
* Due to libpq's underlying API, some operations are limited to "int"
* sizes, typically 2 GB, even though a large object can grow much larger.
*/
//@{
/// Seek in large object's data stream.
/** Does not throw exception in case of error; inspect return value and
* `errno` instead.
* @param dest Offset to go to.
* @param dir Origin to which dest is relative: ios_base::beg (from beginning
* of the object), ios_base::cur (from current access position), or
* ios_base;:end (from end of object).
* @return New position in large object, or -1 if an error occurred.
*/
pos_type cseek(off_type dest, seekdir dir) noexcept;
/// Write to large object's data stream.
/** Does not throw exception in case of error; inspect return value and
* `errno` instead.
* @param buf Data to write.
* @param len Number of bytes to write.
* @return Number of bytes actually written, or -1 if an error occurred.
*/
off_type cwrite(char const buf[], std::size_t len) noexcept;
/// Read from large object's data stream.
/** Does not throw exception in case of error; inspect return value and
* `errno` instead.
* @param buf Area where incoming bytes should be stored.
* @param len Number of bytes to read.
* @return Number of bytes actually read, or -1 if an error occurred..
*/
off_type cread(char buf[], std::size_t len) noexcept;
/// Report current position in large object's data stream.
/** Does not throw exception in case of error; inspect return value and
* `errno` instead.
* @return Current position in large object, of -1 if an error occurred.
*/
[[nodiscard]] pos_type ctell() const noexcept;
//@}
/**
* @name Error/warning output
*/
//@{
/// Issue message to transaction's notice processor.
void process_notice(zview) noexcept;
//@}
using largeobject::remove;
using largeobject::operator==;
using largeobject::operator!=;
using largeobject::operator<;
using largeobject::operator<=;
using largeobject::operator>;
using largeobject::operator>=;
largeobjectaccess() = delete;
largeobjectaccess(largeobjectaccess const &) = delete;
largeobjectaccess operator=(largeobjectaccess const &) = delete;
private:
PQXX_PRIVATE std::string reason(int err) const;
internal::pq::PGconn *raw_connection() const
{
return largeobject::raw_connection(m_trans);
}
PQXX_PRIVATE void open(openmode mode);
void close() noexcept;
dbtransaction &m_trans;
int m_fd = -1;
};
/// Streambuf to use large objects in standard I/O streams.
/** @deprecated Access large objects directly using the @ref blob class.
*
* The standard streambuf classes provide uniform access to data storage such
* as files or string buffers, so they can be accessed using standard input or
* output streams. This streambuf implementation provided similar access to
* large objects, so they could be read and written using the same stream
* classes.
*
* This functionality was considered too fragile and complex, so it has been
* replaced with a single, much simpler class.
*/
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
class largeobject_streambuf : public std::basic_streambuf<CHAR, TRAITS>
{
using size_type = largeobject::size_type;
public:
using char_type = CHAR;
using traits_type = TRAITS;
using int_type = typename traits_type::int_type;
using pos_type = typename traits_type::pos_type;
using off_type = typename traits_type::off_type;
using openmode = largeobjectaccess::openmode;
using seekdir = largeobjectaccess::seekdir;
/// Default open mode: in, out, binary.
static constexpr auto default_mode{
std::ios::in | std::ios::out | std::ios::binary};
#include "pqxx/internal/ignore-deprecated-pre.hxx"
[[deprecated("Use blob instead.")]] largeobject_streambuf(
dbtransaction &t, largeobject o, openmode mode = default_mode,
size_type buf_size = 512) :
m_bufsize{buf_size}, m_obj{t, o, mode}, m_g{nullptr}, m_p{nullptr}
{
initialize(mode);
}
#include "pqxx/internal/ignore-deprecated-post.hxx"
[[deprecated("Use blob instead.")]] largeobject_streambuf(
dbtransaction &t, oid o, openmode mode = default_mode,
size_type buf_size = 512) :
m_bufsize{buf_size}, m_obj{t, o, mode}, m_g{nullptr}, m_p{nullptr}
{
initialize(mode);
}
virtual ~largeobject_streambuf() noexcept
{
delete[] m_p;
delete[] m_g;
}
/// For use by large object stream classes.
void process_notice(zview const &s) { m_obj.process_notice(s); }
protected:
virtual int sync() override
{
// setg() sets eback, gptr, egptr.
this->setg(this->eback(), this->eback(), this->egptr());
return overflow(eof());
}
virtual pos_type seekoff(off_type offset, seekdir dir, openmode) override
{
return adjust_eof(m_obj.cseek(largeobjectaccess::off_type(offset), dir));
}
virtual pos_type seekpos(pos_type pos, openmode) override
{
largeobjectaccess::pos_type const newpos{
m_obj.cseek(largeobjectaccess::off_type(pos), std::ios::beg)};
return adjust_eof(newpos);
}
virtual int_type overflow(int_type ch) override
{
auto *const pp{this->pptr()};
if (pp == nullptr)
return eof();
auto *const pb{this->pbase()};
int_type res{0};
if (pp > pb)
{
auto const write_sz{pp - pb};
auto const written_sz{
m_obj.cwrite(pb, static_cast<std::size_t>(pp - pb))};
if (internal::cmp_less_equal(written_sz, 0))
throw internal_error{
"pqxx::largeobject: write failed "
"(is transaction still valid on write or flush?), "
"libpq reports error"};
else if (write_sz != written_sz)
throw internal_error{
"pqxx::largeobject: write failed "
"(is transaction still valid on write or flush?), " +
std::to_string(written_sz) + "/" + std::to_string(write_sz) +
" bytes written"};
auto const out{adjust_eof(written_sz)};
if constexpr (std::is_arithmetic_v<decltype(out)>)
res = check_cast<int_type>(out, "largeobject position"sv);
else
res = int_type(out);
}
this->setp(m_p, m_p + m_bufsize);
// Write that one more character, if it's there.
if (ch != eof())
{
*this->pptr() = static_cast<char_type>(ch);
this->pbump(1);
}
return res;
}
virtual int_type overflow() { return overflow(eof()); }
virtual int_type underflow() override
{
if (this->gptr() == nullptr)
return eof();
auto *const eb{this->eback()};
auto const res{adjust_eof(
m_obj.cread(this->eback(), static_cast<std::size_t>(m_bufsize)))};
this->setg(
eb, eb, eb + (res == eof() ? 0 : static_cast<std::size_t>(res)));
return (res == eof() or res == 0) ? eof() : traits_type::to_int_type(*eb);
}
private:
/// Shortcut for traits_type::eof().
static int_type eof() { return traits_type::eof(); }
/// Helper: change error position of -1 to EOF (probably a no-op).
template<typename INTYPE> static std::streampos adjust_eof(INTYPE pos)
{
bool const at_eof{pos == -1};
if constexpr (std::is_arithmetic_v<std::streampos>)
{
return check_cast<std::streampos>(
(at_eof ? eof() : pos), "large object seek"sv);
}
else
{
return std::streampos(at_eof ? eof() : pos);
}
}
void initialize(openmode mode)
{
if ((mode & std::ios::in) != 0)
{
m_g = new char_type[unsigned(m_bufsize)];
this->setg(m_g, m_g, m_g);
}
if ((mode & std::ios::out) != 0)
{
m_p = new char_type[unsigned(m_bufsize)];
this->setp(m_p, m_p + m_bufsize);
}
}
size_type const m_bufsize;
largeobjectaccess m_obj;
/// Get & put buffers.
char_type *m_g, *m_p;
};
/// Input stream that gets its data from a large object.
/** @deprecated Access large objects directly using the @ref blob class.
*
* This class worked like any other istream, but to read data from a large
* object. It supported all formatting and streaming operations of
* `std::istream`.
*
* This functionality was considered too fragile and complex, so it has been
* replaced with a single, much simpler class.
*/
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
class basic_ilostream : public std::basic_istream<CHAR, TRAITS>
{
using super = std::basic_istream<CHAR, TRAITS>;
public:
using char_type = CHAR;
using traits_type = TRAITS;
using int_type = typename traits_type::int_type;
using pos_type = typename traits_type::pos_type;
using off_type = typename traits_type::off_type;
#include "pqxx/internal/ignore-deprecated-pre.hxx"
/// Create a basic_ilostream.
/**
* @param t Transaction in which this stream is to exist.
* @param o Large object to access.
* @param buf_size Size of buffer to use internally (optional).
*/
[[deprecated("Use blob instead.")]] basic_ilostream(
dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) :
super{nullptr},
m_buf{t, o, std::ios::in | std::ios::binary, buf_size}
{
super::init(&m_buf);
}
#include "pqxx/internal/ignore-deprecated-post.hxx"
/// Create a basic_ilostream.
/**
* @param t Transaction in which this stream is to exist.
* @param o Identifier of a large object to access.
* @param buf_size Size of buffer to use internally (optional).
*/
[[deprecated("Use blob instead.")]] basic_ilostream(
dbtransaction &t, oid o, largeobject::size_type buf_size = 512) :
super{nullptr},
m_buf{t, o, std::ios::in | std::ios::binary, buf_size}
{
super::init(&m_buf);
}
private:
largeobject_streambuf<CHAR, TRAITS> m_buf;
};
using ilostream = basic_ilostream<char>;
/// Output stream that writes data back to a large object.
/** @deprecated Access large objects directly using the @ref blob class.
*
* This worked like any other ostream, but to write data to a large object.
* It supported all formatting and streaming operations of `std::ostream`.
*
* This functionality was considered too fragile and complex, so it has been
* replaced with a single, much simpler class.
*/
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
class basic_olostream : public std::basic_ostream<CHAR, TRAITS>
{
using super = std::basic_ostream<CHAR, TRAITS>;
public:
using char_type = CHAR;
using traits_type = TRAITS;
using int_type = typename traits_type::int_type;
using pos_type = typename traits_type::pos_type;
using off_type = typename traits_type::off_type;
#include "pqxx/internal/ignore-deprecated-pre.hxx"
/// Create a basic_olostream.
/**
* @param t transaction in which this stream is to exist.
* @param o a large object to access.
* @param buf_size size of buffer to use internally (optional).
*/
[[deprecated("Use blob instead.")]] basic_olostream(
dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) :
super{nullptr},
m_buf{t, o, std::ios::out | std::ios::binary, buf_size}
{
super::init(&m_buf);
}
#include "pqxx/internal/ignore-deprecated-post.hxx"
/// Create a basic_olostream.
/**
* @param t transaction in which this stream is to exist.
* @param o a large object to access.
* @param buf_size size of buffer to use internally (optional).
*/
[[deprecated("Use blob instead.")]] basic_olostream(
dbtransaction &t, oid o, largeobject::size_type buf_size = 512) :
super{nullptr},
m_buf{t, o, std::ios::out | std::ios::binary, buf_size}
{
super::init(&m_buf);
}
~basic_olostream()
{
try
{
m_buf.pubsync();
m_buf.pubsync();
}
catch (std::exception const &e)
{
m_buf.process_notice(e.what());
}
}
private:
largeobject_streambuf<CHAR, TRAITS> m_buf;
};
using olostream = basic_olostream<char>;
/// Stream that reads and writes a large object.
/** @deprecated Access large objects directly using the @ref blob class.
*
* This worked like a std::iostream, but to read data from, or write data to, a
* large object. It supported all formatting and streaming operations of
* `std::iostream`.
*
* This functionality was considered too fragile and complex, so it has been
* replaced with a single, much simpler class.
*/
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
class basic_lostream : public std::basic_iostream<CHAR, TRAITS>
{
using super = std::basic_iostream<CHAR, TRAITS>;
public:
using char_type = CHAR;
using traits_type = TRAITS;
using int_type = typename traits_type::int_type;
using pos_type = typename traits_type::pos_type;
using off_type = typename traits_type::off_type;
/// Create a basic_lostream.
/**
* @param t Transaction in which this stream is to exist.
* @param o Large object to access.
* @param buf_size Size of buffer to use internally (optional).
*/
[[deprecated("Use blob instead.")]] basic_lostream(
dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) :
super{nullptr},
m_buf{
t, o, std::ios::in | std::ios::out | std::ios::binary, buf_size}
{
super::init(&m_buf);
}
/// Create a basic_lostream.
/**
* @param t Transaction in which this stream is to exist.
* @param o Large object to access.
* @param buf_size Size of buffer to use internally (optional).
*/
[[deprecated("Use blob instead.")]] basic_lostream(
dbtransaction &t, oid o, largeobject::size_type buf_size = 512) :
super{nullptr},
m_buf{
t, o, std::ios::in | std::ios::out | std::ios::binary, buf_size}
{
super::init(&m_buf);
}
~basic_lostream()
{
try
{
m_buf.pubsync();
m_buf.pubsync();
}
catch (std::exception const &e)
{
m_buf.process_notice(e.what());
}
}
private:
largeobject_streambuf<CHAR, TRAITS> m_buf;
};
using lostream = basic_lostream<char>;
} // namespace pqxx
#endif
@@ -0,0 +1,8 @@
/** pqxx::nontransaction class.
*
* pqxx::nontransaction provides nontransactional database access.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/nontransaction.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,76 @@
/* Definition of the pqxx::nontransaction class.
*
* pqxx::nontransaction provides nontransactional database access
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/nontransaction instead.
*
* 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.
*/
#ifndef PQXX_H_NONTRANSACTION
#define PQXX_H_NONTRANSACTION
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include "pqxx/connection.hxx"
#include "pqxx/result.hxx"
#include "pqxx/transaction.hxx"
namespace pqxx
{
using namespace std::literals;
/// Simple "transaction" class offering no transactional integrity.
/**
* @ingroup transactions
*
* nontransaction, like transaction or any other transaction_base-derived
* class, provides access to a database through a connection. Unlike its
* siblings, however, nontransaction does not maintain any kind of
* transactional integrity. This may be useful eg. for read-only access to the
* database that does not require a consistent, atomic view on its data; or for
* operations that are not allowed within a backend transaction, such as
* creating tables.
*
* For queries that update the database, however, a real transaction is likely
* to be faster unless the transaction consists of only a single record update.
*
* Also, you can keep a nontransaction open for as long as you like. Actual
* back-end transactions are limited in lifespan, and will sometimes fail just
* because they took too long to execute or were left idle for too long. This
* will not happen with a nontransaction (although the connection may still
* time out, e.g. when the network is unavailable for a very long time).
*
* Any query executed in a nontransaction is committed immediately, and neither
* commit() nor abort() has any effect.
*
* Database features that require a backend transaction, such as cursors or
* large objects, will not work in a nontransaction.
*/
class PQXX_LIBEXPORT nontransaction final : public transaction_base
{
public:
/// Constructor.
/** Create a "dummy" transaction.
* @param c Connection in which this "transaction" will operate.
* @param tname Optional tname for the transaction, beginning with a letter
* and containing only letters and digits.
*/
nontransaction(connection &c, std::string_view tname = ""sv) :
transaction_base{c, tname, std::shared_ptr<std::string>{}}
{
register_transaction();
}
virtual ~nontransaction() override { close(); }
private:
virtual void do_commit() override {}
};
} // namespace pqxx
#endif
@@ -0,0 +1,8 @@
/** pqxx::notification_receiver functor interface.
*
* pqxx::notification_receiver handles incoming notifications.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/notification.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,94 @@
/* Definition of the pqxx::notification_receiver functor interface.
*
* pqxx::notification_receiver handles incoming notifications.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/notification instead.
*
* 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.
*/
#ifndef PQXX_H_NOTIFICATION
#define PQXX_H_NOTIFICATION
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <string>
#include "pqxx/types.hxx"
namespace pqxx
{
/// "Observer" base class for notifications.
/** @addtogroup notification Notifications and Receivers
*
* To listen on a notification issued using the NOTIFY command, derive your own
* class from notification_receiver and define its function-call operator to
* perform whatever action you wish to take when the given notification
* arrives. Then create an object of that class and pass it to your connection.
* DO NOT use raw SQL to listen for notifications, or your attempts to listen
* won't be resumed when a connection fails--and you'll have no way to notice.
*
* Notifications never arrive inside a transaction, not even in a
* nontransaction. Therefore, you are free to open a transaction of your own
* inside your receiver's function invocation operator.
*
* Notifications you are listening for may arrive anywhere within libpqxx code,
* but be aware that **PostgreSQL defers notifications occurring inside
* transactions.** (This was done for excellent reasons; just think about what
* happens if the transaction where you happen to handle an incoming
* notification is later rolled back for other reasons). So if you're keeping
* a transaction open, don't expect any of your receivers on the same
* connection to be notified.
*
* (For very similar reasons, outgoing notifications are also not sent until
* the transaction that sends them commits.)
*
* Multiple receivers on the same connection may listen on a notification of
* the same name. An incoming notification is processed by invoking all
* receivers (zero or more) of the same name.
*/
class PQXX_LIBEXPORT PQXX_NOVTABLE notification_receiver
{
public:
/// Register the receiver with a connection.
/**
* @param c Connnection to operate on.
* @param channel Name of the notification to listen for.
*/
notification_receiver(connection &c, std::string_view channel);
/// Register the receiver with a connection.
notification_receiver(notification_receiver const &) = delete;
/// Register the receiver with a connection.
notification_receiver &operator=(notification_receiver const &) = delete;
/// Deregister the receiver.
virtual ~notification_receiver();
/// The channel that this receiver listens on.
[[nodiscard]] std::string const &channel() const & { return m_channel; }
// TODO: Change API to take payload as zview instead of string ref.
/// Overridable: action to invoke when notification arrives.
/**
* @param payload An optional string that may have been passed to the NOTIFY
* command.
* @param backend_pid Process ID of the database backend process that served
* our connection when the notification arrived. The actual process ID
* behind the connection may have changed by the time this method is called.
*/
virtual void operator()(std::string const &payload, int backend_pid) = 0;
protected:
connection &conn() const noexcept { return m_conn; }
private:
connection &m_conn;
std::string m_channel;
};
} // namespace pqxx
#endif
+8
View File
@@ -0,0 +1,8 @@
/** Helper classes for passing statement parameters.
*
* Use these for prepared statements and parameterised statements.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/params.hxx"
#include "pqxx/internal/header-post.hxx"
+383
View File
@@ -0,0 +1,383 @@
/* Helpers for prepared statements and parameterised statements.
*
* See the connection class for more about such 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.
*/
#ifndef PQXX_H_PARAMS
#define PQXX_H_PARAMS
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <array>
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/statement_parameters.hxx"
#include "pqxx/types.hxx"
/// @deprecated The new @ref params class replaces all of this.
namespace pqxx::prepare
{
/// Pass a number of statement parameters only known at runtime.
/** @deprecated Use @ref params instead.
*
* When you call any of the `exec_params` functions, the number of arguments
* is normally known at compile time. This helper function supports the case
* where it is not.
*
* Use this function to pass a variable number of parameters, based on a
* sequence ranging from `begin` to `end` exclusively.
*
* The technique combines with the regular static parameters. You can use it
* to insert dynamic parameter lists in any place, or places, among the call's
* parameters. You can even insert multiple dynamic sequences.
*
* @param begin A pointer or iterator for iterating parameters.
* @param end A pointer or iterator for iterating parameters.
* @return An object representing the parameters.
*/
template<typename IT>
[[deprecated("Use the params class instead.")]] constexpr inline auto
make_dynamic_params(IT begin, IT end)
{
return pqxx::internal::dynamic_params(begin, end);
}
/// Pass a number of statement parameters only known at runtime.
/** @deprecated Use @ref params instead.
*
* When you call any of the `exec_params` functions, the number of arguments
* is normally known at compile time. This helper function supports the case
* where it is not.
*
* Use this function to pass a variable number of parameters, based on a
* container of parameter values.
*
* The technique combines with the regular static parameters. You can use it
* to insert dynamic parameter lists in any place, or places, among the call's
* parameters. You can even insert multiple dynamic containers.
*
* @param container A container of parameter values.
* @return An object representing the parameters.
*/
template<typename C>
[[deprecated("Use the params class instead.")]] constexpr inline auto
make_dynamic_params(C const &container)
{
using IT = typename C::const_iterator;
#include "pqxx/internal/ignore-deprecated-pre.hxx"
return pqxx::internal::dynamic_params<IT>{container};
#include "pqxx/internal/ignore-deprecated-post.hxx"
}
/// Pass a number of statement parameters only known at runtime.
/** @deprecated Use @ref params instead.
*
* When you call any of the `exec_params` functions, the number of arguments
* is normally known at compile time. This helper function supports the case
* where it is not.
*
* Use this function to pass a variable number of parameters, based on a
* container of parameter values.
*
* The technique combines with the regular static parameters. You can use it
* to insert dynamic parameter lists in any place, or places, among the call's
* parameters. You can even insert multiple dynamic containers.
*
* @param container A container of parameter values.
* @param accessor For each parameter `p`, pass `accessor(p)`.
* @return An object representing the parameters.
*/
template<typename C, typename ACCESSOR>
[[deprecated("Use the params class instead.")]] constexpr inline auto
make_dynamic_params(C &container, ACCESSOR accessor)
{
using IT = decltype(std::begin(container));
#include "pqxx/internal/ignore-deprecated-pre.hxx"
return pqxx::internal::dynamic_params<IT, ACCESSOR>{container, accessor};
#include "pqxx/internal/ignore-deprecated-post.hxx"
}
} // namespace pqxx::prepare
namespace pqxx
{
/// Generate parameter placeholders for use in an SQL statement.
/** When you want to pass parameters to a prepared statement or a parameterised
* statement, you insert placeholders into the SQL. During invocation, the
* database replaces those with the respective parameter values you passed.
*
* The placeholders look like `$1` (for the first parameter value), `$2` (for
* the second), and so on. You can just write those directly in your
* statement. But for those rare cases where it becomes difficult to track
* which number a placeholder should have, you can use a `placeholders` object
* to count and generate them in order.
*/
template<typename COUNTER = unsigned int> class placeholders
{
public:
/// Maximum number of parameters we support.
static inline constexpr unsigned int max_params{
(std::numeric_limits<COUNTER>::max)()};
placeholders()
{
static constexpr auto initial{"$1\0"sv};
initial.copy(std::data(m_buf), std::size(initial));
}
/// Read an ephemeral version of the current placeholder text.
/** @warning Changing the current placeholder number will overwrite this.
* Use the view immediately, or lose it.
*/
constexpr zview view() const &noexcept
{
return zview{std::data(m_buf), m_len};
}
/// Read the current placeholder text, as a `std::string`.
/** This will be slightly slower than converting to a `zview`. With most
* C++ implementations however, until you get into ridiculous numbers of
* parameters, the string will benefit from the Short String Optimization, or
* SSO.
*/
std::string get() const { return std::string(std::data(m_buf), m_len); }
/// Move on to the next parameter.
void next() &
{
if (m_current >= max_params)
throw range_error{pqxx::internal::concat(
"Too many parameters in one statement: limit is ", max_params, ".")};
++m_current;
if (m_current % 10 == 0)
{
// Carry the 1. Don't get too clever for this relatively rare
// case, just rewrite the entire number. Leave the $ in place
// though.
char *const data{std::data(m_buf)};
char *const end{string_traits<COUNTER>::into_buf(
data + 1, data + std::size(m_buf), m_current)};
// (Subtract because we don't include the trailing zero.)
m_len = check_cast<COUNTER>(end - data, "placeholders counter") - 1;
}
else
{
PQXX_LIKELY
// Shortcut for the common case: just increment that last digit.
++m_buf[m_len - 1];
}
}
/// Return the current placeholder number. The initial placeholder is 1.
COUNTER count() const noexcept { return m_current; }
private:
/// Current placeholder number. Starts at 1.
COUNTER m_current = 1;
/// Length of the current placeholder string, not including trailing zero.
COUNTER m_len = 2;
/// Text buffer where we render the placeholders, with a trailing zero.
/** We keep reusing this for every subsequent placeholder, just because we
* don't like string allocations.
*
* Maximum length is the maximum base-10 digits that COUNTER can fully
* represent, plus 1 more for the extra digit that it can only partially
* fill up, plus room for the dollar sign and the trailing zero.
*/
std::array<char, std::numeric_limits<COUNTER>::digits10 + 3> m_buf;
};
/// Build a parameter list for a parameterised or prepared statement.
/** When calling a parameterised statement or a prepared statement, you can
* pass parameters into the statement directly in the invocation, as
* additional arguments to `exec_prepared` or `exec_params`. But in
* complex cases, sometimes that's just not convenient.
*
* In those situations, you can create a `params` and append your parameters
* into that, one by one. Then you pass the `params` to `exec_prepared` or
* `exec_params`.
*
* Combinations also work: if you have a `params` containing a string
* parameter, and you call `exec_params` with an `int` argument followed by
* your `params`, you'll be passing the `int` as the first parameter and
* the string as the second. You can even insert a `params` in a `params`,
* or pass two `params` objects to a statement.
*/
class PQXX_LIBEXPORT params
{
public:
params() = default;
/// Pre-populate a `params` with `args`. Feel free to add more later.
template<typename... Args> constexpr params(Args &&...args)
{
reserve(sizeof...(args));
append_pack(std::forward<Args>(args)...);
}
/// Pre-allocate room for at least `n` parameters.
/** This is not needed, but it may improve efficiency.
*
* Reserve space if you're going to add parameters individually, and you've
* got some idea of how many there are going to be. It may save some
* memory re-allocations.
*/
void reserve(std::size_t n) &;
// C++20: constexpr.
/// Get the number of parameters currently in this `params`.
[[nodiscard]] auto size() const noexcept { return m_params.size(); }
// C++20: Use the vector's ssize() directly and go noexcept+constexpr.
/// Get the number of parameters (signed).
/** Unlike `size()`, this is not yet `noexcept`. That's because C++17's
* `std::vector` does not have a `ssize()` member function. These member
* functions are `noexcept`, but `std::size()` and `std::ssize()` are
* not.
*/
[[nodiscard]] auto ssize() const { return pqxx::internal::ssize(m_params); }
/// Append a null value.
void append() &;
/// Append a non-null zview parameter.
/** The underlying data must stay valid for as long as the `params`
* remains active.
*/
void append(zview) &;
/// Append a non-null string parameter.
/** Copies the underlying data into internal storage. For best efficiency,
* use the @ref zview variant if you can, or `std::move()`
*/
void append(std::string const &) &;
/// Append a non-null string parameter.
void append(std::string &&) &;
/// Append a non-null binary parameter.
/** The underlying data must stay valid for as long as the `params`
* remains active.
*/
void append(std::basic_string_view<std::byte>) &;
/// Append a non-null binary parameter.
/** Copies the underlying data into internal storage. For best efficiency,
* use the `std::basic_string_view<std::byte>` variant if you can, or
* `std::move()`.
*/
void append(std::basic_string<std::byte> const &) &;
#if defined(PQXX_HAVE_CONCEPTS)
/// Append a non-null binary parameter.
/** The `data` object must stay in place and unchanged, for as long as the
* `params` remains active.
*/
template<binary DATA> void append(DATA const &data) &
{
append(
std::basic_string_view<std::byte>{std::data(data), std::size(data)});
}
#endif // PQXX_HAVE_CONCEPTS
/// Append a non-null binary parameter.
void append(std::basic_string<std::byte> &&) &;
/// @deprecated Append binarystring parameter.
/** The binarystring must stay valid for as long as the `params` remains
* active.
*/
void append(binarystring const &value) &;
/// Append all parameters from value.
template<typename IT, typename ACCESSOR>
void append(pqxx::internal::dynamic_params<IT, ACCESSOR> const &value) &
{
for (auto &param : value) append(value.access(param));
}
void append(params const &value) &;
void append(params &&value) &;
/// Append a non-null parameter, converting it to its string
/// representation.
template<typename TYPE> void append(TYPE const &value) &
{
// TODO: Pool storage for multiple string conversions in one buffer?
if constexpr (nullness<strip_t<TYPE>>::always_null)
{
ignore_unused(value);
m_params.emplace_back();
}
else if (is_null(value))
{
m_params.emplace_back();
}
else
{
m_params.emplace_back(entry{to_string(value)});
}
}
/// Append all elements of `range` as parameters.
template<PQXX_RANGE_ARG RANGE> void append_multi(RANGE const &range) &
{
#if defined(PQXX_HAVE_CONCEPTS)
if constexpr (std::ranges::sized_range<RANGE>)
reserve(std::size(*this) + std::size(range));
#endif
for (auto &value : range) append(value);
}
/// For internal use: Generate a `params` object for use in calls.
/** The params object encapsulates the pointers which we will need to pass
* to libpq when calling a parameterised or prepared statement.
*
* The pointers in the params will refer to storage owned by either the
* params object, or the caller. This is not a problem because a
* `c_params` object is guaranteed to live only while the call is going on.
* As soon as we climb back out of that call tree, we're done with that
* data.
*/
pqxx::internal::c_params make_c_params() const;
private:
/// Recursively append a pack of params.
template<typename Arg, typename... More>
void append_pack(Arg &&arg, More &&...args)
{
this->append(std::forward<Arg>(arg));
// Recurse for remaining args.
append_pack(std::forward<More>(args)...);
}
/// Terminating case: append an empty parameter pack. It's not hard BTW.
constexpr void append_pack() noexcept {}
// The way we store a parameter depends on whether it's binary or text
// (most types are text), and whether we're responsible for storing the
// contents.
using entry = std::variant<
std::nullptr_t, zview, std::string, std::basic_string_view<std::byte>,
std::basic_string<std::byte>>;
std::vector<entry> m_params;
static constexpr std::string_view s_overflow{
"Statement parameter length overflow."sv};
};
} // namespace pqxx
#endif
+8
View File
@@ -0,0 +1,8 @@
/** pqxx::pipeline class.
*
* Throughput-optimized query interface.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/pipeline.hxx"
#include "pqxx/internal/header-post.hxx"
+237
View File
@@ -0,0 +1,237 @@
/* Definition of the pqxx::pipeline class.
*
* Throughput-optimized mechanism for executing queries.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/pipeline instead.
*
* 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.
*/
#ifndef PQXX_H_PIPELINE
#define PQXX_H_PIPELINE
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <limits>
#include <map>
#include <string>
#include "pqxx/transaction_base.hxx"
namespace pqxx
{
// TODO: libpq 14 introduced a similar "pipeline mode." Can we use that?
/// Processes several queries in FIFO manner, optimized for high throughput.
/** Use a pipeline if you want to keep doing useful work while your queries are
* executing. Result retrieval is decoupled from execution request; queries
* "go in at the front" and results "come out the back."
*
* Actually, you can retrieve the results in any order if you want, but it may
* lead to surprising "time travel" effects if any of the queries fails. In
* particular, syntax errors in the queries can confuse things and show up too
* early in the stream of results.
*
* Generally, if any of the queries fails, it will throw an exception at the
* point where you request its result. But it may happen earlier, especially
* if you request results out of chronological order.
*
* @warning While a pipeline is active, you cannot execute queries, open
* streams, etc. on the same transaction. A transaction can have at most one
* object of a type derived from @ref pqxx::transaction_focus active on it at a
* time.
*/
class PQXX_LIBEXPORT pipeline : public transaction_focus
{
public:
/// Identifying numbers for queries.
using query_id = long;
pipeline(pipeline const &) = delete;
pipeline &operator=(pipeline const &) = delete;
/// Start a pipeline.
explicit pipeline(transaction_base &t) : transaction_focus{t, s_classname}
{
init();
}
/// Start a pipeline. Assign it a name, for more helpful error messages.
pipeline(transaction_base &t, std::string_view tname) :
transaction_focus{t, s_classname, tname}
{
init();
}
/// Close the pipeline.
~pipeline() noexcept;
/// Add query to the pipeline.
/** Queries accumulate in the pipeline, which sends them to the backend in a
* batch separated by semicolons. The queries you insert must not use this
* trick themselves, or the pipeline will get hopelessly confused!
*
* @return Identifier for this query, unique only within this pipeline.
*/
query_id insert(std::string_view) &;
/// Wait for all ongoing or pending operations to complete, and detach.
/** Detaches from the transaction when done.
*
* This does not produce the queries' results, so it may not report any
* errors which may have occurred in their execution. To be sure that your
* statements succeeded, call @ref retrieve until the pipeline is empty.
*/
void complete();
/// Forget all ongoing or pending operations and retrieved results.
/** Queries already sent to the backend may still be completed, depending
* on implementation and timing.
*
* Any error state (unless caused by an internal error) will also be cleared.
* This is mostly useful in a nontransaction, since a backend transaction is
* aborted automatically when an error occurs.
*
* Detaches from the transaction when done.
*/
void flush();
/// Cancel ongoing query, if any.
/** May cancel any or all of the queries that have been inserted at this
* point whose results have not yet been retrieved. If the pipeline lives in
* a backend transaction, that transaction may be left in a nonfunctional
* state in which it can only be aborted.
*
* Therefore, either use this function in a nontransaction, or abort the
* transaction after calling it.
*/
void cancel();
/// Is result for given query available?
[[nodiscard]] bool is_finished(query_id) const;
/// Retrieve result for given query.
/** If the query failed for whatever reason, this will throw an exception.
* The function will block if the query has not finished yet.
* @warning If results are retrieved out-of-order, i.e. in a different order
* than the one in which their queries were inserted, errors may "propagate"
* to subsequent queries.
*/
result retrieve(query_id qid)
{
return retrieve(m_queries.find(qid)).second;
}
/// Retrieve oldest unretrieved result (possibly wait for one).
/** @return The query's identifier and its result set. */
std::pair<query_id, result> retrieve();
[[nodiscard]] bool empty() const noexcept { return std::empty(m_queries); }
/// Set maximum number of queries to retain before issuing them to the
/// backend.
/** The pipeline will perform better if multiple queries are issued at once,
* but retaining queries until the results are needed (as opposed to issuing
* them to the backend immediately) may negate any performance benefits the
* pipeline can offer.
*
* Recommended practice is to set this value no higher than the number of
* queries you intend to insert at a time.
* @param retain_max A nonnegative "retention capacity;" passing zero will
* cause queries to be issued immediately
* @return Old retention capacity
*/
int retain(int retain_max = 2) &;
/// Resume retained query emission. Harmless when not needed.
void resume() &;
private:
struct PQXX_PRIVATE Query
{
explicit Query(std::string_view q) :
query{std::make_shared<std::string>(q)}
{}
std::shared_ptr<std::string> query;
result res;
};
using QueryMap = std::map<query_id, Query>;
void init();
void attach();
void detach();
/// Upper bound to query id's.
static constexpr query_id qid_limit() noexcept
{
// Parenthesise this to work around an eternal Visual C++ problem:
// Without the extra parentheses, unless NOMINMAX is defined, the
// preprocessor will mistake this "max" for its annoying built-in macro
// of the same name.
return (std::numeric_limits<query_id>::max)();
}
/// Create new query_id.
PQXX_PRIVATE query_id generate_id();
bool have_pending() const noexcept
{
return m_issuedrange.second != m_issuedrange.first;
}
PQXX_PRIVATE void issue();
/// The given query failed; never issue anything beyond that.
void set_error_at(query_id qid) noexcept
{
PQXX_UNLIKELY
if (qid < m_error)
m_error = qid;
}
/// Throw pqxx::internal_error.
[[noreturn]] PQXX_PRIVATE void internal_error(std::string const &err);
PQXX_PRIVATE bool obtain_result(bool expect_none = false);
PQXX_PRIVATE void obtain_dummy();
PQXX_PRIVATE void get_further_available_results();
PQXX_PRIVATE void check_end_results();
/// Receive any results that happen to be available; it's not urgent.
PQXX_PRIVATE void receive_if_available();
/// Receive results, up to stop if possible.
PQXX_PRIVATE void receive(pipeline::QueryMap::const_iterator stop);
std::pair<pipeline::query_id, result> retrieve(pipeline::QueryMap::iterator);
QueryMap m_queries;
std::pair<QueryMap::iterator, QueryMap::iterator> m_issuedrange;
int m_retain = 0;
int m_num_waiting = 0;
query_id m_q_id = 0;
/// Is there a "dummy query" pending?
bool m_dummy_pending = false;
/// Point at which an error occurred; no results beyond it will be available
query_id m_error = qid_limit();
/// Encoding.
/** We store this in the object to avoid the risk of exceptions at awkward
* moments.
*/
internal::encoding_group m_encoding;
static constexpr std::string_view s_classname{"pipeline"};
};
} // namespace pqxx
#endif
+28
View File
@@ -0,0 +1,28 @@
/// Convenience header: include all libpqxx definitions.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/array.hxx"
#include "pqxx/binarystring.hxx"
#include "pqxx/blob.hxx"
#include "pqxx/connection.hxx"
#include "pqxx/cursor.hxx"
#include "pqxx/errorhandler.hxx"
#include "pqxx/except.hxx"
#include "pqxx/largeobject.hxx"
#include "pqxx/nontransaction.hxx"
#include "pqxx/notification.hxx"
#include "pqxx/params.hxx"
#include "pqxx/pipeline.hxx"
#include "pqxx/prepared_statement.hxx"
#include "pqxx/result.hxx"
#include "pqxx/internal/result_iterator.hxx"
#include "pqxx/internal/result_iter.hxx"
#include "pqxx/robusttransaction.hxx"
#include "pqxx/row.hxx"
#include "pqxx/stream_from.hxx"
#include "pqxx/stream_to.hxx"
#include "pqxx/subtransaction.hxx"
#include "pqxx/transaction.hxx"
#include "pqxx/transactor.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,3 @@
/// @deprecated Include @c <pqxx/params> instead.
#include "params.hxx"
@@ -0,0 +1,3 @@
/// @deprecated Include @c <pqxx/params> instead.
#include "params.hxx"
+6
View File
@@ -0,0 +1,6 @@
/** Client-side support for SQL range types.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/range.hxx"
#include "pqxx/internal/header-post.hxx"
+515
View File
@@ -0,0 +1,515 @@
#ifndef PQXX_H_RANGE
#define PQXX_H_RANGE
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <variant>
#include "pqxx/internal/array-composite.hxx"
#include "pqxx/internal/concat.hxx"
namespace pqxx
{
/// An _unlimited_ boundary value to a @ref pqxx::range.
/** Use this as a lower or upper bound for a range if the range should extend
* to infinity on that side.
*
* An unlimited boundary is always inclusive of "infinity" values, if the
* range's value type supports them.
*/
struct no_bound
{
template<typename TYPE> constexpr bool extends_down_to(TYPE const &) const
{
return true;
}
template<typename TYPE> constexpr bool extends_up_to(TYPE const &) const
{
return true;
}
};
/// An _inclusive_ boundary value to a @ref pqxx::range.
/** Use this as a lower or upper bound for a range if the range should include
* the value.
*/
template<typename TYPE> class inclusive_bound
{
public:
inclusive_bound() = delete;
explicit inclusive_bound(TYPE const &value) : m_value{value}
{
if (is_null(value))
throw argument_error{"Got null value as an inclusive range bound."};
}
[[nodiscard]] constexpr TYPE const &get() const &noexcept { return m_value; }
// TODO: constexpr and/or noexcept if underlying operator supports it.
/// Would this bound, as a lower bound, include value?
[[nodiscard]] bool extends_down_to(TYPE const &value) const
{
return not(value < m_value);
}
// TODO: constexpr and/or noexcept if underlying operator supports it.
/// Would this bound, as an upper bound, include value?
[[nodiscard]] bool extends_up_to(TYPE const &value) const
{
return not(m_value < value);
}
private:
TYPE m_value;
};
/// An _exclusive_ boundary value to a @ref pqxx::range.
/** Use this as a lower or upper bound for a range if the range should _not_
* include the value.
*/
template<typename TYPE> class exclusive_bound
{
public:
exclusive_bound() = delete;
explicit exclusive_bound(TYPE const &value) : m_value{value}
{
if (is_null(value))
throw argument_error{"Got null value as an exclusive range bound."};
}
[[nodiscard]] constexpr TYPE const &get() const &noexcept { return m_value; }
// TODO: constexpr and/or noexcept if underlying operator supports it.
/// Would this bound, as a lower bound, include value?
[[nodiscard]] bool extends_down_to(TYPE const &value) const
{
return m_value < value;
}
// TODO: constexpr and/or noexcept if underlying operator supports it.
/// Would this bound, as an upper bound, include value?
[[nodiscard]] bool extends_up_to(TYPE const &value) const
{
return value < m_value;
}
private:
TYPE m_value;
};
/// A range boundary value.
/** A range bound is either no bound at all; or an inclusive bound; or an
* exclusive bound. Pass one of the three to the constructor.
*/
template<typename TYPE> class range_bound
{
public:
range_bound() = delete;
// TODO: constexpr and/or noexcept if underlying constructor supports it.
range_bound(no_bound) : m_bound{} {}
// TODO: constexpr and/or noexcept if underlying constructor supports it.
range_bound(inclusive_bound<TYPE> const &bound) : m_bound{bound} {}
// TODO: constexpr and/or noexcept if underlying constructor supports it.
range_bound(exclusive_bound<TYPE> const &bound) : m_bound{bound} {}
// TODO: constexpr and/or noexcept if underlying constructor supports it.
range_bound(range_bound const &) = default;
// TODO: constexpr and/or noexcept if underlying constructor supports it.
range_bound(range_bound &&) = default;
// TODO: constexpr and/or noexcept if underlying operators support it.
bool operator==(range_bound const &rhs) const
{
if (this->is_limited())
return (
rhs.is_limited() and (this->is_inclusive() == rhs.is_inclusive()) and
(*this->value() == *rhs.value()));
else
return not rhs.is_limited();
}
// TODO: constexpr and/or noexcept if underlying operator supports it.
bool operator!=(range_bound const &rhs) const { return not(*this == rhs); }
range_bound &operator=(range_bound const &) = default;
range_bound &operator=(range_bound &&) = default;
/// Is this a finite bound?
constexpr bool is_limited() const noexcept
{
return not std::holds_alternative<no_bound>(m_bound);
}
/// Is this boundary an inclusive one?
constexpr bool is_inclusive() const noexcept
{
return std::holds_alternative<inclusive_bound<TYPE>>(m_bound);
}
/// Is this boundary an exclusive one?
constexpr bool is_exclusive() const noexcept
{
return std::holds_alternative<exclusive_bound<TYPE>>(m_bound);
}
// TODO: constexpr/noexcept if underlying function supports it.
/// Would this bound, as a lower bound, include `value`?
bool extends_down_to(TYPE const &value) const
{
return std::visit(
[&value](auto const &bound) { return bound.extends_down_to(value); },
m_bound);
}
// TODO: constexpr/noexcept if underlying function supports it.
/// Would this bound, as an upper bound, include `value`?
bool extends_up_to(TYPE const &value) const
{
return std::visit(
[&value](auto const &bound) { return bound.extends_up_to(value); },
m_bound);
}
/// Return bound value, or `nullptr` if it's not limited.
[[nodiscard]] constexpr TYPE const *value() const &noexcept
{
return std::visit(
[](auto const &bound) noexcept {
using bound_t = std::decay_t<decltype(bound)>;
if constexpr (std::is_same_v<bound_t, no_bound>)
return static_cast<TYPE const *>(nullptr);
else
return &bound.get();
},
m_bound);
}
private:
std::variant<no_bound, inclusive_bound<TYPE>, exclusive_bound<TYPE>> m_bound;
};
// C++20: Concepts for comparisons, construction, etc.
/// A C++ equivalent to PostgreSQL's range types.
/** You can use this as a client-side representation of a "range" in SQL.
*
* PostgreSQL defines several range types, differing in the data type over
* which they range. You can also define your own range types.
*
* Usually you'll want the server to deal with ranges. But on occasions where
* you need to work with them client-side, you may want to use @ref
* pqxx::range. (In cases where all you do is pass them along to the server
* though, it's not worth the complexity. In that case you might as well treat
* ranges as just strings.)
*
* For documentation on PostgreSQL's range types, see:
* https://www.postgresql.org/docs/current/rangetypes.html
*
* The value type must be copyable and default-constructible, and support the
* less-than (`<`) and equals (`==`) comparisons. Value initialisation must
* produce a consistent value.
*/
template<typename TYPE> class range
{
public:
/// Create a range.
/** For each of the two bounds, pass a @ref no_bound, @ref inclusive_bound,
* or
* @ref exclusive_bound.
*/
range(range_bound<TYPE> lower, range_bound<TYPE> upper) :
m_lower{lower}, m_upper{upper}
{
if (
lower.is_limited() and upper.is_limited() and
(*upper.value() < *lower.value()))
throw range_error{internal::concat(
"Range's lower bound (", *lower.value(),
") is greater than its upper bound (", *upper.value(), ").")};
}
// TODO: constexpr and/or noexcept if underlying constructor supports it.
/// Create an empty range.
/** SQL has a separate literal to denote an empty range, but any range which
* encompasses no values is an empty range.
*/
range() :
m_lower{exclusive_bound<TYPE>{TYPE{}}},
m_upper{exclusive_bound<TYPE>{TYPE{}}}
{}
// TODO: constexpr and/or noexcept if underlying operators support it.
bool operator==(range const &rhs) const
{
return (this->lower_bound() == rhs.lower_bound() and
this->upper_bound() == rhs.upper_bound()) or
(this->empty() and rhs.empty());
}
// TODO: constexpr and/or noexcept if underlying operator supports it.
bool operator!=(range const &rhs) const { return !(*this == rhs); }
range(range const &) = default;
range(range &&) = default;
range &operator=(range const &) = default;
range &operator=(range &&) = default;
// TODO: constexpr and/or noexcept if underlying operator supports it.
/// Is this range clearly empty?
/** An empty range encompasses no values.
*
* It is possible to "fool" this. For example, if your range is of an
* integer type and has exclusive bounds of 0 and 1, it encompasses no values
* but its `empty()` will return false. The PostgreSQL implementation, by
* contrast, will notice that it is empty. Similar things can happen for
* floating-point types, but with more subtleties and edge cases.
*/
bool empty() const
{
return (m_lower.is_exclusive() or m_upper.is_exclusive()) and
m_lower.is_limited() and m_upper.is_limited() and
not(*m_lower.value() < *m_upper.value());
}
// TODO: constexpr and/or noexcept if underlying functions support it.
/// Does this range encompass `value`?
bool contains(TYPE value) const
{
return m_lower.extends_down_to(value) and m_upper.extends_up_to(value);
}
// TODO: constexpr and/or noexcept if underlying operators support it.
/// Does this range encompass all of `other`?
/** This function is not particularly smart. It does not know, for example,
* that integer ranges `[0,9]` and `[0,10)` contain the same values.
*/
bool contains(range<TYPE> const &other) const
{
return (*this & other) == other;
}
[[nodiscard]] constexpr range_bound<TYPE> const &
lower_bound() const &noexcept
{
return m_lower;
}
[[nodiscard]] constexpr range_bound<TYPE> const &
upper_bound() const &noexcept
{
return m_upper;
}
// TODO: constexpr and/or noexcept if underlying operators support it.
/// Intersection of two ranges.
/** Returns a range describing those values which are in both ranges.
*/
range operator&(range const &other) const
{
range_bound<TYPE> lower{no_bound{}};
if (not this->lower_bound().is_limited())
lower = other.lower_bound();
else if (not other.lower_bound().is_limited())
lower = this->lower_bound();
else if (*this->lower_bound().value() < *other.lower_bound().value())
lower = other.lower_bound();
else if (*other.lower_bound().value() < *this->lower_bound().value())
lower = this->lower_bound();
else if (this->lower_bound().is_exclusive())
lower = this->lower_bound();
else
lower = other.lower_bound();
range_bound<TYPE> upper{no_bound{}};
if (not this->upper_bound().is_limited())
upper = other.upper_bound();
else if (not other.upper_bound().is_limited())
upper = this->upper_bound();
else if (*other.upper_bound().value() < *this->upper_bound().value())
upper = other.upper_bound();
else if (*this->upper_bound().value() < *other.upper_bound().value())
upper = this->upper_bound();
else if (this->upper_bound().is_exclusive())
upper = this->upper_bound();
else
upper = other.upper_bound();
if (
lower.is_limited() and upper.is_limited() and
(*upper.value() < *lower.value()))
return {};
else
return {lower, upper};
}
/// Convert to another base type.
template<typename DEST> operator range<DEST>() const
{
range_bound<DEST> lower{no_bound{}}, upper{no_bound{}};
if (lower_bound().is_inclusive())
lower = inclusive_bound<DEST>{*lower_bound().value()};
else if (lower_bound().is_exclusive())
lower = exclusive_bound<DEST>{*lower_bound().value()};
if (upper_bound().is_inclusive())
upper = inclusive_bound<DEST>{*upper_bound().value()};
else if (upper_bound().is_exclusive())
upper = exclusive_bound<DEST>{*upper_bound().value()};
return {lower, upper};
}
private:
range_bound<TYPE> m_lower, m_upper;
};
/// String conversions for a @ref range type.
/** Conversion assumes that either your client encoding is UTF-8, or the values
* are pure ASCII.
*/
template<typename TYPE> struct string_traits<range<TYPE>>
{
[[nodiscard]] static inline zview
to_buf(char *begin, char *end, range<TYPE> const &value)
{
return generic_to_buf(begin, end, value);
}
static inline char *
into_buf(char *begin, char *end, range<TYPE> const &value)
{
if (value.empty())
{
if ((end - begin) <= internal::ssize(s_empty))
throw conversion_overrun{s_overrun.c_str()};
char *here = begin + s_empty.copy(begin, std::size(s_empty));
*here++ = '\0';
return here;
}
else
{
if (end - begin < 4)
throw conversion_overrun{s_overrun.c_str()};
char *here = begin;
*here++ =
(static_cast<char>(value.lower_bound().is_inclusive() ? '[' : '('));
TYPE const *lower{value.lower_bound().value()};
// Convert bound (but go back to overwrite that trailing zero).
if (lower != nullptr)
here = string_traits<TYPE>::into_buf(here, end, *lower) - 1;
*here++ = ',';
TYPE const *upper{value.upper_bound().value()};
// Convert bound (but go back to overwrite that trailing zero).
if (upper != nullptr)
here = string_traits<TYPE>::into_buf(here, end, *upper) - 1;
if ((end - here) < 2)
throw conversion_overrun{s_overrun.c_str()};
*here++ =
static_cast<char>(value.upper_bound().is_inclusive() ? ']' : ')');
*here++ = '\0';
return here;
}
}
[[nodiscard]] static inline range<TYPE> from_string(std::string_view text)
{
if (std::size(text) < 3)
throw pqxx::conversion_error{err_bad_input(text)};
bool left_inc{false};
switch (text[0])
{
case '[': left_inc = true; break;
case '(': break;
case 'e':
case 'E':
if (
(std::size(text) != std::size(s_empty)) or
(text[1] != 'm' and text[1] != 'M') or
(text[2] != 'p' and text[2] != 'P') or
(text[3] != 't' and text[3] != 'T') or
(text[4] != 'y' and text[4] != 'Y'))
throw pqxx::conversion_error{err_bad_input(text)};
return {};
break;
default: throw pqxx::conversion_error{err_bad_input(text)};
}
auto scan{internal::get_glyph_scanner(internal::encoding_group::UTF8)};
// The field parser uses this to track which field it's parsing, and
// when not to expect a field separator.
std::size_t index{0};
// The last field we expect to see.
static constexpr std::size_t last{1};
// Current parsing position. We skip the opening parenthesis or bracket.
std::size_t pos{1};
// The string may leave out either bound to indicate that it's unlimited.
std::optional<TYPE> lower, upper;
// We reuse the same field parser we use for composite values and arrays.
internal::parse_composite_field(index, text, pos, lower, scan, last);
internal::parse_composite_field(index, text, pos, upper, scan, last);
// We need one more character: the closing parenthesis or bracket.
if (pos != std::size(text))
throw pqxx::conversion_error{err_bad_input(text)};
char const closing{text[pos - 1]};
if (closing != ')' and closing != ']')
throw pqxx::conversion_error{err_bad_input(text)};
bool const right_inc{closing == ']'};
range_bound<TYPE> lower_bound{no_bound{}}, upper_bound{no_bound{}};
if (lower)
{
if (left_inc)
lower_bound = inclusive_bound{*lower};
else
lower_bound = exclusive_bound{*lower};
}
if (upper)
{
if (right_inc)
upper_bound = inclusive_bound{*upper};
else
upper_bound = exclusive_bound{*upper};
}
return {lower_bound, upper_bound};
}
[[nodiscard]] static inline constexpr std::size_t
size_buffer(range<TYPE> const &value) noexcept
{
TYPE const *lower{value.lower_bound().value()},
*upper{value.upper_bound().value()};
std::size_t const lsz{
lower == nullptr ? 0 : string_traits<TYPE>::size_buffer(*lower) - 1},
usz{upper == nullptr ? 0 : string_traits<TYPE>::size_buffer(*upper) - 1};
if (value.empty())
return std::size(s_empty) + 1;
else
return 1 + lsz + 1 + usz + 2;
}
private:
static constexpr zview s_empty{"empty"_zv};
static constexpr auto s_overrun{"Not enough space in buffer for range."_zv};
/// Compose error message for invalid range input.
static std::string err_bad_input(std::string_view text)
{
return internal::concat("Invalid range input: '", text, "'");
}
};
/// A range type does not have an innate null value.
template<typename TYPE> struct nullness<range<TYPE>> : no_null<range<TYPE>>
{};
} // namespace pqxx
#endif
+16
View File
@@ -0,0 +1,16 @@
/** pqxx::result class and support classes.
*
* pqxx::result represents the set of result rows from a database query.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/result.hxx"
// Now include some types which depend on result, but which the user will
// expect to see defined after including this header.
#include "pqxx/internal/result_iterator.hxx"
#include "pqxx/field.hxx"
#include "pqxx/internal/result_iter.hxx"
#include "pqxx/internal/header-post.hxx"
+335
View File
@@ -0,0 +1,335 @@
/* Definitions for the pqxx::result class and support classes.
*
* pqxx::result represents the set of result rows from a database query.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result instead.
*
* 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.
*/
#ifndef PQXX_H_RESULT
#define PQXX_H_RESULT
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <functional>
#include <ios>
#include <memory>
#include <stdexcept>
#include "pqxx/except.hxx"
#include "pqxx/types.hxx"
#include "pqxx/util.hxx"
#include "pqxx/zview.hxx"
#include "pqxx/internal/encodings.hxx"
namespace pqxx::internal
{
// TODO: Make noexcept (but breaks ABI).
PQXX_LIBEXPORT void clear_result(pq::PGresult const *);
} // namespace pqxx::internal
namespace pqxx::internal::gate
{
class result_connection;
class result_creation;
class result_pipeline;
class result_row;
class result_sql_cursor;
} // namespace pqxx::internal::gate
namespace pqxx
{
/// Result set containing data returned by a query or command.
/** This behaves as a container (as defined by the C++ standard library) and
* provides random access const iterators to iterate over its rows. You can
* also access a row by indexing a `result R` by the row's zero-based
* number:
*
*
* for (result::size_type i=0; i < std::size(R); ++i) Process(R[i]);
*
*
* Result sets in libpqxx are lightweight, reference-counted wrapper objects
* which are relatively small and cheap to copy. Think of a result object as
* a "smart pointer" to an underlying result set.
*
* @warning The result set that a result object points to is not thread-safe.
* If you copy a result object, it still refers to the same underlying result
* set. So never copy, destroy, query, or otherwise access a result while
* another thread may be copying, destroying, querying, or otherwise accessing
* the same result set--even if it is doing so through a different result
* object!
*/
class PQXX_LIBEXPORT result
{
public:
using size_type = result_size_type;
using difference_type = result_difference_type;
using reference = row;
using const_iterator = const_result_iterator;
using pointer = const_iterator;
using iterator = const_iterator;
using const_reverse_iterator = const_reverse_result_iterator;
using reverse_iterator = const_reverse_iterator;
result() noexcept :
m_data{make_data_pointer()},
m_query{},
m_encoding{internal::encoding_group::MONOBYTE}
{}
result(result const &rhs) noexcept = default;
result(result &&rhs) noexcept = default;
/// Assign one result to another.
/** Copying results is cheap: it copies only smart pointers, but the actual
* data stays in the same place.
*/
result &operator=(result const &rhs) noexcept = default;
/// Assign one result to another, invaliding the old one.
result &operator=(result &&rhs) noexcept = default;
/**
* @name Comparisons
*
* You can compare results for equality. Beware: this is a very strict,
* dumb comparison. The smallest difference between two results (such as a
* string "Foo" versus a string "foo") will make them unequal.
*/
//@{
/// Compare two results for equality.
[[nodiscard]] bool operator==(result const &) const noexcept;
/// Compare two results for inequality.
[[nodiscard]] bool operator!=(result const &rhs) const noexcept
{
return not operator==(rhs);
}
//@}
/// Iterate rows, reading them directly into a tuple of "TYPE...".
/** Converts the fields to values of the given respective types.
*
* Use this only with a ranged "for" loop. The iteration produces
* std::tuple<TYPE...> which you can "unpack" to a series of `auto`
* variables.
*/
template<typename... TYPE> auto iter() const;
[[nodiscard]] const_reverse_iterator rbegin() const;
[[nodiscard]] const_reverse_iterator crbegin() const;
[[nodiscard]] const_reverse_iterator rend() const;
[[nodiscard]] const_reverse_iterator crend() const;
[[nodiscard]] const_iterator begin() const noexcept;
[[nodiscard]] const_iterator cbegin() const noexcept;
[[nodiscard]] inline const_iterator end() const noexcept;
[[nodiscard]] inline const_iterator cend() const noexcept;
[[nodiscard]] reference front() const noexcept;
[[nodiscard]] reference back() const noexcept;
[[nodiscard]] PQXX_PURE size_type size() const noexcept;
[[nodiscard]] PQXX_PURE bool empty() const noexcept;
[[nodiscard]] size_type capacity() const noexcept { return size(); }
/// Exchange two `result` values in an exception-safe manner.
/** If the swap fails, the two values will be exactly as they were before.
*
* The swap is not necessarily thread-safe.
*/
void swap(result &) noexcept;
/// Index a row by number.
/** This returns a @ref row object. Generally you should not keep the row
* around as a variable, but if you do, make sure that your variable is a
* `row`, not a `row&`.
*/
[[nodiscard]] row operator[](size_type i) const noexcept;
#if defined(PQXX_HAVE_MULTIDIMENSIONAL_SUBSCRIPT)
// TODO: If C++23 will let us, also accept string for the column.
[[nodiscard]] field
operator[](size_type row_num, row_size_type col_num) const noexcept;
#endif
/// Index a row by number, but check that the row number is valid.
row at(size_type) const;
/// Index a field by row number and column number.
field at(size_type, row_size_type) const;
/// Let go of the result's data.
/** Use this if you need to deallocate the result data earlier than you can
* destroy the `result` object itself.
*
* Multiple `result` objects can refer to the same set of underlying data.
* The underlying data will be deallocated once all `result` objects that
* refer to it are cleared or destroyed.
*/
void clear() noexcept
{
m_data.reset();
m_query = nullptr;
}
/**
* @name Column information
*/
//@{
/// Number of columns in result.
[[nodiscard]] PQXX_PURE row_size_type columns() const noexcept;
/// Number of given column (throws exception if it doesn't exist).
[[nodiscard]] row_size_type column_number(zview name) const;
/// Name of column with this number (throws exception if it doesn't exist)
[[nodiscard]] char const *column_name(row_size_type number) const &;
/// Return column's type, as an OID from the system catalogue.
[[nodiscard]] oid column_type(row_size_type col_num) const;
/// Return column's type, as an OID from the system catalogue.
[[nodiscard]] oid column_type(zview col_name) const
{
return column_type(column_number(col_name));
}
/// What table did this column come from?
[[nodiscard]] oid column_table(row_size_type col_num) const;
/// What table did this column come from?
[[nodiscard]] oid column_table(zview col_name) const
{
return column_table(column_number(col_name));
}
/// What column in its table did this column come from?
[[nodiscard]] row_size_type table_column(row_size_type col_num) const;
/// What column in its table did this column come from?
[[nodiscard]] row_size_type table_column(zview col_name) const
{
return table_column(column_number(col_name));
}
//@}
/// Query that produced this result, if available (empty string otherwise)
[[nodiscard]] PQXX_PURE std::string const &query() const &noexcept;
/// If command was an `INSERT` of 1 row, return oid of the inserted row.
/** @return Identifier of inserted row if exactly one row was inserted, or
* @ref oid_none otherwise.
*/
[[nodiscard]] PQXX_PURE oid inserted_oid() const;
/// If command was `INSERT`, `UPDATE`, or `DELETE`: number of affected rows.
/** @return Number of affected rows if last command was `INSERT`, `UPDATE`,
* or `DELETE`; zero for all other commands.
*/
[[nodiscard]] PQXX_PURE size_type affected_rows() const;
// C++20: Concept like std::invocable, but without specifying param types.
/// Run `func` on each row, passing the row's fields as parameters.
/** Goes through the rows from first to last. You provide a callable `func`.
*
* For each row in the `result`, `for_each` will call `func`. It converts
* the row's fields to the types of `func`'s parameters, and pass them to
* `func`.
*
* (Therefore `func` must have a _single_ signature. It can't be a generic
* lambda, or an object of a class with multiple overloaded function call
* operators. Otherwise, `for_each` will have no way to detect a parameter
* list without ambiguity.)
*
* If any of your parameter types is `std::string_view`, it refers to the
* underlying storage of this `result`.
*
* If any of your parameter types is a reference type, its argument will
* refer to a temporary value which only lives for the duration of that
* single invocation to `func`. If the reference is an lvalue reference, it
* must be `const`.
*
* For example, this queries employee names and salaries from the database
* and prints how much each would like to earn instead:
* ```cxx
* tx.exec("SELECT name, salary FROM employee").for_each(
* [](std::string_view name, float salary){
* std::cout << name << " would like " << salary * 2 << ".\n";
* })
* ```
*
* If `func` throws an exception, processing stops at that point and
* propagates the exception.
*
* @throws usage_error if `func`'s number of parameters does not match the
* number of columns in this result.
*/
template<typename CALLABLE> inline void for_each(CALLABLE &&func) const;
private:
using data_pointer = std::shared_ptr<internal::pq::PGresult const>;
/// Underlying libpq result set.
data_pointer m_data;
/// Factory for data_pointer.
static data_pointer
make_data_pointer(internal::pq::PGresult const *res = nullptr) noexcept
{
return {res, internal::clear_result};
}
friend class pqxx::internal::gate::result_pipeline;
PQXX_PURE std::shared_ptr<std::string const> query_ptr() const noexcept
{
return m_query;
}
/// Query string.
std::shared_ptr<std::string const> m_query;
internal::encoding_group m_encoding;
static std::string const s_empty_string;
friend class pqxx::field;
// TODO: noexcept. Breaks ABI.
PQXX_PURE char const *get_value(size_type row, row_size_type col) const;
// TODO: noexcept. Breaks ABI.
PQXX_PURE bool get_is_null(size_type row, row_size_type col) const;
PQXX_PURE
field_size_type get_length(size_type, row_size_type) const noexcept;
friend class pqxx::internal::gate::result_creation;
result(
internal::pq::PGresult *rhs, std::shared_ptr<std::string> query,
internal::encoding_group enc);
PQXX_PRIVATE void check_status(std::string_view desc = ""sv) const;
friend class pqxx::internal::gate::result_connection;
friend class pqxx::internal::gate::result_row;
bool operator!() const noexcept { return m_data.get() == nullptr; }
operator bool() const noexcept { return m_data.get() != nullptr; }
[[noreturn]] PQXX_PRIVATE void
throw_sql_error(std::string const &Err, std::string const &Query) const;
PQXX_PRIVATE PQXX_PURE int errorposition() const;
PQXX_PRIVATE std::string status_error() const;
friend class pqxx::internal::gate::result_sql_cursor;
PQXX_PURE char const *cmd_status() const noexcept;
};
} // namespace pqxx
#endif
@@ -0,0 +1,8 @@
/** pqxx::robusttransaction class.
*
* pqxx::robusttransaction is a slower but safer transaction class.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/robusttransaction.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,120 @@
/* Definition of the pqxx::robusttransaction class.
*
* pqxx::robusttransaction is a slower but safer transaction class.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/robusttransaction instead.
*
* 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.
*/
#ifndef PQXX_H_ROBUSTTRANSACTION
#define PQXX_H_ROBUSTTRANSACTION
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include "pqxx/dbtransaction.hxx"
namespace pqxx::internal
{
/// Helper base class for the @ref robusttransaction class template.
class PQXX_LIBEXPORT PQXX_NOVTABLE basic_robusttransaction
: public dbtransaction
{
public:
virtual ~basic_robusttransaction() override = 0;
protected:
basic_robusttransaction(
connection &c, zview begin_command, std::string_view tname);
basic_robusttransaction(connection &c, zview begin_command);
private:
using IDType = unsigned long;
std::string m_conn_string;
std::string m_xid;
int m_backendpid = -1;
void init(zview begin_command);
// @warning This function will become `final`.
virtual void do_commit() override;
};
} // namespace pqxx::internal
namespace pqxx
{
/**
* @ingroup transactions
*
* @{
*/
/// Slightly slower, better-fortified version of transaction.
/** Requires PostgreSQL 10 or better.
*
* robusttransaction is similar to transaction, but spends more time and effort
* to deal with the hopefully rare case that the connection to the backend is
* lost just while it's trying to commit. In such cases, the client does not
* know whether the backend (on the other side of the broken connection)
* managed to commit the transaction.
*
* When this happens, robusttransaction tries to reconnect to the database and
* figure out what happened.
*
* This service level was made optional since you may not want to pay the
* overhead where it is not necessary. Certainly the use of this class makes
* no sense for local connections, or for transactions that read the database
* but never modify it, or for noncritical database manipulations.
*
* Besides being slower, it's also more complex. Which means that in practice
* a robusttransaction could actually fail more instead of less often than a
* normal transaction. What robusttransaction tries to achieve is to give you
* certainty, not just be more successful per se.
*/
template<isolation_level ISOLATION = read_committed>
class robusttransaction final : public internal::basic_robusttransaction
{
public:
/** Create robusttransaction of given name.
* @param c Connection inside which this robusttransaction should live.
* @param tname optional human-readable name for this transaction.
*/
robusttransaction(connection &c, std::string_view tname) :
internal::basic_robusttransaction{
c, pqxx::internal::begin_cmd<ISOLATION, write_policy::read_write>,
tname}
{}
/** Create robusttransaction of given name.
* @param c Connection inside which this robusttransaction should live.
* @param tname optional human-readable name for this transaction.
*/
robusttransaction(connection &c, std::string &&tname) :
internal::basic_robusttransaction{
c, pqxx::internal::begin_cmd<ISOLATION, write_policy::read_write>,
std::move(tname)}
{}
/** Create robusttransaction of given name.
* @param c Connection inside which this robusttransaction should live.
*/
explicit robusttransaction(connection &c) :
internal::basic_robusttransaction{
c, pqxx::internal::begin_cmd<ISOLATION, write_policy::read_write>}
{}
virtual ~robusttransaction() noexcept override { close(); }
};
/**
* @}
*/
} // namespace pqxx
#endif
+11
View File
@@ -0,0 +1,11 @@
/** pqxx::row class.
*
* pqxx::row refers to a row in a result.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/result.hxx"
#include "pqxx/row.hxx"
#include "pqxx/internal/header-post.hxx"
+561
View File
@@ -0,0 +1,561 @@
/* Definitions for the pqxx::result class and support classes.
*
* pqxx::result represents the set of result rows from a database query.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result instead.
*
* 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.
*/
#ifndef PQXX_H_ROW
#define PQXX_H_ROW
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include "pqxx/except.hxx"
#include "pqxx/field.hxx"
#include "pqxx/result.hxx"
#include "pqxx/internal/concat.hxx"
namespace pqxx::internal
{
template<typename... T> class result_iter;
} // namespace pqxx::internal
namespace pqxx
{
/// Reference to one row in a result.
/** A row represents one row (also called a row) in a query result set.
* It also acts as a container mapping column numbers or names to field
* values (see below):
*
* ```cxx
* cout << row["date"].c_str() << ": " << row["name"].c_str() << endl;
* ```
*
* The row itself acts like a (non-modifyable) container, complete with its
* own const_iterator and const_reverse_iterator.
*/
class PQXX_LIBEXPORT row
{
public:
using size_type = row_size_type;
using difference_type = row_difference_type;
using const_iterator = const_row_iterator;
using iterator = const_iterator;
using reference = field;
using pointer = const_row_iterator;
using const_reverse_iterator = const_reverse_row_iterator;
using reverse_iterator = const_reverse_iterator;
row() noexcept = default;
row(row &&) noexcept = default;
row(row const &) noexcept = default;
row &operator=(row const &) noexcept = default;
row &operator=(row &&) noexcept = default;
/**
* @name Comparison
*/
//@{
[[nodiscard]] PQXX_PURE bool operator==(row const &) const noexcept;
[[nodiscard]] bool operator!=(row const &rhs) const noexcept
{
return not operator==(rhs);
}
//@}
[[nodiscard]] const_iterator begin() const noexcept;
[[nodiscard]] const_iterator cbegin() const noexcept;
[[nodiscard]] const_iterator end() const noexcept;
[[nodiscard]] const_iterator cend() const noexcept;
/**
* @name Field access
*/
//@{
[[nodiscard]] reference front() const noexcept;
[[nodiscard]] reference back() const noexcept;
// TODO: noexcept. Breaks ABI.
[[nodiscard]] const_reverse_row_iterator rbegin() const;
// TODO: noexcept. Breaks ABI.
[[nodiscard]] const_reverse_row_iterator crbegin() const;
// TODO: noexcept. Breaks ABI.
[[nodiscard]] const_reverse_row_iterator rend() const;
// TODO: noexcept. Breaks ABI.
[[nodiscard]] const_reverse_row_iterator crend() const;
[[nodiscard]] reference operator[](size_type) const noexcept;
/** Address field by name.
* @warning This is much slower than indexing by number, or iterating.
*/
[[nodiscard]] reference operator[](zview col_name) const;
reference at(size_type) const;
/** Address field by name.
* @warning This is much slower than indexing by number, or iterating.
*/
reference at(zview col_name) const;
[[nodiscard]] constexpr size_type size() const noexcept
{
return m_end - m_begin;
}
[[deprecated("Swap iterators, not rows.")]] void swap(row &) noexcept;
/// Row number, assuming this is a real row and not end()/rend().
[[nodiscard]] constexpr result::size_type rownumber() const noexcept
{
return m_index;
}
/**
* @name Column information
*/
//@{
/// Number of given column (throws exception if it doesn't exist).
[[nodiscard]] size_type column_number(zview col_name) const;
/// Return a column's type.
[[nodiscard]] oid column_type(size_type) const;
/// Return a column's type.
[[nodiscard]] oid column_type(zview col_name) const
{
return column_type(column_number(col_name));
}
/// What table did this column come from?
[[nodiscard]] oid column_table(size_type col_num) const;
/// What table did this column come from?
[[nodiscard]] oid column_table(zview col_name) const
{
return column_table(column_number(col_name));
}
/// What column number in its table did this result column come from?
/** A meaningful answer can be given only if the column in question comes
* directly from a column in a table. If the column is computed in any
* other way, a logic_error will be thrown.
*
* @param col_num a zero-based column number in this result set
* @return a zero-based column number in originating table
*/
[[nodiscard]] size_type table_column(size_type) const;
/// What column number in its table did this result column come from?
[[nodiscard]] size_type table_column(zview col_name) const
{
return table_column(column_number(col_name));
}
//@}
[[nodiscard]] constexpr result::size_type num() const noexcept
{
return rownumber();
}
/** Produce a slice of this row, containing the given range of columns.
*
* @deprecated I haven't heard of anyone caring about row slicing at all in
* at least the last 15 years. Yet it adds complexity, so unless anyone
* files a bug explaining why they really need this feature, I'm going to
* remove it. Even if they do, the feature may need an update.
*
* The slice runs from the range's starting column to the range's end
* column, exclusive. It looks just like a normal result row, except
* slices can be empty.
*/
[[deprecated("Row slicing is going away. File a bug if you need it.")]] row
slice(size_type sbegin, size_type send) const;
/// Is this a row without fields? Can only happen to a slice.
[[nodiscard, deprecated("Row slicing is going away.")]] PQXX_PURE bool
empty() const noexcept;
/// Extract entire row's values into a tuple.
/** Converts to the types of the tuple's respective fields.
*/
template<typename Tuple> void to(Tuple &t) const
{
check_size(std::tuple_size_v<Tuple>);
convert(t);
}
template<typename... TYPE> std::tuple<TYPE...> as() const
{
check_size(sizeof...(TYPE));
using seq = std::make_index_sequence<sizeof...(TYPE)>;
return get_tuple<std::tuple<TYPE...>>(seq{});
}
protected:
friend class const_row_iterator;
friend class result;
row(result const &r, result_size_type index, size_type cols) noexcept;
/// Throw @ref usage_error if row size is not `expected`.
void check_size(size_type expected) const
{
if (size() != expected)
throw usage_error{internal::concat(
"Tried to extract ", expected, " field(s) from a row of ", size(),
".")};
}
/// Convert to a given tuple of values, don't check sizes.
/** We need this for cases where we have a full tuple of field types, but
* not a parameter pack.
*/
template<typename TUPLE> TUPLE as_tuple() const
{
using seq = std::make_index_sequence<std::tuple_size_v<TUPLE>>;
return get_tuple<TUPLE>(seq{});
}
template<typename... T> friend class pqxx::internal::result_iter;
/// Convert entire row to tuple fields, without checking row size.
template<typename Tuple> void convert(Tuple &t) const
{
extract_fields(t, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
}
friend class field;
/// Result set of which this is one row.
result m_result;
/// Row number.
/**
* You'd expect this to be unsigned, but due to the way reverse iterators
* are related to regular iterators, it must be allowed to underflow to -1.
*/
result::size_type m_index = 0;
// TODO: Remove m_begin and (if possible) m_end when we remove slice().
/// First column in slice. This row ignores lower-numbered columns.
size_type m_begin = 0;
/// End column in slice. This row only sees lower-numbered columns.
size_type m_end = 0;
private:
template<typename Tuple, std::size_t... indexes>
void extract_fields(Tuple &t, std::index_sequence<indexes...>) const
{
(extract_value<Tuple, indexes>(t), ...);
}
template<typename Tuple, std::size_t index>
void extract_value(Tuple &t) const;
/// Convert row's values as a new tuple.
template<typename TUPLE, std::size_t... indexes>
auto get_tuple(std::index_sequence<indexes...>) const
{
return std::make_tuple(get_field<TUPLE, indexes>()...);
}
/// Extract and convert a field.
template<typename TUPLE, std::size_t index> auto get_field() const
{
return (*this)[index].as<std::tuple_element_t<index, TUPLE>>();
}
};
/// Iterator for fields in a row. Use as row::const_iterator.
class PQXX_LIBEXPORT const_row_iterator : public field
{
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = field const;
using pointer = field const *;
using size_type = row_size_type;
using difference_type = row_difference_type;
using reference = field;
#include "pqxx/internal/ignore-deprecated-pre.hxx"
const_row_iterator() = default;
#include "pqxx/internal/ignore-deprecated-post.hxx"
const_row_iterator(row const &t, row_size_type c) noexcept :
field{t.m_result, t.m_index, c}
{}
const_row_iterator(field const &F) noexcept : field{F} {}
const_row_iterator(const_row_iterator const &) noexcept = default;
const_row_iterator(const_row_iterator &&) noexcept = default;
/**
* @name Dereferencing operators
*/
//@{
[[nodiscard]] constexpr pointer operator->() const noexcept { return this; }
[[nodiscard]] reference operator*() const noexcept { return {*this}; }
//@}
/**
* @name Manipulations
*/
//@{
const_row_iterator &operator=(const_row_iterator const &) noexcept = default;
const_row_iterator &operator=(const_row_iterator &&) noexcept = default;
// TODO: noexcept. Breaks ABI.
const_row_iterator operator++(int);
const_row_iterator &operator++() noexcept
{
++m_col;
return *this;
}
// TODO: noexcept. Breaks ABI.
const_row_iterator operator--(int);
const_row_iterator &operator--() noexcept
{
--m_col;
return *this;
}
const_row_iterator &operator+=(difference_type i) noexcept
{
m_col = size_type(difference_type(m_col) + i);
return *this;
}
const_row_iterator &operator-=(difference_type i) noexcept
{
m_col = size_type(difference_type(m_col) - i);
return *this;
}
//@}
/**
* @name Comparisons
*/
//@{
[[nodiscard]] constexpr bool
operator==(const_row_iterator const &i) const noexcept
{
return col() == i.col();
}
[[nodiscard]] constexpr bool
operator!=(const_row_iterator const &i) const noexcept
{
return col() != i.col();
}
[[nodiscard]] constexpr bool
operator<(const_row_iterator const &i) const noexcept
{
return col() < i.col();
}
[[nodiscard]] constexpr bool
operator<=(const_row_iterator const &i) const noexcept
{
return col() <= i.col();
}
[[nodiscard]] constexpr bool
operator>(const_row_iterator const &i) const noexcept
{
return col() > i.col();
}
[[nodiscard]] constexpr bool
operator>=(const_row_iterator const &i) const noexcept
{
return col() >= i.col();
}
//@}
/**
* @name Arithmetic operators
*/
//@{
[[nodiscard]] inline const_row_iterator
operator+(difference_type) const noexcept;
friend const_row_iterator
operator+(difference_type, const_row_iterator const &) noexcept;
[[nodiscard]] inline const_row_iterator
operator-(difference_type) const noexcept;
[[nodiscard]] inline difference_type
operator-(const_row_iterator const &) const noexcept;
//@}
};
/// Reverse iterator for a row. Use as row::const_reverse_iterator.
class PQXX_LIBEXPORT const_reverse_row_iterator : private const_row_iterator
{
public:
using super = const_row_iterator;
using iterator_type = const_row_iterator;
using iterator_type::difference_type;
using iterator_type::iterator_category;
using iterator_type::pointer;
using value_type = iterator_type::value_type;
using reference = iterator_type::reference;
const_reverse_row_iterator() noexcept = default;
const_reverse_row_iterator(const_reverse_row_iterator const &) noexcept =
default;
const_reverse_row_iterator(const_reverse_row_iterator &&) noexcept = default;
explicit const_reverse_row_iterator(super const &rhs) noexcept :
const_row_iterator{rhs}
{
super::operator--();
}
[[nodiscard]] PQXX_PURE iterator_type base() const noexcept;
/**
* @name Dereferencing operators
*/
//@{
using iterator_type::operator->;
using iterator_type::operator*;
//@}
/**
* @name Manipulations
*/
//@{
const_reverse_row_iterator &
operator=(const_reverse_row_iterator const &r) noexcept
{
iterator_type::operator=(r);
return *this;
}
const_reverse_row_iterator operator++() noexcept
{
iterator_type::operator--();
return *this;
}
// TODO: noexcept. Breaks ABI.
const_reverse_row_iterator operator++(int);
const_reverse_row_iterator &operator--() noexcept
{
iterator_type::operator++();
return *this;
}
const_reverse_row_iterator operator--(int);
// TODO: noexcept. Breaks ABI.
const_reverse_row_iterator &operator+=(difference_type i) noexcept
{
iterator_type::operator-=(i);
return *this;
}
const_reverse_row_iterator &operator-=(difference_type i) noexcept
{
iterator_type::operator+=(i);
return *this;
}
//@}
/**
* @name Arithmetic operators
*/
//@{
[[nodiscard]] const_reverse_row_iterator
operator+(difference_type i) const noexcept
{
return const_reverse_row_iterator{base() - i};
}
[[nodiscard]] const_reverse_row_iterator
operator-(difference_type i) noexcept
{
return const_reverse_row_iterator{base() + i};
}
[[nodiscard]] difference_type
operator-(const_reverse_row_iterator const &rhs) const noexcept
{
return rhs.const_row_iterator::operator-(*this);
}
//@}
/**
* @name Comparisons
*/
//@{
[[nodiscard]] bool
operator==(const_reverse_row_iterator const &rhs) const noexcept
{
return iterator_type::operator==(rhs);
}
[[nodiscard]] bool
operator!=(const_reverse_row_iterator const &rhs) const noexcept
{
return !operator==(rhs);
}
[[nodiscard]] constexpr bool
operator<(const_reverse_row_iterator const &rhs) const noexcept
{
return iterator_type::operator>(rhs);
}
[[nodiscard]] constexpr bool
operator<=(const_reverse_row_iterator const &rhs) const noexcept
{
return iterator_type::operator>=(rhs);
}
[[nodiscard]] constexpr bool
operator>(const_reverse_row_iterator const &rhs) const noexcept
{
return iterator_type::operator<(rhs);
}
[[nodiscard]] constexpr bool
operator>=(const_reverse_row_iterator const &rhs) const noexcept
{
return iterator_type::operator<=(rhs);
}
//@}
};
const_row_iterator
const_row_iterator::operator+(difference_type o) const noexcept
{
// TODO:: More direct route to home().columns()?
return {
row{home(), idx(), home().columns()},
size_type(difference_type(col()) + o)};
}
inline const_row_iterator operator+(
const_row_iterator::difference_type o, const_row_iterator const &i) noexcept
{
return i + o;
}
inline const_row_iterator
const_row_iterator::operator-(difference_type o) const noexcept
{
// TODO:: More direct route to home().columns()?
return {
row{home(), idx(), home().columns()},
size_type(difference_type(col()) - o)};
}
inline const_row_iterator::difference_type
const_row_iterator::operator-(const_row_iterator const &i) const noexcept
{
return difference_type(num() - i.num());
}
template<typename Tuple, std::size_t index>
inline void row::extract_value(Tuple &t) const
{
using field_type = strip_t<decltype(std::get<index>(t))>;
field const f{m_result, m_index, index};
std::get<index>(t) = from_string<field_type>(f);
}
} // namespace pqxx
#endif
@@ -0,0 +1,6 @@
/** Helper similar to Python's @c str.join().
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/separated_list.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,142 @@
/* Helper similar to Python's `str.join()`.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/separated_list instead.
*
* 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.
*/
#ifndef PQXX_H_SEPARATED_LIST
#define PQXX_H_SEPARATED_LIST
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <algorithm>
#include <numeric>
#include "pqxx/strconv.hxx"
// C++20: Simplify using std::ranges::range.
// C++20: Optimise buffer allocation using random_access_range/iterator.
namespace pqxx
{
/**
* @defgroup utility Utility functions
*/
//@{
/// Represent sequence of values as a string, joined by a given separator.
/**
* Use this to turn e.g. the numbers 1, 2, and 3 into a string "1, 2, 3".
*
* @param sep separator string (to be placed between items)
* @param begin beginning of items sequence
* @param end end of items sequence
* @param access functor defining how to dereference sequence elements
*/
template<typename ITER, typename ACCESS>
[[nodiscard]] inline std::string
separated_list(std::string_view sep, ITER begin, ITER end, ACCESS access)
{
if (end == begin)
return {};
auto next{begin};
++next;
if (next == end)
return to_string(access(begin));
// From here on, we've got at least 2 elements -- meaning that we need sep.
using elt_type = strip_t<decltype(access(begin))>;
using traits = string_traits<elt_type>;
std::size_t budget{0};
for (ITER cnt{begin}; cnt != end; ++cnt)
budget += traits::size_buffer(access(cnt));
budget +=
static_cast<std::size_t>(std::distance(begin, end)) * std::size(sep);
std::string result;
result.resize(budget);
char *const data{result.data()};
char *here{data};
char *stop{data + budget};
here = traits::into_buf(here, stop, access(begin)) - 1;
for (++begin; begin != end; ++begin)
{
here += sep.copy(here, std::size(sep));
here = traits::into_buf(here, stop, access(begin)) - 1;
}
result.resize(static_cast<std::size_t>(here - data));
return result;
}
/// Render sequence as a string, using given separator between items.
template<typename ITER>
[[nodiscard]] inline std::string
separated_list(std::string_view sep, ITER begin, ITER end)
{
return separated_list(sep, begin, end, [](ITER i) { return *i; });
}
/// Render items in a container as a string, using given separator.
template<typename CONTAINER>
[[nodiscard]] inline auto
separated_list(std::string_view sep, CONTAINER const &c)
/*
Always std::string; necessary because SFINAE doesn't work with the
contents of function bodies, so the check for iterability has to be in
the signature.
*/
-> typename std::enable_if<
(not std::is_void<decltype(std::begin(c))>::value and
not std::is_void<decltype(std::end(c))>::value),
std::string>::type
{
return separated_list(sep, std::begin(c), std::end(c));
}
/// Render items in a tuple as a string, using given separator.
template<
typename TUPLE, std::size_t INDEX = 0, typename ACCESS,
typename std::enable_if<
(INDEX == std::tuple_size<TUPLE>::value - 1), int>::type = 0>
[[nodiscard]] inline std::string separated_list(
std::string_view /* sep */, TUPLE const &t, ACCESS const &access)
{
return to_string(access(&std::get<INDEX>(t)));
}
template<
typename TUPLE, std::size_t INDEX = 0, typename ACCESS,
typename std::enable_if<
(INDEX < std::tuple_size<TUPLE>::value - 1), int>::type = 0>
[[nodiscard]] inline std::string
separated_list(std::string_view sep, TUPLE const &t, ACCESS const &access)
{
std::string out{to_string(access(&std::get<INDEX>(t)))};
out.append(sep);
out.append(separated_list<TUPLE, INDEX + 1>(sep, t, access));
return out;
}
template<
typename TUPLE, std::size_t INDEX = 0,
typename std::enable_if<
(INDEX <= std::tuple_size<TUPLE>::value), int>::type = 0>
[[nodiscard]] inline std::string
separated_list(std::string_view sep, TUPLE const &t)
{
// TODO: Optimise allocation.
return separated_list(sep, t, [](TUPLE const &tup) { return *tup; });
}
//@}
} // namespace pqxx
#endif
+6
View File
@@ -0,0 +1,6 @@
/** String conversion definitions.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/strconv.hxx"
#include "pqxx/internal/header-post.hxx"
+468
View File
@@ -0,0 +1,468 @@
/* String conversion definitions.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stringconv instead.
*
* 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.
*/
#ifndef PQXX_H_STRCONV
#define PQXX_H_STRCONV
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <algorithm>
#include <cstring>
#include <limits>
#include <sstream>
#include <stdexcept>
#include <typeinfo>
#if __has_include(<charconv>)
# include <charconv>
#endif
#if defined(PQXX_HAVE_RANGES) && __has_include(<ranges>)
# include <ranges>
#endif
#include "pqxx/except.hxx"
#include "pqxx/util.hxx"
#include "pqxx/zview.hxx"
namespace pqxx::internal
{
/// Attempt to demangle @c std::type_info::name() to something human-readable.
PQXX_LIBEXPORT std::string demangle_type_name(char const[]);
} // namespace pqxx::internal
namespace pqxx
{
/**
* @defgroup stringconversion String conversion
*
* The PostgreSQL server accepts and represents data in string form. It has
* its own formats for various data types. The string conversions define how
* various C++ types translate to and from their respective PostgreSQL text
* representations.
*
* Each conversion is defined by a specialisations of @c string_traits. It
* gets complicated if you want top performance, but until you do, all you
* really need to care about when converting values between C++ in-memory
* representations such as @c int and the postgres string representations is
* the @c pqxx::to_string and @c pqxx::from_string functions.
*
* If you need to convert a type which is not supported out of the box, you'll
* need to define your own specialisations for these templates, similar to the
* ones defined here and in `pqxx/conversions.hxx`. Any conversion code which
* "sees" your specialisation will now support your conversion. In particular,
* you'll be able to read result fields into a variable of the new type.
*
* There is a macro to help you define conversions for individual enumeration
* types. The conversion will represent enumeration values as numeric strings.
*/
//@{
/// A human-readable name for a type, used in error messages and such.
/** Actually this may not always be very user-friendly. It uses
* @c std::type_info::name(). On gcc-like compilers we try to demangle its
* output. Visual Studio produces human-friendly names out of the box.
*
* This variable is not inline. Inlining it gives rise to "memory leak"
* warnings from asan, the address sanitizer, possibly from use of
* @c std::type_info::name.
*/
template<typename TYPE>
std::string const type_name{internal::demangle_type_name(typeid(TYPE).name())};
/// Traits describing a type's "null value," if any.
/** Some C++ types have a special value or state which correspond directly to
* SQL's NULL.
*
* The @c nullness traits describe whether it exists, and whether a particular
* value is null.
*/
template<typename TYPE, typename ENABLE = void> struct nullness
{
/// Does this type have a null value?
static bool has_null;
/// Is this type always null?
static bool always_null;
/// Is @c value a null?
static bool is_null(TYPE const &value);
/// Return a null value.
/** Don't use this in generic code to compare a value and see whether it is
* null. Some types may have multiple null values which do not compare as
* equal, or may define a null value which is not equal to anything including
* itself, like in SQL.
*/
[[nodiscard]] static TYPE null();
};
/// Nullness traits describing a type which does not have a null value.
template<typename TYPE> struct no_null
{
/// Does @c TYPE have a "built-in null value"?
/** For example, a pointer can equal @c nullptr, which makes a very natural
* representation of an SQL null value. For such types, the code sometimes
* needs to make special allowances.
*
* for most types, such as @c int or @c std::string, there is no built-in
* null. If you want to represent an SQL null value for such a type, you
* would have to wrap it in something that does have a null value. For
* example, you could use @c std::optional<int> for "either an @c int or a
* null value."
*/
static constexpr bool has_null = false;
/// Are all values of this type null?
/** There are a few special C++ types which are always null - mainly
* @c std::nullptr_t.
*/
static constexpr bool always_null = false;
/// Does a given value correspond to an SQL null value?
/** Most C++ types, such as @c int or @c std::string, have no inherent null
* value. But some types such as C-style string pointers do have a natural
* equivalent to an SQL null.
*/
[[nodiscard]] static constexpr bool is_null(TYPE const &) noexcept
{
return false;
}
};
/// Traits class for use in string conversions.
/** Specialize this template for a type for which you wish to add to_string
* and from_string support.
*
* String conversions are not meant to work for nulls. Check for null before
* converting a value of @c TYPE to a string, or vice versa.
*/
template<typename TYPE> struct string_traits
{
/// Return a @c string_view representing value, plus terminating zero.
/** Produces a @c string_view containing the PostgreSQL string representation
* for @c value.
*
* Uses the space from @c begin to @c end as a buffer, if needed. The
* returned string may lie somewhere in that buffer, or it may be a
* compile-time constant, or it may be null if value was a null value. Even
* if the string is stored in the buffer, its @c begin() may or may not be
* the same as @c begin.
*
* The @c string_view is guaranteed to be valid as long as the buffer from
* @c begin to @c end remains accessible and unmodified.
*
* @throws pqxx::conversion_overrun if the provided buffer space may not be
* enough. For maximum performance, this is a conservative estimate. It may
* complain about a buffer which is actually large enough for your value, if
* an exact check gets too expensive.
*/
[[nodiscard]] static inline zview
to_buf(char *begin, char *end, TYPE const &value);
/// Write value's string representation into buffer at @c begin.
/** Assumes that value is non-null.
*
* Writes value's string representation into the buffer, starting exactly at
* @c begin, and ensuring a trailing zero. Returns the address just beyond
* the trailing zero, so the caller could use it as the @c begin for another
* call to @c into_buf writing a next value.
*/
static inline char *into_buf(char *begin, char *end, TYPE const &value);
/// Parse a string representation of a @c TYPE value.
/** Throws @c conversion_error if @c value does not meet the expected format
* for a value of this type.
*/
[[nodiscard]] static inline TYPE from_string(std::string_view text);
// C++20: Can we make these all constexpr?
/// Estimate how much buffer space is needed to represent value.
/** The estimate may be a little pessimistic, if it saves time.
*
* The estimate includes the terminating zero.
*/
[[nodiscard]] static inline std::size_t
size_buffer(TYPE const &value) noexcept;
};
/// Nullness: Enums do not have an inherent null value.
template<typename ENUM>
struct nullness<ENUM, std::enable_if_t<std::is_enum_v<ENUM>>> : no_null<ENUM>
{};
} // namespace pqxx
namespace pqxx::internal
{
/// Helper class for defining enum conversions.
/** The conversion will convert enum values to numeric strings, and vice versa.
*
* To define a string conversion for an enum type, derive a @c string_traits
* specialisation for the enum from this struct.
*
* There's usually an easier way though: the @c PQXX_DECLARE_ENUM_CONVERSION
* macro. Use @c enum_traits manually only if you need to customise your
* traits type in more detail.
*/
template<typename ENUM> struct enum_traits
{
using impl_type = std::underlying_type_t<ENUM>;
using impl_traits = string_traits<impl_type>;
[[nodiscard]] static constexpr zview
to_buf(char *begin, char *end, ENUM const &value)
{
return impl_traits::to_buf(begin, end, to_underlying(value));
}
static constexpr char *into_buf(char *begin, char *end, ENUM const &value)
{
return impl_traits::into_buf(begin, end, to_underlying(value));
}
[[nodiscard]] static ENUM from_string(std::string_view text)
{
return static_cast<ENUM>(impl_traits::from_string(text));
}
[[nodiscard]] static std::size_t size_buffer(ENUM const &value) noexcept
{
return impl_traits::size_buffer(to_underlying(value));
}
private:
// C++23: Replace with std::to_underlying.
static constexpr impl_type to_underlying(ENUM const &value) noexcept
{
return static_cast<impl_type>(value);
}
};
} // namespace pqxx::internal
/// Macro: Define a string conversion for an enum type.
/** This specialises the @c pqxx::string_traits template, so use it in the
* @c ::pqxx namespace.
*
* For example:
*
* #include <iostream>
* #include <pqxx/strconv>
* enum X { xa, xb };
* namespace pqxx { PQXX_DECLARE_ENUM_CONVERSION(x); }
* int main() { std::cout << pqxx::to_string(xa) << std::endl; }
*/
#define PQXX_DECLARE_ENUM_CONVERSION(ENUM) \
template<> struct string_traits<ENUM> : pqxx::internal::enum_traits<ENUM> \
{}; \
template<> inline std::string const type_name<ENUM> { #ENUM }
namespace pqxx
{
/// Parse a value in postgres' text format as a TYPE.
/** If the form of the value found in the string does not match the expected
* type, e.g. if a decimal point is found when converting to an integer type,
* the conversion fails. Overflows (e.g. converting "9999999999" to a 16-bit
* C++ type) are also treated as errors. If in some cases this behaviour
* should be inappropriate, convert to something bigger such as @c long @c int
* first and then truncate the resulting value.
*
* Only the simplest possible conversions are supported. Fancy features like
* hexadecimal or octal, spurious signs, or exponent notation won't work.
* Whitespace is not stripped away. Only the kinds of strings that come out of
* PostgreSQL and out of to_string() can be converted.
*/
template<typename TYPE>
[[nodiscard]] inline TYPE from_string(std::string_view text)
{
return string_traits<TYPE>::from_string(text);
}
/// "Convert" a std::string_view to a std::string_view.
/** Just returns its input.
*
* @warning Of course the result is only valid for as long as the original
* string remains valid! Never access the string referenced by the return
* value after the original has been destroyed.
*/
template<>
[[nodiscard]] inline std::string_view from_string(std::string_view text)
{
return text;
}
/// Attempt to convert postgres-generated string to given built-in object.
/** This is like the single-argument form of the function, except instead of
* returning the value, it sets @c value.
*
* You may find this more convenient in that it infers the type you want from
* the argument you pass. But there are disadvantages: it requires an
* assignment operator, and it may be less efficient.
*/
template<typename T> inline void from_string(std::string_view text, T &value)
{
value = from_string<T>(text);
}
/// Convert a value to a readable string that PostgreSQL will understand.
/** The conversion does no special formatting, and ignores any locale settings.
* The resulting string will be human-readable and in a format suitable for use
* in SQL queries. It won't have niceties such as "thousands separators"
* though.
*/
template<typename TYPE> inline std::string to_string(TYPE const &value);
/// Convert multiple values to strings inside a single buffer.
/** There must be enough room for all values, or this will throw
* @c conversion_overrun. You can obtain a conservative estimate of the buffer
* space required by calling @c size_buffer() on the values.
*
* The @c std::string_view results may point into the buffer, so don't assume
* that they will remain valid after you destruct or move the buffer.
*/
template<typename... TYPE>
[[nodiscard]] inline std::vector<std::string_view>
to_buf(char *here, char const *end, TYPE... value)
{
return {[&here, end](auto v) {
auto begin = here;
here = string_traits<decltype(v)>::into_buf(begin, end, v);
// Exclude the trailing zero out of the string_view.
auto len{static_cast<std::size_t>(here - begin) - 1};
return std::string_view{begin, len};
}(value)...};
}
/// Convert a value to a readable string that PostgreSQL will understand.
/** This variant of to_string can sometimes save a bit of time in loops, by
* re-using a std::string for multiple conversions.
*/
template<typename TYPE>
inline void into_string(TYPE const &value, std::string &out);
/// Is @c value null?
template<typename TYPE>
[[nodiscard]] inline constexpr bool is_null(TYPE const &value) noexcept
{
return nullness<strip_t<TYPE>>::is_null(value);
}
/// Estimate how much buffer space is needed to represent values as a string.
/** The estimate may be a little pessimistic, if it saves time. It also
* includes room for a terminating zero after each value.
*/
template<typename... TYPE>
[[nodiscard]] inline std::size_t size_buffer(TYPE const &...value) noexcept
{
return (string_traits<strip_t<TYPE>>::size_buffer(value) + ...);
}
/// Does this type translate to an SQL array?
/** Specialisations may override this to be true for container types.
*
* This may not always be a black-and-white choice. For instance, a
* @c std::string is a container, but normally it translates to an SQL string,
* not an SQL array.
*/
template<typename TYPE> inline constexpr bool is_sql_array{false};
/// Can we use this type in arrays and composite types without quoting them?
/** Define this as @c true only if values of @c TYPE can never contain any
* special characters that might need escaping or confuse the parsing of array
* or composite * types, such as commas, quotes, parentheses, braces, newlines,
* and so on.
*
* When converting a value of such a type to a string in an array or a field in
* a composite type, we do not need to add quotes, nor escape any special
* characters.
*
* This is just an optimisation, so it defaults to @c false to err on the side
* of slow correctness.
*/
template<typename TYPE> inline constexpr bool is_unquoted_safe{false};
/// Element separator between SQL array elements of this type.
template<typename T> inline constexpr char array_separator{','};
/// What's the preferred format for passing non-null parameters of this type?
/** This affects how we pass parameters of @c TYPE when calling parameterised
* statements or prepared statements.
*
* Generally we pass parameters in text format, but binary strings are the
* exception. We also pass nulls in binary format, so this function need not
* handle null values.
*/
template<typename TYPE> inline constexpr format param_format(TYPE const &)
{
return format::text;
}
/// Implement @c string_traits<TYPE>::to_buf by calling @c into_buf.
/** When you specialise @c string_traits for a new type, most of the time its
* @c to_buf implementation has no special optimisation tricks and just writes
* its text into the buffer it receives from the caller, starting at the
* beginning.
*
* In that common situation, you can implement @c to_buf as just a call to
* @c generic_to_buf. It will call @c into_buf and return the right result for
* @c to_buf.
*/
template<typename TYPE>
inline zview generic_to_buf(char *begin, char *end, TYPE const &value)
{
using traits = string_traits<TYPE>;
// The trailing zero does not count towards the zview's size, so subtract 1
// from the result we get from into_buf().
if (is_null(value))
return {};
else
return {begin, traits::into_buf(begin, end, value) - begin - 1};
}
#if defined(PQXX_HAVE_CONCEPTS)
/// Concept: Binary string, akin to @c std::string for binary data.
/** Any type that satisfies this concept can represent an SQL BYTEA value.
*
* A @c binary has a @c begin(), @c end(), @c size(), and @data(). Each byte
* is a @c std::byte, and they must all be laid out contiguously in memory so
* we can reference them by a pointer.
*/
template<class TYPE>
concept binary = std::ranges::contiguous_range<TYPE> and
std::is_same_v<strip_t<value_type<TYPE>>, std::byte>;
#endif
//@}
} // namespace pqxx
#include "pqxx/internal/conversions.hxx"
#endif
@@ -0,0 +1,8 @@
/** pqxx::stream_from class.
*
* pqxx::stream_from enables optimized batch reads from a database table.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/stream_from.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,361 @@
/* Definition of the pqxx::stream_from class.
*
* pqxx::stream_from enables optimized batch reads from a database table.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stream_from instead.
*
* 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.
*/
#ifndef PQXX_H_STREAM_FROM
#define PQXX_H_STREAM_FROM
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include <cassert>
#include <functional>
#include <variant>
#include "pqxx/connection.hxx"
#include "pqxx/except.hxx"
#include "pqxx/internal/concat.hxx"
#include "pqxx/internal/encoding_group.hxx"
#include "pqxx/internal/stream_iterator.hxx"
#include "pqxx/separated_list.hxx"
#include "pqxx/transaction_focus.hxx"
namespace pqxx
{
class transaction_base;
/// Pass this to a `stream_from` constructor to stream table contents.
/** @deprecated Use @ref stream_from::table() instead.
*/
constexpr from_table_t from_table;
/// Pass this to a `stream_from` constructor to stream query results.
/** @deprecated Use stream_from::query() instead.
*/
constexpr from_query_t from_query;
/// Stream data from the database.
/** For larger data sets, retrieving data this way is likely to be faster than
* executing a query and then iterating and converting the rows fields. You
* will also be able to start processing before all of the data has come in.
*
* There are also downsides. Not all kinds of query will work in a stream.
* But straightforward `SELECT` and `UPDATE ... RETURNING` queries should work.
* This function makes use of @ref pqxx::stream_from, which in turn uses
* PostgreSQL's `COPY` command, so see the documentation for those to get the
* full details.
*
* There are other downsides. If there stream encounters an error, it may
* leave the entire connection in an unusable state, so you'll have to give the
* whole thing up. Finally, opening a stream puts the connection in a special
* state, so you won't be able to do many other things with the connection or
* the transaction while the stream is open.
*
* There are two ways of starting a stream: you stream either all rows in a
* table (using one of the factories, `table()` or `raw_table()`), or the
* results of a query (using the `query()` factory).
*
* Usually you'll want the `stream` convenience wrapper in
* @ref transaction_base, * so you don't need to deal with this class directly.
*
* @warning While a stream is active, you cannot execute queries, open a
* pipeline, etc. on the same transaction. A transaction can have at most one
* object of a type derived from @ref pqxx::transaction_focus active on it at a
* time.
*/
class PQXX_LIBEXPORT stream_from : transaction_focus
{
public:
using raw_line =
std::pair<std::unique_ptr<char, std::function<void(char *)>>, std::size_t>;
/// Factory: Execute query, and stream the results.
/** The query can be a SELECT query or a VALUES query; or it can be an
* UPDATE, INSERT, or DELETE with a RETURNING clause.
*
* The query is executed as part of a COPY statement, so there are additional
* restrictions on what kind of query you can use here. See the PostgreSQL
* documentation for the COPY command:
*
* https://www.postgresql.org/docs/current/sql-copy.html
*/
static stream_from query(transaction_base &tx, std::string_view q)
{
#include "pqxx/internal/ignore-deprecated-pre.hxx"
return {tx, from_query, q};
#include "pqxx/internal/ignore-deprecated-post.hxx"
}
/**
* @name Streaming data from tables
*
* You can use `stream_from` to read a table's contents. This is a quick
* and easy way to read a table, but it comes with limitations. It cannot
* stream from a view, only from a table. It does not support conditions.
* And there are no guarantees about ordering. If you need any of those
* things, consider streaming from a query instead.
*/
//@{
/// Factory: Stream data from a pre-quoted table and columns.
/** Use this factory if you need to create multiple streams using the same
* table path and/or columns list, and you want to save a bit of work on
* composing the internal SQL statement for starting the stream. It lets you
* compose the string representations for the table path and the columns
* list, so you can compute these once and then re-use them later.
*
* @param tx The transaction within which the stream will operate.
* @param path Name or path for the table upon which the stream will
* operate. If any part of the table path may contain special
* characters or be case-sensitive, quote the path using
* pqxx::connection::quote_table().
* @param columns Columns which the stream will read. They should be
* comma-separated and, if needed, quoted. You can produce the string
* using pqxx::connection::quote_columns(). If you omit this argument,
* the stream will read all columns in the table, in schema order.
*/
static stream_from raw_table(
transaction_base &tx, std::string_view path,
std::string_view columns = ""sv);
/// Factory: Stream data from a given table.
/** This is the convenient way to stream from a table.
*/
static stream_from table(
transaction_base &tx, table_path path,
std::initializer_list<std::string_view> columns = {});
//@}
/// Execute query, and stream over the results.
/** @deprecated Use factory function @ref query instead.
*/
[[deprecated("Use query() factory instead.")]] stream_from(
transaction_base &, from_query_t, std::string_view query);
/// Stream all rows in table, all columns.
/** @deprecated Use factories @ref table or @ref raw_table instead.
*/
[[deprecated("Use table() or raw_table() factory instead.")]] stream_from(
transaction_base &, from_table_t, std::string_view table);
/// Stream given columns from all rows in table.
/** @deprecated Use factories @ref table or @ref raw_table instead.
*/
template<typename Iter>
[[deprecated("Use table() or raw_table() factory instead.")]] stream_from(
transaction_base &, from_table_t, std::string_view table,
Iter columns_begin, Iter columns_end);
/// Stream given columns from all rows in table.
/** @deprecated Use factory function @ref query instead.
*/
template<typename Columns>
[[deprecated("Use table() or raw_table() factory instead.")]] stream_from(
transaction_base &tx, from_table_t, std::string_view table,
Columns const &columns);
#include "pqxx/internal/ignore-deprecated-pre.hxx"
/// @deprecated Use factories @ref table or @ref raw_table instead.
[[deprecated("Use the from_table_t overload instead.")]] stream_from(
transaction_base &tx, std::string_view table) :
stream_from{tx, from_table, table}
{}
#include "pqxx/internal/ignore-deprecated-post.hxx"
/// @deprecated Use factories @ref table or @ref raw_table instead.
template<typename Columns>
[[deprecated("Use the from_table_t overload instead.")]] stream_from(
transaction_base &tx, std::string_view table, Columns const &columns) :
stream_from{tx, from_table, table, columns}
{}
/// @deprecated Use factories @ref table or @ref raw_table instead.
template<typename Iter>
[[deprecated("Use the from_table_t overload instead.")]] stream_from(
transaction_base &, std::string_view table, Iter columns_begin,
Iter columns_end);
~stream_from() noexcept;
/// May this stream still produce more data?
[[nodiscard]] constexpr operator bool() const noexcept
{
return not m_finished;
}
/// Has this stream produced all the data it is going to produce?
[[nodiscard]] constexpr bool operator!() const noexcept
{
return m_finished;
}
/// Finish this stream. Call this before continuing to use the connection.
/** Consumes all remaining lines, and closes the stream.
*
* This may take a while if you're abandoning the stream before it's done, so
* skip it in error scenarios where you're not planning to use the connection
* again afterwards.
*/
void complete();
/// Read one row into a tuple.
/** Converts the row's fields into the fields making up the tuple.
*
* For a column which can contain nulls, be sure to give the corresponding
* tuple field a type which can be null. For example, to read a field as
* `int` when it may contain nulls, read it as `std::optional<int>`.
* Using `std::shared_ptr` or `std::unique_ptr` will also work.
*/
template<typename Tuple> stream_from &operator>>(Tuple &);
/// Doing this with a `std::variant` is going to be horrifically borked.
template<typename... Vs>
stream_from &operator>>(std::variant<Vs...> &) = delete;
/// Iterate over this stream. Supports range-based "for" loops.
/** Produces an input iterator over the stream.
*
* Do not call this yourself. Use it like "for (auto data : stream.iter())".
*/
template<typename... TYPE> [[nodiscard]] auto iter() &
{
return pqxx::internal::stream_input_iteration<TYPE...>{*this};
}
/// Read a row. Return fields as views, valid until you read the next row.
/** Returns `nullptr` when there are no more rows to read. Do not attempt
* to read any further rows after that.
*
* Do not access the vector, or the storage referenced by the views, after
* closing or completing the stream, or after attempting to read a next row.
*
* A @ref pqxx::zview is like a `std::string_view`, but with the added
* guarantee that if its data pointer is non-null, the string is followed by
* a terminating zero (which falls just outside the view itself).
*
* If any of the views' data pointer is null, that means that the
* corresponding SQL field is null.
*
* @warning The return type may change in the future, to support C++20
* coroutine-based usage.
*/
std::vector<zview> const *read_row() &;
/// Read a raw line of text from the COPY command.
/** @warning Do not use this unless you really know what you're doing. */
raw_line get_raw_line();
private:
// TODO: Clean up this signature once we cull the deprecated constructors.
/// @deprecated
stream_from(
transaction_base &tx, std::string_view table, std::string_view columns,
from_table_t);
// TODO: Clean up this signature once we cull the deprecated constructors.
/// @deprecated
stream_from(
transaction_base &, std::string_view unquoted_table,
std::string_view columns, from_table_t, int);
template<typename Tuple, std::size_t... indexes>
void extract_fields(Tuple &t, std::index_sequence<indexes...>) const
{
(extract_value<Tuple, indexes>(t), ...);
}
pqxx::internal::glyph_scanner_func *m_glyph_scanner;
/// Current row's fields' text, combined into one reusable string.
std::string m_row;
/// The current row's fields.
std::vector<zview> m_fields;
bool m_finished = false;
void close();
template<typename Tuple, std::size_t index>
void extract_value(Tuple &) const;
/// Read a line of COPY data, write `m_row` and `m_fields`.
void parse_line();
};
template<typename Columns>
inline stream_from::stream_from(
transaction_base &tx, from_table_t, std::string_view table_name,
Columns const &columns) :
stream_from{
tx, from_table, table_name, std::begin(columns), std::end(columns)}
{}
template<typename Iter>
inline stream_from::stream_from(
transaction_base &tx, from_table_t, std::string_view table,
Iter columns_begin, Iter columns_end) :
stream_from{
tx, table, separated_list(",", columns_begin, columns_end),
from_table, 1}
{}
template<typename Tuple> inline stream_from &stream_from::operator>>(Tuple &t)
{
if (m_finished)
return *this;
static constexpr auto tup_size{std::tuple_size_v<Tuple>};
m_fields.reserve(tup_size);
parse_line();
if (m_finished)
return *this;
if (std::size(m_fields) != tup_size)
throw usage_error{internal::concat(
"Tried to extract ", tup_size, " field(s) from a stream of ",
std::size(m_fields), ".")};
extract_fields(t, std::make_index_sequence<tup_size>{});
return *this;
}
template<typename Tuple, std::size_t index>
inline void stream_from::extract_value(Tuple &t) const
{
using field_type = strip_t<decltype(std::get<index>(t))>;
using nullity = nullness<field_type>;
assert(index < std::size(m_fields));
if constexpr (nullity::always_null)
{
if (std::data(m_fields[index]) != nullptr)
throw conversion_error{"Streaming non-null value into null field."};
}
else if (std::data(m_fields[index]) == nullptr)
{
if constexpr (nullity::has_null)
std::get<index>(t) = nullity::null();
else
internal::throw_null_conversion(type_name<field_type>);
}
else
{
// Don't ever try to convert a non-null value to nullptr_t!
std::get<index>(t) = from_string<field_type>(m_fields[index]);
}
}
} // namespace pqxx
#endif
+8
View File
@@ -0,0 +1,8 @@
/** pqxx::stream_to class.
*
* pqxx::stream_to enables optimized batch updates to a database table.
*/
// Actual definitions in .hxx file so editors and such recognize file type.
#include "pqxx/internal/header-pre.hxx"
#include "pqxx/stream_to.hxx"
#include "pqxx/internal/header-post.hxx"
@@ -0,0 +1,455 @@
/* Definition of the pqxx::stream_to class.
*
* pqxx::stream_to enables optimized batch updates to a database table.
*
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stream_to.hxx instead.
*
* 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.
*/
#ifndef PQXX_H_STREAM_TO
#define PQXX_H_STREAM_TO
#if !defined(PQXX_HEADER_PRE)
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
#endif
#include "pqxx/separated_list.hxx"
#include "pqxx/transaction_base.hxx"
namespace pqxx
{
/// Efficiently write data directly to a database table.
/** If you wish to insert rows of data into a table, you can compose INSERT
* statements and execute them. But it's slow and tedious, and you need to
* worry about quoting and escaping the data.
*
* If you're just inserting a single row, it probably won't matter much. You
* can use prepared or parameterised statements to take care of the escaping
* for you. But if you're inserting large numbers of rows you will want
* something better.
*
* Inserting rows one by one using INSERT statements involves a lot of
* pointless overhead, especially when you are working with a remote database
* server over the network. You may end up sending each row over the network
* as a separate query, and waiting for a reply. Do it "in bulk" using
* `stream_to`, and you may find that it goes many times faster. Sometimes
* you gain orders of magnitude in speed.
*
* Here's how it works: you create a `stream_to` stream to start writing to
* your table. You will probably want to specify the columns. Then, you
* feed your data into the stream one row at a time. And finally, you call the
* stream's @ref complete function to tell it to finalise the operation, wait
* for completion, and check for errors.
*
* (You _must_ complete the stream before committing or aborting the
* transaction. The connection is in a special state while the stream is
* active, where it can't process commands, and can't commit or abort a
* transaction.)
*
* So how do you feed a row of data into the stream? There's several ways, but
* the preferred one is to call its @ref write_values. Pass the field values
* as arguments. Doesn't matter what type they are, as long as libpqxx knows
* how to convert them to PostgreSQL's text format: `int`, `std::string` or
* `std:string_view`, `float` and `double`, `bool`... lots of basic types
* are supported. If some of the values are null, feel free to use
* `std::optional`, `std::shared_ptr`, or `std::unique_ptr`.
*
* The arguments' types don't even have to match the fields' SQL types. If you
* want to insert an `int` into a `DECIMAL` column, that's your choice -- it
* will produce a `DECIMAL` value which happens to be integral. Insert a
* `float` into a `VARCHAR` column? That's fine, you'll get a string whose
* contents happen to read like a number. And so on. You can even insert
* different types of value in the same column on different rows. If you have
* a code path where a particular field is always null, just insert `nullptr`.
*
* There is another way to insert rows: the `<<` ("shift-left") operator.
* It's not as fast and it doesn't support variable arguments: each row must be
* either a `std::tuple` or something iterable, such as a `std::vector`, or
* anything else with a `begin()` and `end()`.
*
* @warning While a stream is active, you cannot execute queries, open a
* pipeline, etc. on the same transaction. A transaction can have at most one
* object of a type derived from @ref pqxx::transaction_focus active on it at a
* time.
*/
class PQXX_LIBEXPORT stream_to : transaction_focus
{
public:
/// Stream data to a pre-quoted table and columns.
/** This factory can be useful when it's not convenient to provide the
* columns list in the form of a `std::initializer_list`, or when the list
* of columns is simply not known at compile time.
*
* Also use this if you need to create multiple streams using the same table
* path and/or columns list, and you want to save a bit of work on composing
* the internal SQL statement for starting the stream. It lets you compose
* the string representations for the table path and the columns list, so you
* can compute these once and then re-use them later.
*
* @param tx The transaction within which the stream will operate.
* @param path Name or path for the table upon which the stream will
* operate. If any part of the table path may contain special
* characters or be case-sensitive, quote the path using
* pqxx::connection::quote_table().
* @param columns Columns to which the stream will write. They should be
* comma-separated and, if needed, quoted. You can produce the string
* using pqxx::connection::quote_columns(). If you omit this argument,
* the stream will write all columns in the table, in schema order.
*/
static stream_to raw_table(
transaction_base &tx, std::string_view path, std::string_view columns = "")
{
return {tx, path, columns};
}
/// Create a `stream_to` writing to a named table and columns.
/** Use this to stream data to a table, where the list of columns is known at
* compile time.
*
* @param tx The transaction within which the stream will operate.
* @param path A @ref table_path designating the target table.
* @param columns Optionally, the columns to which the stream should write.
* If you do not pass this, the stream will write to all columns in the
* table, in schema order.
*/
static stream_to 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));
}
#if defined(PQXX_HAVE_CONCEPTS)
/// Create a `stream_to` writing to a named table and columns.
/** Use this version to stream data to a table, when the list of columns is
* not known at compile time.
*
* @param tx The transaction within which the stream will operate.
* @param path A @ref table_path designating the target table.
* @param columns The columns to which the stream should write.
*/
template<PQXX_CHAR_STRINGS_ARG COLUMNS>
static stream_to
table(transaction_base &tx, table_path path, COLUMNS const &columns)
{
auto const &conn{tx.conn()};
return stream_to::raw_table(
tx, conn.quote_table(path), tx.conn().quote_columns(columns));
}
/// Create a `stream_to` writing to a named table and columns.
/** Use this version to stream data to a table, when the list of columns is
* not known at compile time.
*
* @param tx The transaction within which the stream will operate.
* @param path A @ref table_path designating the target table.
* @param columns The columns to which the stream should write.
*/
template<PQXX_CHAR_STRINGS_ARG COLUMNS>
static stream_to
table(transaction_base &tx, std::string_view path, COLUMNS const &columns)
{
return stream_to::raw_table(tx, path, tx.conn().quote_columns(columns));
}
#endif // PQXX_HAVE_CONCEPTS
/// Create a stream, without specifying columns.
/** @deprecated Use @ref table or @ref raw_table as a factory.
*
* Fields will be inserted in whatever order the columns have in the
* database.
*
* You'll probably want to specify the columns, so that the mapping between
* your data fields and the table is explicit in your code, and not hidden
* in an "implicit contract" between your code and your schema.
*/
[[deprecated("Use table() or raw_table() factory.")]] stream_to(
transaction_base &tx, std::string_view table_name) :
stream_to{tx, table_name, ""sv}
{}
/// Create a stream, specifying column names as a container of strings.
/** @deprecated Use @ref table or @ref raw_table as a factory.
*/
template<typename Columns>
[[deprecated("Use table() or raw_table() factory.")]] stream_to(
transaction_base &, std::string_view table_name, Columns const &columns);
/// Create a stream, specifying column names as a sequence of strings.
/** @deprecated Use @ref table or @ref raw_table as a factory.
*/
template<typename Iter>
[[deprecated("Use table() or raw_table() factory.")]] stream_to(
transaction_base &, std::string_view table_name, Iter columns_begin,
Iter columns_end);
~stream_to() noexcept;
/// Does this stream still need to @ref complete()?
[[nodiscard]] constexpr operator bool() const noexcept
{
return not m_finished;
}
/// Has this stream been through its concluding @c complete()?
[[nodiscard]] constexpr bool operator!() const noexcept
{
return m_finished;
}
/// Complete the operation, and check for errors.
/** Always call this to close the stream in an orderly fashion, even after
* an error. (In the case of an error, abort the transaction afterwards.)
*
* The only circumstance where it's safe to skip this is after an error, if
* you're discarding the entire connection.
*/
void complete();
/// Insert a row of data.
/** Returns a reference to the stream, so you can chain the calls.
*
* The @c row can be a tuple, or any type that can be iterated. Each
* item becomes a field in the row, in the same order as the columns you
* specified when creating the stream.
*
* If you don't already happen to have your fields in the form of a tuple or
* container, prefer @c write_values. It's faster and more convenient.
*/
template<typename Row> stream_to &operator<<(Row const &row)
{
write_row(row);
return *this;
}
/// Stream a `stream_from` straight into a `stream_to`.
/** This can be useful when copying between different databases. If the
* source and the destination are on the same database, you'll get better
* performance doing it all in a regular query.
*/
stream_to &operator<<(stream_from &);
/// Insert a row of data, given in the form of a @c std::tuple or container.
/** The @c row can be a tuple, or any type that can be iterated. Each
* item becomes a field in the row, in the same order as the columns you
* specified when creating the stream.
*
* The preferred way to insert a row is @c write_values.
*/
template<typename Row> void write_row(Row const &row)
{
fill_buffer(row);
write_buffer();
}
/// Insert values as a row.
/** This is the recommended way of inserting data. Pass your field values,
* of any convertible type.
*/
template<typename... Ts> void write_values(Ts const &...fields)
{
fill_buffer(fields...);
write_buffer();
}
private:
/// Stream a pre-quoted table name and columns list.
stream_to(
transaction_base &tx, std::string_view path, std::string_view columns);
bool m_finished = false;
/// Reusable buffer for a row. Saves doing an allocation for each row.
std::string m_buffer;
/// Reusable buffer for converting/escaping a field.
std::string m_field_buf;
/// Glyph scanner, for parsing the client encoding.
internal::glyph_scanner_func *m_scanner;
/// Write a row of raw text-format data into the destination table.
void write_raw_line(std::string_view);
/// Write a row of data from @c m_buffer into the destination table.
/** Resets the buffer for the next row.
*/
void write_buffer();
/// COPY encoding for a null field, plus subsequent separator.
static constexpr std::string_view null_field{"\\N\t"};
/// Estimate buffer space needed for a field which is always null.
template<typename T>
static std::enable_if_t<nullness<T>::always_null, std::size_t>
estimate_buffer(T const &)
{
return std::size(null_field);
}
/// Estimate buffer space needed for field f.
/** The estimate is not very precise. We don't actually know how much space
* we'll need once the escaping comes in.
*/
template<typename T>
static std::enable_if_t<not nullness<T>::always_null, std::size_t>
estimate_buffer(T const &field)
{
return is_null(field) ? std::size(null_field) : size_buffer(field);
}
/// Append escaped version of @c data to @c m_buffer, plus a tab.
void escape_field_to_buffer(std::string_view data);
/// Append string representation for @c f to @c m_buffer.
/** This is for the general case, where the field may contain a value.
*
* Also appends a tab. The tab is meant to be a separator, not a terminator,
* so if you write any fields at all, you'll end up with one tab too many
* at the end of the buffer.
*/
template<typename Field>
std::enable_if_t<not nullness<Field>::always_null>
append_to_buffer(Field const &f)
{
// We append each field, terminated by a tab. That will leave us with
// one tab too many, assuming we write any fields at all; we remove that
// at the end.
if (is_null(f))
{
// Easy. Append null and tab in one go.
m_buffer.append(null_field);
}
else
{
// Convert f into m_buffer.
using traits = string_traits<Field>;
auto const budget{estimate_buffer(f)};
auto const offset{std::size(m_buffer)};
if constexpr (std::is_arithmetic_v<Field>)
{
// Specially optimised for "safe" types, which never need any
// escaping. Convert straight into m_buffer.
// The budget we get from size_buffer() includes room for the trailing
// zero, which we must remove. But we're also inserting tabs between
// fields, so we re-purpose the extra byte for that.
auto const total{offset + budget};
m_buffer.resize(total);
auto const data{m_buffer.data()};
char *const end{traits::into_buf(data + offset, data + total, f)};
*(end - 1) = '\t';
// Shrink to fit. Keep the tab though.
m_buffer.resize(static_cast<std::size_t>(end - data));
}
else if constexpr (
std::is_same_v<Field, std::string> or
std::is_same_v<Field, std::string_view> or
std::is_same_v<Field, zview>)
{
// This string may need escaping.
m_field_buf.resize(budget);
escape_field_to_buffer(f);
}
else
{
// This field needs to be converted to a string, and after that,
// escaped as well.
m_field_buf.resize(budget);
auto const data{m_field_buf.data()};
escape_field_to_buffer(
traits::to_buf(data, data + std::size(m_field_buf), f));
}
}
}
/// Append string representation for a null field to @c m_buffer.
/** This special case is for types which are always null.
*
* Also appends a tab. The tab is meant to be a separator, not a terminator,
* so if you write any fields at all, you'll end up with one tab too many
* at the end of the buffer.
*/
template<typename Field>
std::enable_if_t<nullness<Field>::always_null>
append_to_buffer(Field const &)
{
m_buffer.append(null_field);
}
/// Write raw COPY line into @c m_buffer, based on a container of fields.
template<typename Container>
std::enable_if_t<not std::is_same_v<typename Container::value_type, char>>
fill_buffer(Container const &c)
{
// To avoid unnecessary allocations and deallocations, we run through c
// twice: once to determine how much buffer space we may need, and once to
// actually write it into the buffer.
std::size_t budget{0};
for (auto const &f : c) budget += estimate_buffer(f);
m_buffer.reserve(budget);
for (auto const &f : c) append_to_buffer(f);
}
/// Estimate how many buffer bytes we need to write tuple.
template<typename Tuple, std::size_t... indexes>
static std::size_t
budget_tuple(Tuple const &t, std::index_sequence<indexes...>)
{
return (estimate_buffer(std::get<indexes>(t)) + ...);
}
/// Write tuple of fields to @c m_buffer.
template<typename Tuple, std::size_t... indexes>
void append_tuple(Tuple const &t, std::index_sequence<indexes...>)
{
(append_to_buffer(std::get<indexes>(t)), ...);
}
/// Write raw COPY line into @c m_buffer, based on a tuple of fields.
template<typename... Elts> void fill_buffer(std::tuple<Elts...> const &t)
{
using indexes = std::make_index_sequence<sizeof...(Elts)>;
m_buffer.reserve(budget_tuple(t, indexes{}));
append_tuple(t, indexes{});
}
/// Write raw COPY line into @c m_buffer, based on varargs fields.
template<typename... Ts> void fill_buffer(const Ts &...fields)
{
(..., append_to_buffer(fields));
}
constexpr static std::string_view s_classname{"stream_to"};
};
template<typename Columns>
inline stream_to::stream_to(
transaction_base &tx, std::string_view table_name, Columns const &columns) :
stream_to{tx, table_name, std::begin(columns), std::end(columns)}
{}
template<typename Iter>
inline stream_to::stream_to(
transaction_base &tx, std::string_view table_name, Iter columns_begin,
Iter columns_end) :
stream_to{
tx,
tx.quote_name(
table_name,
separated_list(",", columns_begin, columns_end, [&tx](auto col) {
return tx.quote_name(*col);
}))}
{}
} // namespace pqxx
#endif

Some files were not shown because too many files have changed in this diff Show More