From e5fbd94650d987e273e5dbd94d4d9727d5ea8df1 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sun, 22 Dec 2024 23:56:15 +0100 Subject: [PATCH 01/64] Bump to 8.0.0 in preparation. --- NEWS | 1 + VERSION | 2 +- configure | 22 +++++++++++----------- include/pqxx/doc/mainpage.md | 2 +- include/pqxx/version.hxx | 10 +++++----- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/NEWS b/NEWS index b61940d63..fe335ae49 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,4 @@ +8.0.0 7.10.1 - Fix string conversion buffer budget for arrays containing nulls. (#921) - Remove `-fanalyzer` option again; gcc is still broken. diff --git a/VERSION b/VERSION index d6e2f7b0a..ae9a76b92 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.10.1 +8.0.0 diff --git a/configure b/configure index 866092d59..a279c9091 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for libpqxx 7.10.1. +# Generated by GNU Autoconf 2.71 for libpqxx 8.0.0. # # Report bugs to . # @@ -621,8 +621,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='libpqxx' PACKAGE_TARNAME='libpqxx' -PACKAGE_VERSION='7.10.1' -PACKAGE_STRING='libpqxx 7.10.1' +PACKAGE_VERSION='8.0.0' +PACKAGE_STRING='libpqxx 8.0.0' PACKAGE_BUGREPORT='Jeroen T. Vermeulen' PACKAGE_URL='' @@ -1381,7 +1381,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures libpqxx 7.10.1 to adapt to many kinds of systems. +\`configure' configures libpqxx 8.0.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1452,7 +1452,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of libpqxx 7.10.1:";; + short | recursive ) echo "Configuration of libpqxx 8.0.0:";; esac cat <<\_ACEOF @@ -1576,7 +1576,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -libpqxx configure 7.10.1 +libpqxx configure 8.0.0 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -1951,7 +1951,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by libpqxx $as_me 7.10.1, which was +It was created by libpqxx $as_me 8.0.0, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3446,7 +3446,7 @@ fi # Define the identity of the package. PACKAGE='libpqxx' - VERSION='7.10.1' + VERSION='8.0.0' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -3550,7 +3550,7 @@ END fi -PQXX_ABI=7.10 +PQXX_ABI=8.0 PQXXVERSION=$PACKAGE_VERSION @@ -19328,7 +19328,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by libpqxx $as_me 7.10.1, which was +This file was extended by libpqxx $as_me 8.0.0, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -19396,7 +19396,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -libpqxx config.status 7.10.1 +libpqxx config.status 8.0.0 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/include/pqxx/doc/mainpage.md b/include/pqxx/doc/mainpage.md index 855304b59..2ce0675f8 100644 --- a/include/pqxx/doc/mainpage.md +++ b/include/pqxx/doc/mainpage.md @@ -1,7 +1,7 @@ libpqxx {#mainpage} ======= -@version 7.10.1 +@version 8.0.0 @author Jeroen T. Vermeulen @see https://pqxx.org/libpqxx/ @see https://github.com/jtv/libpqxx diff --git a/include/pqxx/version.hxx b/include/pqxx/version.hxx index d0bf552c1..3d4f57969 100644 --- a/include/pqxx/version.hxx +++ b/include/pqxx/version.hxx @@ -16,16 +16,16 @@ # endif /// Full libpqxx version string. -# define PQXX_VERSION "7.10.1" +# define PQXX_VERSION "8.0.0" /// Library ABI version. -# define PQXX_ABI "7.10" +# define PQXX_ABI "8.0" /// Major version number. -# define PQXX_VERSION_MAJOR 7 +# define PQXX_VERSION_MAJOR 8 /// Minor version number. -# define PQXX_VERSION_MINOR 10 +# define PQXX_VERSION_MINOR 0 -# define PQXX_VERSION_CHECK check_pqxx_version_7_10 +# define PQXX_VERSION_CHECK check_pqxx_version_8_0 namespace pqxx::internal { From d275085f1907424d97321f962ebd19d8137ccfd7 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 01:06:13 +0100 Subject: [PATCH 02/64] Retire check for integral `` conversions. --- NEWS | 2 + cmake/pqxx_cxx_feature_checks.cmake | 4 - config-tests/PQXX_HAVE_CHARCONV_INT.cxx | 16 --- configitems | 1 - configure | 50 ------- include/pqxx/config.h.in | 3 - pqxx_cxx_feature_checks.ac | 10 -- src/strconv.cxx | 173 +----------------------- 8 files changed, 4 insertions(+), 255 deletions(-) delete mode 100644 config-tests/PQXX_HAVE_CHARCONV_INT.cxx diff --git a/NEWS b/NEWS index fe335ae49..8d5c4062e 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ 8.0.0 + - C++20 is now the oldest C++ version that libpqxx supports. + - Compiler must support integral conversions in `charconv`. 7.10.1 - Fix string conversion buffer budget for arrays containing nulls. (#921) - Remove `-fanalyzer` option again; gcc is still broken. diff --git a/cmake/pqxx_cxx_feature_checks.cmake b/cmake/pqxx_cxx_feature_checks.cmake index ac51729d3..4b498620a 100644 --- a/cmake/pqxx_cxx_feature_checks.cmake +++ b/cmake/pqxx_cxx_feature_checks.cmake @@ -7,10 +7,6 @@ try_compile( PQXX_HAVE_CHARCONV_FLOAT ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_CHARCONV_FLOAT.cxx ) -try_compile( - PQXX_HAVE_CHARCONV_INT ${PROJECT_BINARY_DIR} - SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_CHARCONV_INT.cxx -) try_compile( PQXX_HAVE_CMP ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_CMP.cxx diff --git a/config-tests/PQXX_HAVE_CHARCONV_INT.cxx b/config-tests/PQXX_HAVE_CHARCONV_INT.cxx deleted file mode 100644 index 076ee0de3..000000000 --- a/config-tests/PQXX_HAVE_CHARCONV_INT.cxx +++ /dev/null @@ -1,16 +0,0 @@ -// Test for std::to_string/std::from_string for integral types. -#include -#include - -int main() -{ - char z[100]; - auto rt = std::to_chars(std::begin(z), std::end(z), 9ULL); - if (rt.ec != std::errc{}) - return 1; - unsigned long long n; - auto rf = std::from_chars(std::cbegin(z), std::cend(z), n); - if (rf.ec != std::errc{}) - return 2; - return (n == 9ULL) ? 0 : 1; -} diff --git a/configitems b/configitems index da97c35db..757e9015d 100644 --- a/configitems +++ b/configitems @@ -5,7 +5,6 @@ PACKAGE_STRING internal autotools PACKAGE_TARNAME internal autotools PACKAGE_VERSION internal autotools PQXX_HAVE_ASSUME public compiler -PQXX_HAVE_CHARCONV_INT internal compiler PQXX_HAVE_CHARCONV_FLOAT internal compiler PQXX_HAVE_CMP public compiler PQXX_HAVE_CONCEPTS public compiler diff --git a/configure b/configure index a279c9091..2a48b885d 100755 --- a/configure +++ b/configure @@ -17324,56 +17324,6 @@ fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_CHARCONV_FLOAT" >&5 printf "%s\n" "$PQXX_HAVE_CHARCONV_FLOAT" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_CHARCONV_INT" >&5 -printf %s "checking PQXX_HAVE_CHARCONV_INT... " >&6; } -PQXX_HAVE_CHARCONV_INT=yes -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -// Test for std::to_string/std::from_string for integral types. - -#include - -#include - - - -int main() - -{ - - char z[100]; - - auto rt = std::to_chars(std::begin(z), std::end(z), 9ULL); - - if (rt.ec != std::errc{}) - - return 1; - - unsigned long long n; - - auto rf = std::from_chars(std::cbegin(z), std::cend(z), n); - - if (rf.ec != std::errc{}) - - return 2; - - return (n == 9ULL) ? 0 : 1; - -} - - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO" -then : - -printf "%s\n" "#define PQXX_HAVE_CHARCONV_INT 1" >>confdefs.h - -else $as_nop - PQXX_HAVE_CHARCONV_INT=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_CHARCONV_INT" >&5 -printf "%s\n" "$PQXX_HAVE_CHARCONV_INT" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_CMP" >&5 printf %s "checking PQXX_HAVE_CMP... " >&6; } PQXX_HAVE_CMP=yes diff --git a/include/pqxx/config.h.in b/include/pqxx/config.h.in index fdca38cf9..fa6d215d3 100644 --- a/include/pqxx/config.h.in +++ b/include/pqxx/config.h.in @@ -63,9 +63,6 @@ /* Define if this feature is available. */ #undef PQXX_HAVE_CHARCONV_FLOAT -/* Define if this feature is available. */ -#undef PQXX_HAVE_CHARCONV_INT - /* Define if this feature is available. */ #undef PQXX_HAVE_CMP diff --git a/pqxx_cxx_feature_checks.ac b/pqxx_cxx_feature_checks.ac index 325608f29..7e97e32f2 100644 --- a/pqxx_cxx_feature_checks.ac +++ b/pqxx_cxx_feature_checks.ac @@ -19,16 +19,6 @@ AC_COMPILE_IFELSE( [Define if this feature is available.]), PQXX_HAVE_CHARCONV_FLOAT=no) AC_MSG_RESULT($PQXX_HAVE_CHARCONV_FLOAT) -AC_MSG_CHECKING([PQXX_HAVE_CHARCONV_INT]) -PQXX_HAVE_CHARCONV_INT=yes -AC_COMPILE_IFELSE( - [read_test(PQXX_HAVE_CHARCONV_INT.cxx)], - AC_DEFINE( - [PQXX_HAVE_CHARCONV_INT], - 1, - [Define if this feature is available.]), - PQXX_HAVE_CHARCONV_INT=no) -AC_MSG_RESULT($PQXX_HAVE_CHARCONV_INT) AC_MSG_CHECKING([PQXX_HAVE_CMP]) PQXX_HAVE_CMP=yes AC_COMPILE_IFELSE( diff --git a/src/strconv.cxx b/src/strconv.cxx index b460445ee..7fe7fba2d 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -118,11 +118,9 @@ template constexpr inline char *bottom_to_buf(char *end) } -#if defined(PQXX_HAVE_CHARCONV_INT) || defined(PQXX_HAVE_CHARCONV_FLOAT) /// Call to_chars, report errors as exceptions, add zero, return pointer. template -[[maybe_unused]] inline char * -wrap_to_chars(char *begin, char *end, T const &value) +inline char *wrap_to_chars(char *begin, char *end, T const &value) { auto res{std::to_chars(begin, end - 1, value)}; if (res.ec != std::errc()) @@ -144,7 +142,6 @@ wrap_to_chars(char *begin, char *end, T const &value) *res.ptr++ = '\0'; return res.ptr; } -#endif } // namespace @@ -196,13 +193,9 @@ template zview integral_traits::to_buf( template char *integral_traits::into_buf(char *begin, char *end, T const &value) { -#if defined(PQXX_HAVE_CHARCONV_INT) // This is exactly what to_chars is good at. Trust standard library // implementers to optimise better than we can. return wrap_to_chars(begin, end, value); -#else - return generic_into_buf(begin, end, value); -#endif } @@ -289,9 +282,7 @@ std::string PQXX_COLD state_buffer_overrun(int have_bytes, int need_bytes) namespace { -#if defined(PQXX_HAVE_CHARCONV_INT) || defined(PQXX_HAVE_CHARCONV_FLOAT) -template -[[maybe_unused]] inline TYPE from_string_arithmetic(std::string_view in) +template inline TYPE from_string_arithmetic(std::string_view in) { char const *here{std::data(in)}; auto const end{std::data(in) + std::size(in)}; @@ -332,162 +323,6 @@ template else throw pqxx::conversion_error{base + ": " + msg}; } -#endif -} // namespace - - -namespace -{ -#if !defined(PQXX_HAVE_CHARCONV_INT) -[[noreturn, maybe_unused]] void PQXX_COLD report_overflow() -{ - throw pqxx::conversion_error{ - "Could not convert string to integer: value out of range."}; -} - -template struct numeric_ten -{ - static inline constexpr T value = 10; -}; - -template struct numeric_high_threshold -{ - static inline constexpr T value = - (std::numeric_limits::max)() / numeric_ten::value; -}; - -template struct numeric_low_threshold -{ - static inline constexpr T value = - (std::numeric_limits::min)() / numeric_ten::value; -}; - -/// Return 10*n, or throw exception if it overflows. -template -[[maybe_unused]] constexpr inline T safe_multiply_by_ten(T n) -{ - using limits = std::numeric_limits; - - if (n > numeric_high_threshold::value) - PQXX_UNLIKELY - report_overflow(); - if constexpr (limits::is_signed) - { - if (numeric_low_threshold::value > n) - PQXX_UNLIKELY - report_overflow(); - } - return T(n * numeric_ten::value); -} - - -/// Add digit d to nonnegative n, or throw exception if it overflows. -template -[[maybe_unused]] constexpr inline T safe_add_digit(T n, T d) -{ - T const high_threshold{static_cast(std::numeric_limits::max() - d)}; - if (n > high_threshold) - PQXX_UNLIKELY - report_overflow(); - return static_cast(n + d); -} - - -/// Subtract digit d to nonpositive n, or throw exception if it overflows. -template -[[maybe_unused]] constexpr inline T safe_sub_digit(T n, T d) -{ - T const low_threshold{static_cast(std::numeric_limits::min() + d)}; - if (n < low_threshold) - PQXX_UNLIKELY - report_overflow(); - return static_cast(n - d); -} - - -/// For use in string parsing: add new numeric digit to intermediate value. -template -[[maybe_unused]] constexpr inline L absorb_digit_positive(L value, R digit) -{ - return safe_add_digit(safe_multiply_by_ten(value), L(digit)); -} - - -/// For use in string parsing: subtract digit from intermediate value. -template -[[maybe_unused]] constexpr inline L absorb_digit_negative(L value, R digit) -{ - return safe_sub_digit(safe_multiply_by_ten(value), L(digit)); -} - - -template -[[maybe_unused]] constexpr T from_string_integer(std::string_view text) -{ - if (std::size(text) == 0) - throw pqxx::conversion_error{ - "Attempt to convert empty string to " + pqxx::type_name + "."}; - - char const *const data{std::data(text)}; - std::size_t i{0}; - - // Skip whitespace. This is not the proper way to do it, but I see no way - // that any of the supported encodings could ever produce a valid character - // whose byte sequence would confuse this code. - // - // Why skip whitespace? Because that's how integral conversions are meant to - // work _for composite types._ I see no clean way to support leading - // whitespace there without putting the code in here. A shame about the - // overhead, modest as it is, for the normal case. - for (; i < std::size(text) and (data[i] == ' ' or data[i] == '\t'); ++i); - if (i == std::size(text)) - throw pqxx::conversion_error{ - "Converting string to " + pqxx::type_name + - ", but it contains only whitespace."}; - - char const initial{data[i]}; - T result{0}; - - if (pqxx::internal::is_digit(initial)) - { - for (; pqxx::internal::is_digit(data[i]); ++i) - result = absorb_digit_positive( - result, pqxx::internal::digit_to_number(data[i])); - } - else if (initial == '-') - { - if constexpr (not std::is_signed_v) - throw pqxx::conversion_error{ - "Attempt to convert negative value to " + pqxx::type_name + "."}; - - ++i; - if (i >= std::size(text)) - throw pqxx::conversion_error{ - "Converting string to " + pqxx::type_name + - ", but it contains only a sign."}; - for (; i < std::size(text) and pqxx::internal::is_digit(data[i]); ++i) - result = absorb_digit_negative( - result, pqxx::internal::digit_to_number(data[i])); - } - else - { - throw pqxx::conversion_error{ - "Could not convert string to " + pqxx::type_name + - ": " - "'" + - std::string{text} + "'."}; - } - - if (i < std::size(text)) - throw pqxx::conversion_error{ - "Unexpected text after " + pqxx::type_name + - ": " - "'" + - std::string{text} + "'."}; - - return result; -} -#endif // !PQXX_HAVE_CHARCONV_INT } // namespace @@ -711,11 +546,7 @@ namespace pqxx::internal { template T integral_traits::from_string(std::string_view text) { -#if defined(PQXX_HAVE_CHARCONV_INT) return from_string_arithmetic(text); -#else - return from_string_integer(text); -#endif } template short integral_traits::from_string(std::string_view); From 45008acc13ca06d5b5cd86e12187ccbcc7e62799 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 01:16:51 +0100 Subject: [PATCH 03/64] Retire checks for missing `std::cmp_less` etc. --- cmake/pqxx_cxx_feature_checks.cmake | 4 -- config-tests/PQXX_HAVE_CMP.cxx | 9 ---- configitems | 1 - configure | 36 --------------- include/pqxx/binarystring.hxx | 2 +- include/pqxx/config.h.in | 3 -- include/pqxx/internal/conversions.hxx | 18 ++++---- include/pqxx/largeobject.hxx | 2 +- include/pqxx/util.hxx | 64 +-------------------------- pqxx_cxx_feature_checks.ac | 10 ----- src/largeobject.cxx | 2 +- test/test_types.hxx | 2 +- test/unit/test_errorhandler.cxx | 2 +- 13 files changed, 15 insertions(+), 140 deletions(-) delete mode 100644 config-tests/PQXX_HAVE_CMP.cxx diff --git a/cmake/pqxx_cxx_feature_checks.cmake b/cmake/pqxx_cxx_feature_checks.cmake index 4b498620a..8fc7aa668 100644 --- a/cmake/pqxx_cxx_feature_checks.cmake +++ b/cmake/pqxx_cxx_feature_checks.cmake @@ -7,10 +7,6 @@ try_compile( PQXX_HAVE_CHARCONV_FLOAT ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_CHARCONV_FLOAT.cxx ) -try_compile( - PQXX_HAVE_CMP ${PROJECT_BINARY_DIR} - SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_CMP.cxx -) try_compile( PQXX_HAVE_CONCEPTS ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_CONCEPTS.cxx diff --git a/config-tests/PQXX_HAVE_CMP.cxx b/config-tests/PQXX_HAVE_CMP.cxx deleted file mode 100644 index cd3f53b2b..000000000 --- a/config-tests/PQXX_HAVE_CMP.cxx +++ /dev/null @@ -1,9 +0,0 @@ -// Test for C++20 std::cmp_greater etc. support. -// C++20: Assume support. -#include - - -int main() -{ - return std::cmp_greater(-1, 2u) && std::cmp_less_equal(3, 0); -} diff --git a/configitems b/configitems index 757e9015d..0a85b9a24 100644 --- a/configitems +++ b/configitems @@ -6,7 +6,6 @@ PACKAGE_TARNAME internal autotools PACKAGE_VERSION internal autotools PQXX_HAVE_ASSUME public compiler PQXX_HAVE_CHARCONV_FLOAT internal compiler -PQXX_HAVE_CMP public compiler PQXX_HAVE_CONCEPTS public compiler PQXX_HAVE_CXA_DEMANGLE internal compiler PQXX_HAVE_GCC_PURE public compiler diff --git a/configure b/configure index 2a48b885d..2951c0f84 100755 --- a/configure +++ b/configure @@ -17324,42 +17324,6 @@ fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_CHARCONV_FLOAT" >&5 printf "%s\n" "$PQXX_HAVE_CHARCONV_FLOAT" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_CMP" >&5 -printf %s "checking PQXX_HAVE_CMP... " >&6; } -PQXX_HAVE_CMP=yes -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -// Test for C++20 std::cmp_greater etc. support. - -// C++20: Assume support. - -#include - - - - - -int main() - -{ - - return std::cmp_greater(-1, 2u) && std::cmp_less_equal(3, 0); - -} - - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO" -then : - -printf "%s\n" "#define PQXX_HAVE_CMP 1" >>confdefs.h - -else $as_nop - PQXX_HAVE_CMP=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_CMP" >&5 -printf "%s\n" "$PQXX_HAVE_CMP" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_CONCEPTS" >&5 printf %s "checking PQXX_HAVE_CONCEPTS... " >&6; } PQXX_HAVE_CONCEPTS=yes diff --git a/include/pqxx/binarystring.hxx b/include/pqxx/binarystring.hxx index ab3ebe8f9..fe113a42d 100644 --- a/include/pqxx/binarystring.hxx +++ b/include/pqxx/binarystring.hxx @@ -212,7 +212,7 @@ template<> struct string_traits static char *into_buf(char *begin, char *end, binarystring const &value) { auto const budget{size_buffer(value)}; - if (internal::cmp_less(end - begin, budget)) + if (std::cmp_less(end - begin, budget)) throw conversion_overrun{ "Not enough buffer space to escape binary data."}; std::string_view text{value.view()}; diff --git a/include/pqxx/config.h.in b/include/pqxx/config.h.in index fa6d215d3..c4bade597 100644 --- a/include/pqxx/config.h.in +++ b/include/pqxx/config.h.in @@ -63,9 +63,6 @@ /* Define if this feature is available. */ #undef PQXX_HAVE_CHARCONV_FLOAT -/* Define if this feature is available. */ -#undef PQXX_HAVE_CMP - /* Define if this feature is available. */ #undef PQXX_HAVE_CONCEPTS diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index 462c2d5ce..57bc370e9 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -98,7 +98,7 @@ inline char *generic_into_buf(char *begin, char *end, T const &value) auto const space{end - begin}; // Include the trailing zero. auto const len = std::size(text) + 1; - if (internal::cmp_greater(len, space)) + if (std::cmp_greater(len, space)) throw conversion_overrun{ "Not enough buffer space to insert " + type_name + ". " + state_buffer_overrun(space, len)}; @@ -586,7 +586,7 @@ template struct string_traits static char *into_buf(char *begin, char *end, char const (&value)[N]) { - if (internal::cmp_less(end - begin, size_buffer(value))) + if (std::cmp_less(end - begin, size_buffer(value))) throw conversion_overrun{ "Could not convert char[] to string: too long for buffer."}; std::memcpy(begin, value, N); @@ -618,7 +618,7 @@ template<> struct string_traits static char *into_buf(char *begin, char *end, std::string const &value) { - if (internal::cmp_greater_equal(std::size(value), end - begin)) + if (std::cmp_greater_equal(std::size(value), end - begin)) throw conversion_overrun{ "Could not convert string to string: too long for buffer."}; // Include the trailing zero. @@ -661,7 +661,7 @@ template<> struct string_traits static char *into_buf(char *begin, char *end, std::string_view const &value) { - if (internal::cmp_greater_equal(std::size(value), end - begin)) + if (std::cmp_greater_equal(std::size(value), end - begin)) throw conversion_overrun{ "Could not store string_view: too long for buffer."}; value.copy(begin, std::size(value)); @@ -700,7 +700,7 @@ template<> struct string_traits static char *into_buf(char *begin, char *end, zview const &value) { auto const size{std::size(value)}; - if (internal::cmp_less_equal(end - begin, std::size(value))) + if (std::cmp_less_equal(end - begin, std::size(value))) throw conversion_overrun{"Not enough buffer space to store this zview."}; value.copy(begin, size); begin[size] = '\0'; @@ -928,7 +928,7 @@ template struct string_traits static char *into_buf(char *begin, char *end, DATA const &value) { auto const budget{size_buffer(value)}; - if (internal::cmp_less(end - begin, budget)) + if (std::cmp_less(end - begin, budget)) throw conversion_overrun{ "Not enough buffer space to escape binary data."}; internal::esc_bin(value, begin); @@ -965,7 +965,7 @@ template<> struct string_traits static char *into_buf(char *begin, char *end, bytes const &value) { auto const budget{size_buffer(value)}; - if (internal::cmp_less(end - begin, budget)) + if (std::cmp_less(end - begin, budget)) throw conversion_overrun{ "Not enough buffer space to escape binary data."}; internal::esc_bin(value, begin); @@ -1011,7 +1011,7 @@ template<> struct string_traits static char *into_buf(char *begin, char *end, bytes_view const &value) { auto const budget{size_buffer(value)}; - if (internal::cmp_less(end - begin, budget)) + if (std::cmp_less(end - begin, budget)) throw conversion_overrun{ "Not enough buffer space to escape binary data."}; internal::esc_bin(value, begin); @@ -1052,7 +1052,7 @@ public: { assert(begin <= end); std::size_t const budget{size_buffer(value)}; - if (internal::cmp_less(end - begin, budget)) + if (std::cmp_less(end - begin, budget)) throw conversion_overrun{ "Not enough buffer space to convert array to string."}; diff --git a/include/pqxx/largeobject.hxx b/include/pqxx/largeobject.hxx index 6439826b6..379157a64 100644 --- a/include/pqxx/largeobject.hxx +++ b/include/pqxx/largeobject.hxx @@ -447,7 +447,7 @@ protected: auto const write_sz{pp - pb}; auto const written_sz{ m_obj.cwrite(pb, static_cast(pp - pb))}; - if (internal::cmp_less_equal(written_sz, 0)) + if (std::cmp_less_equal(written_sz, 0)) throw internal_error{ "pqxx::largeobject: write failed " "(is transaction still valid on write or flush?), " diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index 5ed0496b3..acfc873ce 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -58,68 +58,6 @@ namespace pqxx /// Internal items for libpqxx' own use. Do not use these yourself. namespace pqxx::internal { - -// C++20: Retire wrapper. -/// Same as `std::cmp_less`, or a workaround where that's not available. -template -inline constexpr bool cmp_less(LEFT lhs, RIGHT rhs) noexcept -{ -#if defined(PQXX_HAVE_CMP) - return std::cmp_less(lhs, rhs); -#else - // We need a variable just because lgtm.com gives off a false positive - // warning when we compare the values directly. It considers that a - // "self-comparison." - constexpr bool left_signed{std::is_signed_v}; - if constexpr (left_signed == std::is_signed_v) - return lhs < rhs; - else if constexpr (std::is_signed_v) - return (lhs <= 0) ? true : (std::make_unsigned_t(lhs) < rhs); - else - return (rhs <= 0) ? false : (lhs < std::make_unsigned_t(rhs)); -#endif -} - - -// C++20: Retire wrapper. -/// C++20 std::cmp_greater, or workaround if not available. -template -inline constexpr bool cmp_greater(LEFT lhs, RIGHT rhs) noexcept -{ -#if defined(PQXX_HAVE_CMP) - return std::cmp_greater(lhs, rhs); -#else - return cmp_less(rhs, lhs); -#endif -} - - -// C++20: Retire wrapper. -/// C++20 std::cmp_less_equal, or workaround if not available. -template -inline constexpr bool cmp_less_equal(LEFT lhs, RIGHT rhs) noexcept -{ -#if defined(PQXX_HAVE_CMP) - return std::cmp_less_equal(lhs, rhs); -#else - return not cmp_less(rhs, lhs); -#endif -} - - -// C++20: Retire wrapper. -/// C++20 std::cmp_greater_equal, or workaround if not available. -template -inline constexpr bool cmp_greater_equal(LEFT lhs, RIGHT rhs) noexcept -{ -#if defined(PQXX_HAVE_CMP) - return std::cmp_greater_equal(lhs, rhs); -#else - return not cmp_less(lhs, rhs); -#endif -} - - /// Efficiently concatenate two strings. /** This is a special case of concatenate(), needed because dependency * management does not let us use that function here. @@ -198,7 +136,7 @@ inline TO check_cast(FROM value, std::string_view description) constexpr auto to_max{static_cast((to_limits::max)())}; if constexpr (from_max > to_max) { - if (internal::cmp_greater(value, to_max)) + if (std::cmp_greater(value, to_max)) throw range_error{internal::cat2("Cast overflow: "sv, description)}; } } diff --git a/pqxx_cxx_feature_checks.ac b/pqxx_cxx_feature_checks.ac index 7e97e32f2..195ff71ca 100644 --- a/pqxx_cxx_feature_checks.ac +++ b/pqxx_cxx_feature_checks.ac @@ -19,16 +19,6 @@ AC_COMPILE_IFELSE( [Define if this feature is available.]), PQXX_HAVE_CHARCONV_FLOAT=no) AC_MSG_RESULT($PQXX_HAVE_CHARCONV_FLOAT) -AC_MSG_CHECKING([PQXX_HAVE_CMP]) -PQXX_HAVE_CMP=yes -AC_COMPILE_IFELSE( - [read_test(PQXX_HAVE_CMP.cxx)], - AC_DEFINE( - [PQXX_HAVE_CMP], - 1, - [Define if this feature is available.]), - PQXX_HAVE_CMP=no) -AC_MSG_RESULT($PQXX_HAVE_CMP) AC_MSG_CHECKING([PQXX_HAVE_CONCEPTS]) PQXX_HAVE_CONCEPTS=yes AC_COMPILE_IFELSE( diff --git a/src/largeobject.cxx b/src/largeobject.cxx index 4adecf72d..94572d466 100644 --- a/src/largeobject.cxx +++ b/src/largeobject.cxx @@ -237,7 +237,7 @@ pqxx::largeobjectaccess::write(char const buf[], std::size_t len) { if (id() == oid_none) throw usage_error{"No object selected."}; - if (auto const bytes{cwrite(buf, len)}; internal::cmp_less(bytes, len)) + if (auto const bytes{cwrite(buf, len)}; std::cmp_less(bytes, len)) { int const err{errno}; if (err == ENOMEM) diff --git a/test/test_types.hxx b/test/test_types.hxx index e655e7f1f..ebf53c94e 100644 --- a/test/test_types.hxx +++ b/test/test_types.hxx @@ -117,7 +117,7 @@ template<> struct string_traits static char *into_buf(char *begin, char *end, ipv4 const &value) { - if (pqxx::internal::cmp_less(end - begin, size_buffer(value))) + if (std::cmp_less(end - begin, size_buffer(value))) throw conversion_error{"Buffer too small for ipv4."}; char *here = begin; for (int i = 0; i < 4; ++i) diff --git a/test/unit/test_errorhandler.cxx b/test/unit/test_errorhandler.cxx index 9dc27a806..b6cd5d70a 100644 --- a/test/unit/test_errorhandler.cxx +++ b/test/unit/test_errorhandler.cxx @@ -63,7 +63,7 @@ template<> struct string_traits static char *into_buf(char *begin, char *end, TestErrorHandler *const &value) { std::string text{"TestErrorHandler at " + pqxx::to_string(value)}; - if (pqxx::internal::cmp_greater_equal(std::size(text), end - begin)) + if (std::cmp_greater_equal(std::size(text), end - begin)) throw conversion_overrun{"Not enough buffer for TestErrorHandler."}; std::memcpy(begin, text.c_str(), std::size(text) + 1); return begin + std::size(text) + 1; From a6572632cf8bc42feaaadb6d3c384fed1166c9d4 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 01:41:56 +0100 Subject: [PATCH 04/64] Assume support for `[[likely]]`/`[[unlikely]]`. --- cmake/pqxx_cxx_feature_checks.cmake | 4 - config-tests/PQXX_HAVE_LIKELY.cxx | 14 -- configitems | 1 - configure | 46 ---- include/pqxx/config.h.in | 3 - include/pqxx/field.hxx | 6 +- include/pqxx/internal/encodings.hxx | 220 ++++++++------------ include/pqxx/internal/header-pre.hxx | 10 - include/pqxx/internal/stream_query_impl.hxx | 4 +- include/pqxx/params.hxx | 3 +- include/pqxx/pipeline.hxx | 2 +- include/pqxx/stream_from.hxx | 8 +- include/pqxx/util.hxx | 4 +- pqxx_cxx_feature_checks.ac | 10 - src/array.cxx | 10 +- src/connection.cxx | 94 ++++----- src/cursor.cxx | 10 +- src/encodings.cxx | 43 ++-- src/pipeline.cxx | 32 ++- src/result.cxx | 19 +- src/robusttransaction.cxx | 12 +- src/strconv.cxx | 35 ++-- src/stream_from.cxx | 20 +- src/stream_to.cxx | 2 +- src/time.cxx | 16 +- src/transaction_base.cxx | 20 +- src/util.cxx | 17 +- 27 files changed, 239 insertions(+), 426 deletions(-) delete mode 100644 config-tests/PQXX_HAVE_LIKELY.cxx diff --git a/cmake/pqxx_cxx_feature_checks.cmake b/cmake/pqxx_cxx_feature_checks.cmake index 8fc7aa668..d9ac004c1 100644 --- a/cmake/pqxx_cxx_feature_checks.cmake +++ b/cmake/pqxx_cxx_feature_checks.cmake @@ -23,10 +23,6 @@ try_compile( PQXX_HAVE_GCC_VISIBILITY ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_GCC_VISIBILITY.cxx ) -try_compile( - PQXX_HAVE_LIKELY ${PROJECT_BINARY_DIR} - SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_LIKELY.cxx -) try_compile( PQXX_HAVE_MULTIDIM ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_MULTIDIM.cxx diff --git a/config-tests/PQXX_HAVE_LIKELY.cxx b/config-tests/PQXX_HAVE_LIKELY.cxx deleted file mode 100644 index 4e57e5e87..000000000 --- a/config-tests/PQXX_HAVE_LIKELY.cxx +++ /dev/null @@ -1,14 +0,0 @@ -// Test for C++20 [[likely]] and [[unlikely]] attributes. -// C++20: Assume support. - -#if !__has_cpp_attribute(likely) -# error "No support for [[likely]] / [[unlikely]] attributes." -#endif - -int foo(int i) -{ - if (i > 0) [[likely]] - return 100; - else - return 0; -} diff --git a/configitems b/configitems index 0a85b9a24..bbac7c1f2 100644 --- a/configitems +++ b/configitems @@ -11,7 +11,6 @@ PQXX_HAVE_CXA_DEMANGLE internal compiler PQXX_HAVE_GCC_PURE public compiler PQXX_HAVE_GCC_VISIBILITY public compiler PQXX_HAVE_MULTIDIM public compiler -PQXX_HAVE_LIKELY public compiler PQXX_HAVE_PATH public compiler PQXX_HAVE_POLL internal compiler PQXX_HAVE_SLEEP_FOR internal compiler diff --git a/configure b/configure index 2951c0f84..57860a64a 100755 --- a/configure +++ b/configure @@ -17500,52 +17500,6 @@ fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_GCC_VISIBILITY" >&5 printf "%s\n" "$PQXX_HAVE_GCC_VISIBILITY" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_LIKELY" >&5 -printf %s "checking PQXX_HAVE_LIKELY... " >&6; } -PQXX_HAVE_LIKELY=yes -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -// Test for C++20 [[likely]] and [[unlikely]] attributes. - -// C++20: Assume support. - - - -#if !__has_cpp_attribute(likely) - -# error "No support for [[likely]] / [[unlikely]] attributes." - -#endif - - - -int foo(int i) - -{ - - if (i > 0) [[likely]] - - return 100; - - else - - return 0; - -} - - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO" -then : - -printf "%s\n" "#define PQXX_HAVE_LIKELY 1" >>confdefs.h - -else $as_nop - PQXX_HAVE_LIKELY=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_LIKELY" >&5 -printf "%s\n" "$PQXX_HAVE_LIKELY" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_MULTIDIM" >&5 printf %s "checking PQXX_HAVE_MULTIDIM... " >&6; } PQXX_HAVE_MULTIDIM=yes diff --git a/include/pqxx/config.h.in b/include/pqxx/config.h.in index c4bade597..26253f1a3 100644 --- a/include/pqxx/config.h.in +++ b/include/pqxx/config.h.in @@ -75,9 +75,6 @@ /* Define if this feature is available. */ #undef PQXX_HAVE_GCC_VISIBILITY -/* Define if this feature is available. */ -#undef PQXX_HAVE_LIKELY - /* Define if this feature is available. */ #undef PQXX_HAVE_MULTIDIM diff --git a/include/pqxx/field.hxx b/include/pqxx/field.hxx index 22475cc8f..84f645611 100644 --- a/include/pqxx/field.hxx +++ b/include/pqxx/field.hxx @@ -374,8 +374,7 @@ inline bool field::to( template<> inline std::string_view field::as() const { if (is_null()) - PQXX_UNLIKELY - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name); return view(); } @@ -412,8 +411,7 @@ inline bool field::to(zview &obj, zview const &default_value) const template<> inline zview field::as() const { if (is_null()) - PQXX_UNLIKELY - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name); return zview{c_str(), size()}; } diff --git a/include/pqxx/internal/encodings.hxx b/include/pqxx/internal/encodings.hxx index 04b8c1788..92aa341fc 100644 --- a/include/pqxx/internal/encodings.hxx +++ b/include/pqxx/internal/encodings.hxx @@ -235,8 +235,8 @@ template<> struct glyph_scanner call(char const /* buffer */[], std::size_t buffer_len, std::size_t start) { // TODO: Don't bother with npos. Let the caller check. - if (start >= buffer_len) - PQXX_UNLIKELY return std::string::npos; + if (start >= buffer_len) [[unlikely]] + return std::string::npos; else return start + 1; } @@ -249,23 +249,22 @@ template<> struct glyph_scanner static PQXX_PURE std::size_t call(char const buffer[], std::size_t buffer_len, std::size_t start) { - if (start >= buffer_len) - PQXX_UNLIKELY return std::string::npos; + if (start >= buffer_len) [[unlikely]] + return std::string::npos; auto const byte1{get_byte(buffer, start)}; if (byte1 < 0x80) return start + 1; if (not between_inc(byte1, 0x81, 0xfe) or (start + 2 > buffer_len)) - PQXX_UNLIKELY - throw_for_encoding_error("BIG5", buffer, start, 1); + [[unlikely]] + throw_for_encoding_error("BIG5", buffer, start, 1); auto const byte2{get_byte(buffer, start + 1)}; if ( not between_inc(byte2, 0x40, 0x7e) and - not between_inc(byte2, 0xa1, 0xfe)) - PQXX_UNLIKELY - throw_for_encoding_error("BIG5", buffer, start, 2); + not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] + throw_for_encoding_error("BIG5", buffer, start, 2); return start + 2; } @@ -297,13 +296,12 @@ template<> struct glyph_scanner return start + 1; if (not between_inc(byte1, 0xa1, 0xf7) or start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_CN", buffer, start, 1); + [[unlikely]] + throw_for_encoding_error("EUC_CN", buffer, start, 1); auto const byte2{get_byte(buffer, start + 1)}; - if (not between_inc(byte2, 0xa1, 0xfe)) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_CN", buffer, start, 2); + if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] + throw_for_encoding_error("EUC_CN", buffer, start, 2); return start + 2; } @@ -327,25 +325,22 @@ template<> struct glyph_scanner if (byte1 < 0x80) return start + 1; - if (start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_JP", buffer, start, 1); + if (start + 2 > buffer_len) [[unlikely]] + throw_for_encoding_error("EUC_JP", buffer, start, 1); auto const byte2{get_byte(buffer, start + 1)}; if (byte1 == 0x8e) { - if (not between_inc(byte2, 0xa1, 0xfe)) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_JP", buffer, start, 2); + if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] + throw_for_encoding_error("EUC_JP", buffer, start, 2); return start + 2; } if (between_inc(byte1, 0xa1, 0xfe)) { - if (not between_inc(byte2, 0xa1, 0xfe)) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_JP", buffer, start, 2); + if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] + throw_for_encoding_error("EUC_JP", buffer, start, 2); return start + 2; } @@ -355,9 +350,8 @@ template<> struct glyph_scanner auto const byte3{get_byte(buffer, start + 2)}; if ( not between_inc(byte2, 0xa1, 0xfe) or - not between_inc(byte3, 0xa1, 0xfe)) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_JP", buffer, start, 3); + not between_inc(byte3, 0xa1, 0xfe)) [[unlikely]] + throw_for_encoding_error("EUC_JP", buffer, start, 3); return start + 3; } @@ -373,21 +367,20 @@ template<> struct glyph_scanner static PQXX_PURE std::size_t call(char const buffer[], std::size_t buffer_len, std::size_t start) { - if (start >= buffer_len) - PQXX_UNLIKELY return std::string::npos; + if (start >= buffer_len) [[unlikely]] + return std::string::npos; auto const byte1{get_byte(buffer, start)}; if (byte1 < 0x80) return start + 1; if (not between_inc(byte1, 0xa1, 0xfe) or start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_KR", buffer, start, 1); + [[unlikely]] + throw_for_encoding_error("EUC_KR", buffer, start, 1); auto const byte2{get_byte(buffer, start + 1)}; - if (not between_inc(byte2, 0xa1, 0xfe)) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_KR", buffer, start, 1); + if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] + throw_for_encoding_error("EUC_KR", buffer, start, 1); return start + 2; } @@ -400,31 +393,27 @@ template<> struct glyph_scanner static PQXX_PURE std::size_t call(char const buffer[], std::size_t buffer_len, std::size_t start) { - if (start >= buffer_len) - PQXX_UNLIKELY - return std::string::npos; + if (start >= buffer_len) [[unlikely]] + return std::string::npos; auto const byte1{get_byte(buffer, start)}; if (byte1 < 0x80) return start + 1; - if (start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_KR", buffer, start, 1); + if (start + 2 > buffer_len) [[unlikely]] + throw_for_encoding_error("EUC_KR", buffer, start, 1); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0xa1, 0xfe)) { - if (not between_inc(byte2, 0xa1, 0xfe)) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_KR", buffer, start, 2); + if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] + throw_for_encoding_error("EUC_KR", buffer, start, 2); return start + 2; } - if (byte1 != 0x8e or start + 4 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("EUC_KR", buffer, start, 1); + if (byte1 != 0x8e or start + 4 > buffer_len) [[unlikely]] + throw_for_encoding_error("EUC_KR", buffer, start, 1); if ( between_inc(byte2, 0xa1, 0xb0) and @@ -432,8 +421,7 @@ template<> struct glyph_scanner between_inc(get_byte(buffer, start + 3), 0xa1, 0xfe)) return start + 4; - PQXX_UNLIKELY - throw_for_encoding_error("EUC_KR", buffer, start, 4); + [[unlikely]] throw_for_encoding_error("EUC_KR", buffer, start, 4); } }; @@ -444,8 +432,8 @@ template<> struct glyph_scanner static PQXX_PURE std::size_t call(char const buffer[], std::size_t buffer_len, std::size_t start) { - if (start >= buffer_len) - PQXX_UNLIKELY return std::string::npos; + if (start >= buffer_len) [[unlikely]] + return std::string::npos; auto const byte1{get_byte(buffer, start)}; if (byte1 < 0x80) @@ -453,23 +441,20 @@ template<> struct glyph_scanner if (byte1 == 0x80) throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); - if (start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); + if (start + 2 > buffer_len) [[unlikely]] + throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte2, 0x40, 0xfe)) { - if (byte2 == 0x7f) - PQXX_UNLIKELY - throw_for_encoding_error("GB18030", buffer, start, 2); + if (byte2 == 0x7f) [[unlikely]] + throw_for_encoding_error("GB18030", buffer, start, 2); return start + 2; } - if (start + 4 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); + if (start + 4 > buffer_len) [[unlikely]] + throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); if ( between_inc(byte2, 0x30, 0x39) and @@ -477,8 +462,7 @@ template<> struct glyph_scanner between_inc(get_byte(buffer, start + 3), 0x30, 0x39)) return start + 4; - PQXX_UNLIKELY - throw_for_encoding_error("GB18030", buffer, start, 4); + [[unlikely]] throw_for_encoding_error("GB18030", buffer, start, 4); } }; @@ -489,16 +473,15 @@ template<> struct glyph_scanner static PQXX_PURE std::size_t call(char const buffer[], std::size_t buffer_len, std::size_t start) { - if (start >= buffer_len) - PQXX_UNLIKELY return std::string::npos; + if (start >= buffer_len) [[unlikely]] + return std::string::npos; auto const byte1{get_byte(buffer, start)}; if (byte1 < 0x80) return start + 1; - if (start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("GBK", buffer, start, 1); + if (start + 2 > buffer_len) [[unlikely]] + throw_for_encoding_error("GBK", buffer, start, 1); auto const byte2{get_byte(buffer, start + 1)}; if ( @@ -516,8 +499,7 @@ template<> struct glyph_scanner byte2 != 0x7f)) return start + 2; - PQXX_UNLIKELY - throw_for_encoding_error("GBK", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("GBK", buffer, start, 2); } }; @@ -536,16 +518,15 @@ template<> struct glyph_scanner static PQXX_PURE std::size_t call(char const buffer[], std::size_t buffer_len, std::size_t start) { - if (start >= buffer_len) - PQXX_UNLIKELY return std::string::npos; + if (start >= buffer_len) [[unlikely]] + return std::string::npos; auto const byte1{get_byte(buffer, start)}; if (byte1 < 0x80) return start + 1; - if (start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("JOHAB", buffer, start, 1); + if (start + 2 > buffer_len) [[unlikely]] + throw_for_encoding_error("JOHAB", buffer, start, 1); auto const byte2{get_byte(buffer, start)}; if ( @@ -555,8 +536,7 @@ template<> struct glyph_scanner (between_inc(byte2, 0x31, 0x7e) or between_inc(byte2, 0x91, 0xfe)))) return start + 2; - PQXX_UNLIKELY - throw_for_encoding_error("JOHAB", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("JOHAB", buffer, start, 2); } }; @@ -573,24 +553,22 @@ template<> struct glyph_scanner static PQXX_PURE std::size_t call(char const buffer[], std::size_t buffer_len, std::size_t start) { - if (start >= buffer_len) - PQXX_UNLIKELY return std::string::npos; + if (start >= buffer_len) [[unlikely]] + return std::string::npos; auto const byte1{get_byte(buffer, start)}; if (byte1 < 0x80) return start + 1; - if (start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("MULE_INTERNAL", buffer, start, 1); + if (start + 2 > buffer_len) [[unlikely]] + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 1); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0x81, 0x8d) and byte2 >= 0xa0) return start + 2; - if (start + 3 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("MULE_INTERNAL", buffer, start, 2); + if (start + 3 > buffer_len) [[unlikely]] + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 2); if ( ((byte1 == 0x9a and between_inc(byte2, 0xa0, 0xdf)) or @@ -599,9 +577,8 @@ template<> struct glyph_scanner (byte2 >= 0xa0)) return start + 3; - if (start + 4 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("MULE_INTERNAL", buffer, start, 3); + if (start + 4 > buffer_len) [[unlikely]] + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 3); if ( ((byte1 == 0x9c and between_inc(byte2, 0xf0, 0xf4)) or @@ -610,8 +587,7 @@ template<> struct glyph_scanner get_byte(buffer, start + 4) >= 0xa0) return start + 4; - PQXX_UNLIKELY - throw_for_encoding_error("MULE_INTERNAL", buffer, start, 4); + [[unlikely]] throw_for_encoding_error("MULE_INTERNAL", buffer, start, 4); } }; @@ -639,24 +615,20 @@ template<> struct glyph_scanner if ( not between_inc(byte1, 0x81, 0x9f) and - not between_inc(byte1, 0xe0, 0xfc)) - PQXX_UNLIKELY - throw_for_encoding_error("SJIS", buffer, start, 1); + not between_inc(byte1, 0xe0, 0xfc)) [[unlikely]] + throw_for_encoding_error("SJIS", buffer, start, 1); - if (start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("SJIS", buffer, start, buffer_len - start); + if (start + 2 > buffer_len) [[unlikely]] + throw_for_encoding_error("SJIS", buffer, start, buffer_len - start); auto const byte2{get_byte(buffer, start + 1)}; - if (byte2 == 0x7f) - PQXX_UNLIKELY - throw_for_encoding_error("SJIS", buffer, start, 2); + if (byte2 == 0x7f) [[unlikely]] + throw_for_encoding_error("SJIS", buffer, start, 2); if (between_inc(byte2, 0x40, 0x9e) or between_inc(byte2, 0x9f, 0xfc)) return start + 2; - PQXX_UNLIKELY - throw_for_encoding_error("SJIS", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("SJIS", buffer, start, 2); } }; @@ -667,16 +639,15 @@ template<> struct glyph_scanner static PQXX_PURE std::size_t call(char const buffer[], std::size_t buffer_len, std::size_t start) { - if (start >= buffer_len) - PQXX_UNLIKELY return std::string::npos; + if (start >= buffer_len) [[unlikely]] + return std::string::npos; auto const byte1{get_byte(buffer, start)}; if (byte1 < 0x80) return start + 1; - if (start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("UHC", buffer, start, buffer_len - start); + if (start + 2 > buffer_len) [[unlikely]] + throw_for_encoding_error("UHC", buffer, start, buffer_len - start); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0x80, 0xc6)) @@ -686,15 +657,13 @@ template<> struct glyph_scanner between_inc(byte2, 0x80, 0xfe)) return start + 2; - PQXX_UNLIKELY - throw_for_encoding_error("UHC", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("UHC", buffer, start, 2); } if (between_inc(byte1, 0xa1, 0xfe)) { - if (not between_inc(byte2, 0xa1, 0xfe)) - PQXX_UNLIKELY - throw_for_encoding_error("UHC", buffer, start, 2); + if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] + throw_for_encoding_error("UHC", buffer, start, 2); return start + 2; } @@ -710,30 +679,27 @@ template<> struct glyph_scanner static PQXX_PURE std::size_t call(char const buffer[], std::size_t buffer_len, std::size_t start) { - if (start >= buffer_len) - PQXX_UNLIKELY return std::string::npos; + if (start >= buffer_len) [[unlikely]] + return std::string::npos; auto const byte1{get_byte(buffer, start)}; if (byte1 < 0x80) return start + 1; - if (start + 2 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + if (start + 2 > buffer_len) [[unlikely]] + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0xc0, 0xdf)) { - if (not between_inc(byte2, 0x80, 0xbf)) - PQXX_UNLIKELY - throw_for_encoding_error("UTF8", buffer, start, 2); + if (not between_inc(byte2, 0x80, 0xbf)) [[unlikely]] + throw_for_encoding_error("UTF8", buffer, start, 2); return start + 2; } - if (start + 3 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + if (start + 3 > buffer_len) [[unlikely]] + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); auto const byte3{get_byte(buffer, start + 2)}; if (between_inc(byte1, 0xe0, 0xef)) @@ -741,13 +707,11 @@ template<> struct glyph_scanner if (between_inc(byte2, 0x80, 0xbf) and between_inc(byte3, 0x80, 0xbf)) return start + 3; - PQXX_UNLIKELY - throw_for_encoding_error("UTF8", buffer, start, 3); + [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 3); } - if (start + 4 > buffer_len) - PQXX_UNLIKELY - throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + if (start + 4 > buffer_len) [[unlikely]] + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); if (between_inc(byte1, 0xf0, 0xf7)) { @@ -756,12 +720,10 @@ template<> struct glyph_scanner between_inc(get_byte(buffer, start + 3), 0x80, 0xbf)) return start + 4; - PQXX_UNLIKELY - throw_for_encoding_error("UTF8", buffer, start, 4); + [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 4); } - PQXX_UNLIKELY - throw_for_encoding_error("UTF8", buffer, start, 1); + [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 1); } }; @@ -798,7 +760,7 @@ map_ascii_search_group(encoding_group enc) noexcept // string byte for byte. Multibyte characters have the high bit set. return encoding_group::MONOBYTE; - default: PQXX_UNLIKELY return enc; + default: [[unlikely]] return enc; } } diff --git a/include/pqxx/internal/header-pre.hxx b/include/pqxx/internal/header-pre.hxx index 5c240d0b7..0b1eb80e3 100644 --- a/include/pqxx/internal/header-pre.hxx +++ b/include/pqxx/internal/header-pre.hxx @@ -170,16 +170,6 @@ # 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 - - // C++23: Assume support. #if defined(PQXX_HAVE_ASSUME) # define PQXX_ASSUME(condition) [[assume(condition)]] diff --git a/include/pqxx/internal/stream_query_impl.hxx b/include/pqxx/internal/stream_query_impl.hxx index 714efd1c0..829730f79 100644 --- a/include/pqxx/internal/stream_query_impl.hxx +++ b/include/pqxx/internal/stream_query_impl.hxx @@ -180,8 +180,8 @@ stream_query::read_line() & { auto line{gate.read_copy_line()}; // Check for completion. - if (not line.first) - PQXX_UNLIKELY close(); + if (not line.first) [[unlikely]] + close(); return line; } catch (std::exception const &) diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index e11075fbf..78d794a82 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -253,9 +253,8 @@ public: } else { - PQXX_LIKELY // Shortcut for the common case: just increment that last digit. - ++m_buf[m_len - 1]; + [[likely]]++ m_buf[m_len - 1]; } } diff --git a/include/pqxx/pipeline.hxx b/include/pqxx/pipeline.hxx index e8dbd8921..9d39933b1 100644 --- a/include/pqxx/pipeline.hxx +++ b/include/pqxx/pipeline.hxx @@ -195,7 +195,7 @@ private: /// The given query failed; never issue anything beyond that. void set_error_at(query_id qid) noexcept { - PQXX_UNLIKELY + [[unlikely]] if (qid < m_error) m_error = qid; } diff --git a/include/pqxx/stream_from.hxx b/include/pqxx/stream_from.hxx index 6a0687c38..ab538fa68 100644 --- a/include/pqxx/stream_from.hxx +++ b/include/pqxx/stream_from.hxx @@ -322,13 +322,13 @@ inline stream_from::stream_from( template inline stream_from &stream_from::operator>>(Tuple &t) { - if (m_finished) - PQXX_UNLIKELY return *this; + if (m_finished) [[unlikely]] + return *this; static constexpr auto tup_size{std::tuple_size_v}; m_fields.reserve(tup_size); parse_line(); - if (m_finished) - PQXX_UNLIKELY return *this; + if (m_finished) [[unlikely]] + return *this; if (std::size(m_fields) != tup_size) throw usage_error{internal::concat( diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index acfc873ce..6fb4126c8 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -573,9 +573,9 @@ inline constexpr char unescape_char(char escaped) noexcept switch (escaped) { case 'b': // Backspace. - PQXX_UNLIKELY return '\b'; + [[unlikely]] return '\b'; case 'f': // Form feed - PQXX_UNLIKELY return '\f'; + [[unlikely]] return '\f'; case 'n': // Line feed. return '\n'; case 'r': // Carriage return. diff --git a/pqxx_cxx_feature_checks.ac b/pqxx_cxx_feature_checks.ac index 195ff71ca..fd91792a3 100644 --- a/pqxx_cxx_feature_checks.ac +++ b/pqxx_cxx_feature_checks.ac @@ -59,16 +59,6 @@ AC_COMPILE_IFELSE( [Define if this feature is available.]), PQXX_HAVE_GCC_VISIBILITY=no) AC_MSG_RESULT($PQXX_HAVE_GCC_VISIBILITY) -AC_MSG_CHECKING([PQXX_HAVE_LIKELY]) -PQXX_HAVE_LIKELY=yes -AC_COMPILE_IFELSE( - [read_test(PQXX_HAVE_LIKELY.cxx)], - AC_DEFINE( - [PQXX_HAVE_LIKELY], - 1, - [Define if this feature is available.]), - PQXX_HAVE_LIKELY=no) -AC_MSG_RESULT($PQXX_HAVE_LIKELY) AC_MSG_CHECKING([PQXX_HAVE_MULTIDIM]) PQXX_HAVE_MULTIDIM=yes AC_COMPILE_IFELSE( diff --git a/src/array.cxx b/src/array.cxx index 6a5dacd54..5d5594ee4 100644 --- a/src/array.cxx +++ b/src/array.cxx @@ -135,8 +135,7 @@ std::pair array_parser::parse_array_step() { // The normal case: we just parsed an unquoted string. The value // is what we need. - PQXX_LIKELY - return std::tuple{juncture::string_value, endpoint}; + [[likely]] return std::tuple{juncture::string_value, endpoint}; } } } @@ -146,9 +145,8 @@ std::pair array_parser::parse_array_step() if (end < std::size(m_input)) { auto next{scan_glyph(end)}; - if (((next - end) == 1) and (m_input[end] == ',')) - PQXX_UNLIKELY - end = next; + if (((next - end) == 1) and (m_input[end] == ',')) [[unlikely]] + end = next; } m_pos = end; @@ -181,7 +179,7 @@ array_parser::specialize_for_encoding(pqxx::internal::encoding_group enc) PQXX_ENCODING_CASE(UHC); PQXX_ENCODING_CASE(UTF8); } - PQXX_UNLIKELY throw pqxx::internal_error{ + [[unlikely]] throw pqxx::internal_error{ pqxx::internal::concat("Unsupported encoding code: ", enc, ".")}; #undef PQXX_ENCODING_CASE diff --git a/src/connection.cxx b/src/connection.cxx index 53397dd05..c95e13c77 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -162,7 +162,7 @@ std::pair pqxx::connection::poll_connect() case PGRES_POLLING_OK: if (not is_open()) throw pqxx::broken_connection{PQerrorMessage(m_conn)}; - PQXX_LIKELY return std::make_pair(false, false); + [[likely]] return std::make_pair(false, false); case PGRES_POLLING_ACTIVE: throw internal_error{ "Nonblocking connection poll returned obsolete 'active' state."}; @@ -477,8 +477,7 @@ pqxx::connection::remove_receiver(pqxx::notification_receiver *T) noexcept if (i == R.second) { - PQXX_UNLIKELY - process_notice(internal::concat( + [[unlikely]] process_notice(internal::concat( "Attempt to remove unknown receiver '", needle.first, "'\n")); } else @@ -529,16 +528,14 @@ void PQXX_COLD pqxx::connection::cancel_query() { std::unique_ptr const cancel{ PQgetCancel(m_conn), wrap_pgfreecancel}; - if (cancel == nullptr) - PQXX_UNLIKELY - throw std::bad_alloc{}; + if (cancel == nullptr) [[unlikely]] + throw std::bad_alloc{}; std::array errbuf{}; auto const err{errbuf.data()}; auto const c{PQcancel(cancel.get(), err, buf_size)}; - if (c == 0) - PQXX_UNLIKELY - throw pqxx::sql_error{std::string{err, std::size(errbuf)}, "[cancel]"}; + if (c == 0) [[unlikely]] + throw pqxx::sql_error{std::string{err, std::size(errbuf)}, "[cancel]"}; } @@ -607,9 +604,8 @@ int pqxx::connection::get_notifs() // Even if somehow we receive notifications during our transaction, don't // deliver them. - if (m_trans != nullptr) - PQXX_UNLIKELY - return 0; + if (m_trans != nullptr) [[unlikely]] + return 0; int notifs = 0; @@ -790,17 +786,16 @@ void pqxx::connection::close() return; try { - if (m_trans) - PQXX_UNLIKELY - process_notice(internal::concat( - "Closing connection while ", - internal::describe_object("transaction"sv, m_trans->name()), - " is still open.\n")); + if (m_trans) [[unlikely]] + process_notice(internal::concat( + "Closing connection while ", + internal::describe_object("transaction"sv, m_trans->name()), + " is still open.\n")); if (not std::empty(m_receivers)) { - PQXX_UNLIKELY - process_notice("Closing connection with outstanding receivers.\n"); + [[unlikely]] process_notice( + "Closing connection with outstanding receivers.\n"); m_receivers.clear(); } @@ -897,7 +892,7 @@ pqxx::connection::read_copy_line() throw internal_error{"table read inexplicably went asynchronous"}; default: // Success, got buffer size. - PQXX_LIKELY + [[likely]] { // Line size includes a trailing zero, which we ignore. auto const text_len{static_cast(line_len) - 1}; @@ -915,12 +910,10 @@ void pqxx::connection::write_copy_line(std::string_view line) static std::string const err_prefix{"Error writing to table: "}; auto const size{check_cast( internal::ssize(line), "Line in stream_to is too long to process."sv)}; - if (PQputCopyData(m_conn, line.data(), size) <= 0) - PQXX_UNLIKELY - throw failure{err_prefix + err_msg()}; - if (PQputCopyData(m_conn, "\n", 1) <= 0) - PQXX_UNLIKELY - throw failure{err_prefix + err_msg()}; + if (PQputCopyData(m_conn, line.data(), size) <= 0) [[unlikely]] + throw failure{err_prefix + err_msg()}; + if (PQputCopyData(m_conn, "\n", 1) <= 0) [[unlikely]] + throw failure{err_prefix + err_msg()}; } @@ -948,9 +941,8 @@ void pqxx::connection::end_copy_write() void pqxx::connection::start_exec(char const query[]) { - if (PQsendQuery(m_conn, query) == 0) - PQXX_UNLIKELY - throw failure{err_msg()}; + if (PQsendQuery(m_conn, query) == 0) [[unlikely]] + throw failure{err_msg()}; } @@ -965,9 +957,8 @@ size_t pqxx::connection::esc_to_buf(std::string_view text, char *buf) const int err{0}; auto const copied{ PQescapeStringConn(m_conn, buf, text.data(), std::size(text), &err)}; - if (err) - PQXX_UNLIKELY - throw argument_error{err_msg()}; + if (err) [[unlikely]] + throw argument_error{err_msg()}; return copied; } @@ -1049,9 +1040,8 @@ std::string pqxx::connection::quote_name(std::string_view identifier) const std::unique_ptr const buf{ PQescapeIdentifier(m_conn, identifier.data(), std::size(identifier)), pqxx::internal::pq::pqfreemem}; - if (buf == nullptr) - PQXX_UNLIKELY - throw failure{err_msg()}; + if (buf == nullptr) [[unlikely]] + throw failure{err_msg()}; return std::string{buf.get()}; } @@ -1080,7 +1070,8 @@ pqxx::connection::esc_like(std::string_view text, char escape_char) const internal::enc_group(encoding_id()), [&out, escape_char](char const *gbegin, char const *gend) { if ((gend - gbegin == 1) and (*gbegin == '_' or *gbegin == '%')) - PQXX_UNLIKELY out.push_back(escape_char); + [[unlikely]] + out.push_back(escape_char); for (; gbegin != gend; ++gbegin) out.push_back(*gbegin); }, @@ -1094,8 +1085,7 @@ int pqxx::connection::await_notification() int notifs = get_notifs(); if (notifs == 0) { - PQXX_LIKELY - internal::wait_fd(socket_of(m_conn), true, false, 10, 0); + [[likely]] internal::wait_fd(socket_of(m_conn), true, false, 10, 0); notifs = get_notifs(); } return notifs; @@ -1108,8 +1098,7 @@ int pqxx::connection::await_notification( int const notifs = get_notifs(); if (notifs == 0) { - PQXX_LIKELY - internal::wait_fd( + [[likely]] internal::wait_fd( socket_of(m_conn), true, false, check_cast(seconds, "Seconds out of range."), check_cast(microseconds, "Microseconds out of range.")); @@ -1141,17 +1130,15 @@ void PQXX_COLD pqxx::connection::set_client_encoding(char const encoding[]) & { case 0: // OK. - PQXX_LIKELY - break; + [[likely]] break; case -1: - PQXX_UNLIKELY + [[unlikely]] if (is_open()) throw failure{"Setting client encoding failed."}; else throw broken_connection{"Lost connection to the database server."}; default: - PQXX_UNLIKELY - throw internal_error{internal::concat( + [[unlikely]] throw internal_error{internal::concat( "Unexpected result from PQsetClientEncoding: ", retval)}; } } @@ -1167,14 +1154,13 @@ int pqxx::connection::encoding_id() const // *before* checking a query result for failure. So, we need to handle // connection failure here and it will apply in lots of places. // TODO: Make pqxx::result::result(...) do all the checking. - PQXX_UNLIKELY + [[unlikely]] if (is_open()) throw failure{"Could not obtain client encoding."}; else throw broken_connection{"Lost connection to the database server."}; } - PQXX_LIKELY - return enc; + [[likely]] return enc; } @@ -1236,15 +1222,13 @@ void pqconninfofree(PQconninfoOption *ptr) std::string pqxx::connection::connection_string() const { - if (m_conn == nullptr) - PQXX_UNLIKELY - throw usage_error{"Can't get connection string: connection is not open."}; + if (m_conn == nullptr) [[unlikely]] + throw usage_error{"Can't get connection string: connection is not open."}; std::unique_ptr const params{ PQconninfo(m_conn), pqconninfofree}; - if (params == nullptr) - PQXX_UNLIKELY - throw std::bad_alloc{}; + if (params == nullptr) [[unlikely]] + throw std::bad_alloc{}; std::string buf; for (std::size_t i{0}; params.get()[i].keyword != nullptr; ++i) diff --git a/src/cursor.cxx b/src/cursor.cxx index 5fac845bb..3e12c0fc5 100644 --- a/src/cursor.cxx +++ b/src/cursor.cxx @@ -247,13 +247,12 @@ pqxx::icursor_iterator &pqxx::icursor_iterator::operator+=(difference_type n) { if (n <= 0) { - PQXX_UNLIKELY + [[unlikely]] if (n == 0) return *this; throw argument_error{"Advancing icursor_iterator by negative offset."}; } - PQXX_LIKELY - m_pos = difference_type( + [[likely]] m_pos = difference_type( pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream}.forward( icursorstream::size_type(n))); m_here.clear(); @@ -267,13 +266,12 @@ pqxx::icursor_iterator::operator=(icursor_iterator const &rhs) noexcept if (&rhs == this) {} else if (rhs.m_stream == m_stream) { - PQXX_UNLIKELY - m_here = rhs.m_here; + [[unlikely]] m_here = rhs.m_here; m_pos = rhs.m_pos; } else { - PQXX_LIKELY + [[likely]] if (m_stream != nullptr) pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream} .remove_iterator(this); diff --git a/src/encodings.cxx b/src/encodings.cxx index e053bf870..f583f644b 100644 --- a/src/encodings.cxx +++ b/src/encodings.cxx @@ -63,8 +63,7 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) case 'B': if (encoding_name == "BIG5"sv) return pqxx::internal::encoding_group::BIG5; - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'E': // C++20: Use string_view::starts_with(). if ((sz >= 6u) and (encoding_name.substr(0, 4) == "EUC_"sv)) @@ -82,15 +81,13 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) if (m.get_name() == subtype) return m.get_group(); } - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'G': if (encoding_name == "GB18030"sv) return pqxx::internal::encoding_group::GB18030; else if (encoding_name == "GBK"sv) return pqxx::internal::encoding_group::GBK; - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'I': // We know iso-8859-X, where 5 <= X < 9. They're all monobyte encodings. // C++20: Use string_view::starts_with(). @@ -100,18 +97,15 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) if (('5' <= subtype) and (subtype < '9')) return pqxx::internal::encoding_group::MONOBYTE; } - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'J': if (encoding_name == "JOHAB"sv) return pqxx::internal::encoding_group::JOHAB; - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'K': if ((encoding_name == "KOI8R"sv) or (encoding_name == "KOI8U"sv)) return pqxx::internal::encoding_group::MONOBYTE; - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'L': // We know LATIN1 through LATIN10. // C++20: Use string_view::starts_with(). @@ -129,13 +123,11 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) return pqxx::internal::encoding_group::MONOBYTE; } } - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'M': if (encoding_name == "MULE_INTERNAL"sv) return pqxx::internal::encoding_group::MULE_INTERNAL; - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'S': if (encoding_name == "SHIFT_JIS_2004"sv) return pqxx::internal::encoding_group::SJIS; @@ -143,15 +135,13 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) return pqxx::internal::encoding_group::SJIS; else if (encoding_name == "SQL_ASCII"sv) return pqxx::internal::encoding_group::MONOBYTE; - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'U': if (encoding_name == "UHC"sv) return pqxx::internal::encoding_group::UHC; else if (encoding_name == "UTF8"sv) return pqxx::internal::encoding_group::UTF8; - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'W': if (encoding_name.substr(0, 3) == "WIN"sv) { @@ -164,12 +154,10 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) if (n == subtype) return pqxx::internal::encoding_group::MONOBYTE; } - PQXX_UNLIKELY - break; - default: PQXX_UNLIKELY break; + [[unlikely]] break; + default: [[unlikely]] break; } - PQXX_UNLIKELY - throw std::invalid_argument{ + [[unlikely]] throw std::invalid_argument{ pqxx::internal::concat("Unrecognized encoding: '", encoding_name, "'.")}; } @@ -194,7 +182,7 @@ PQXX_PURE glyph_scanner_func *get_glyph_scanner(encoding_group enc) switch (enc) { - PQXX_LIKELY CASE_GROUP(MONOBYTE); + [[likely]] CASE_GROUP(MONOBYTE); CASE_GROUP(BIG5); CASE_GROUP(EUC_CN); CASE_GROUP(EUC_JP); @@ -206,9 +194,8 @@ PQXX_PURE glyph_scanner_func *get_glyph_scanner(encoding_group enc) CASE_GROUP(MULE_INTERNAL); CASE_GROUP(SJIS); CASE_GROUP(UHC); - PQXX_LIKELY CASE_GROUP(UTF8); + [[likely]] CASE_GROUP(UTF8); } - PQXX_UNLIKELY throw usage_error{ internal::concat("Unsupported encoding group code ", enc, ".")}; diff --git a/src/pipeline.cxx b/src/pipeline.cxx index 99bea4a5d..199378dcd 100644 --- a/src/pipeline.cxx +++ b/src/pipeline.cxx @@ -238,9 +238,8 @@ bool pqxx::pipeline::obtain_result(bool expect_none) gate.get_result(), pqxx::internal::clear_result}; if (not r) { - if (have_pending() and not expect_none) + if (have_pending() and not expect_none) [[unlikely]] { - PQXX_UNLIKELY set_error_at(m_issuedrange.first->first); m_issuedrange.second = m_issuedrange.first; } @@ -252,18 +251,16 @@ bool pqxx::pipeline::obtain_result(bool expect_none) result const res{pqxx::internal::gate::result_creation::create( r, std::begin(m_queries)->second.query, handler, m_encoding)}; - if (not have_pending()) + if (not have_pending()) [[unlikely]] { - PQXX_UNLIKELY set_error_at(std::begin(m_queries)->first); throw std::logic_error{ "Got more results from pipeline than there were queries."}; } // Must be the result for the oldest pending query. - if (not std::empty(m_issuedrange.first->second.res)) - PQXX_UNLIKELY - internal_error("Multiple results for one query."); + if (not std::empty(m_issuedrange.first->second.res)) [[unlikely]] + internal_error("Multiple results for one query."); m_issuedrange.first->second.res = res; ++m_issuedrange.first; @@ -283,9 +280,9 @@ void pqxx::pipeline::obtain_dummy() gate.get_result(), pqxx::internal::clear_result}; m_dummy_pending = false; - if (not r) - PQXX_UNLIKELY - internal_error("Pipeline got no result from backend when it expected one."); + if (not r) [[unlikely]] + internal_error( + "Pipeline got no result from backend when it expected one."); pqxx::internal::gate::connection_pipeline const pgate{m_trans->conn()}; auto handler{pgate.get_notice_waiters()}; @@ -300,16 +297,13 @@ void pqxx::pipeline::obtain_dummy() } catch (sql_error const &) {} - if (OK) + if (OK) [[likely]] { - PQXX_LIKELY - if (std::size(R) > 1) - PQXX_UNLIKELY - internal_error("Unexpected result for dummy query in pipeline."); - - if (R.at(0).at(0).as() != theDummyValue) - PQXX_UNLIKELY - internal_error("Dummy query in pipeline returned unexpected value."); + if (std::size(R) > 1) [[unlikely]] + internal_error("Unexpected result for dummy query in pipeline."); + + if (R.at(0).at(0).as() != theDummyValue) [[unlikely]] + internal_error("Dummy query in pipeline returned unexpected value."); return; } diff --git a/src/result.cxx b/src/result.cxx index a0dca41be..ad13f207f 100644 --- a/src/result.cxx +++ b/src/result.cxx @@ -63,8 +63,8 @@ pqxx::result::result( bool pqxx::result::operator==(result const &rhs) const noexcept { - if (&rhs == this) - PQXX_UNLIKELY return true; + if (&rhs == this) [[unlikely]] + return true; auto const s{size()}; if (std::size(rhs) != s) return false; @@ -202,7 +202,6 @@ void PQXX_COLD pqxx::result::throw_sql_error( switch (code[0]) { - PQXX_UNLIKELY case '\0': // SQLSTATE is empty. We may have seen this happen in one // circumstance: a client-side socket timeout (while using the @@ -306,9 +305,8 @@ void PQXX_COLD pqxx::result::throw_sql_error( void pqxx::result::check_status(std::string_view desc) const { - if (auto err{status_error()}; not std::empty(err)) + if (auto err{status_error()}; not std::empty(err)) [[unlikely]] { - PQXX_UNLIKELY if (not std::empty(desc)) err = pqxx::internal::concat("Failure during '", desc, "': ", err); throw_sql_error(err, query()); @@ -342,8 +340,7 @@ std::string pqxx::result::status_error() const case PGRES_BAD_RESPONSE: // The server's response was not understood. case PGRES_NONFATAL_ERROR: case PGRES_FATAL_ERROR: - PQXX_UNLIKELY - err = PQresultErrorMessage(m_data.get()); + [[unlikely]] err = PQresultErrorMessage(m_data.get()); break; case PGRES_SINGLE_TUPLE: @@ -457,9 +454,8 @@ pqxx::oid pqxx::result::column_table(row::size_type col_num) const pqxx::row::size_type pqxx::result::table_column(row::size_type col_num) const { auto const n{row::size_type(PQftablecol(m_data.get(), col_num))}; - if (n != 0) - PQXX_LIKELY - return n - 1; + if (n != 0) [[likely]] + return n - 1; // Failed. Now find out why, so we can throw a sensible exception. auto const col_str{to_string(col_num)}; @@ -494,9 +490,8 @@ int pqxx::result::errorposition() const char const *pqxx::result::column_name(pqxx::row::size_type number) const & { auto const n{PQfname(m_data.get(), number)}; - if (n == nullptr) + if (n == nullptr) [[unlikely]] { - PQXX_UNLIKELY if (m_data.get() == nullptr) throw usage_error{"Queried column name on null result."}; throw range_error{internal::concat( diff --git a/src/robusttransaction.cxx b/src/robusttransaction.cxx index e9b900a46..f2726e33b 100644 --- a/src/robusttransaction.cxx +++ b/src/robusttransaction.cxx @@ -56,16 +56,16 @@ constexpr tx_stat parse_status(std::string_view text) noexcept switch (text[0]) { case 'a': - if (text == aborted) - PQXX_LIKELY return tx_aborted; + if (text == aborted) [[likely]] + return tx_aborted; break; case 'c': - if (text == committed) - PQXX_LIKELY return tx_committed; + if (text == committed) [[likely]] + return tx_committed; break; case 'i': - if (text == in_progress) - PQXX_LIKELY return tx_in_progress; + if (text == in_progress) [[likely]] + return tx_in_progress; break; } return tx_unknown; diff --git a/src/strconv.cxx b/src/strconv.cxx index 7fe7fba2d..734d2cc7d 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -123,20 +123,19 @@ template inline char *wrap_to_chars(char *begin, char *end, T const &value) { auto res{std::to_chars(begin, end - 1, value)}; - if (res.ec != std::errc()) - PQXX_UNLIKELY - switch (res.ec) - { - case std::errc::value_too_large: - throw pqxx::conversion_overrun{ - "Could not convert " + pqxx::type_name + - " to string: " - "buffer too small (" + - pqxx::to_string(end - begin) + " bytes)."}; - default: - throw pqxx::conversion_error{ - "Could not convert " + pqxx::type_name + " to string."}; - } + if (res.ec != std::errc()) [[unlikely]] + switch (res.ec) + { + case std::errc::value_too_large: + throw pqxx::conversion_overrun{ + "Could not convert " + pqxx::type_name + + " to string: " + "buffer too small (" + + pqxx::to_string(end - begin) + " bytes)."}; + default: + throw pqxx::conversion_error{ + "Could not convert " + pqxx::type_name + " to string."}; + } // No need to check for overrun here: we never even told to_chars about that // last byte in the buffer, so it didn't get used up. *res.ptr++ = '\0'; @@ -294,9 +293,8 @@ template inline TYPE from_string_arithmetic(std::string_view in) TYPE out{}; auto const res{std::from_chars(here, end, out)}; - if (res.ec == std::errc() and res.ptr == end) - PQXX_LIKELY - return out; + if (res.ec == std::errc() and res.ptr == end) [[likely]] + return out; std::string msg; if (res.ec == std::errc()) @@ -404,9 +402,8 @@ inline T PQXX_COLD from_string_awful_float(std::string_view text) ok = true; result = -std::numeric_limits::infinity(); } - else + else [[likely]] { - PQXX_LIKELY if constexpr (have_thread_local) { thread_local dumb_stringstream S; diff --git a/src/stream_from.cxx b/src/stream_from.cxx index d15b11c66..98d7ace7b 100644 --- a/src/stream_from.cxx +++ b/src/stream_from.cxx @@ -61,12 +61,12 @@ pqxx::stream_from::stream_from( from_table_t) : transaction_focus{tx, class_name, table}, m_char_finder{get_finder(tx)} { - if (std::empty(columns)) - PQXX_UNLIKELY - tx.exec(internal::concat("COPY "sv, table, " TO STDOUT"sv)).no_rows(); - else PQXX_LIKELY tx - .exec(internal::concat("COPY "sv, table, "("sv, columns, ") TO STDOUT"sv)) - .no_rows(); + if (std::empty(columns)) [[unlikely]] + tx.exec(internal::concat("COPY "sv, table, " TO STDOUT"sv)).no_rows(); + else [[likely]] + tx.exec( + internal::concat("COPY "sv, table, "("sv, columns, ") TO STDOUT"sv)) + .no_rows(); register_me(); } @@ -138,9 +138,8 @@ pqxx::stream_from::raw_line pqxx::stream_from::get_raw_line() void pqxx::stream_from::close() { - if (not m_finished) + if (not m_finished) [[unlikely]] { - PQXX_UNLIKELY m_finished = true; unregister_me(); } @@ -178,9 +177,8 @@ void pqxx::stream_from::complete() void pqxx::stream_from::parse_line() { - if (m_finished) - PQXX_UNLIKELY - return; + if (m_finished) [[unlikely]] + return; // TODO: Any way to keep current size in a local var, for speed? m_fields.clear(); diff --git a/src/stream_to.cxx b/src/stream_to.cxx index b109a3a62..2af257bc7 100644 --- a/src/stream_to.cxx +++ b/src/stream_to.cxx @@ -50,7 +50,7 @@ char escape_char(char special) case '\\': return '\\'; default: break; } - PQXX_UNLIKELY throw pqxx::internal_error{pqxx::internal::concat( + throw pqxx::internal_error{pqxx::internal::concat( "Stream escaping unexpectedly stopped at '", static_cast(static_cast(special)), "'.")}; } diff --git a/src/time.cxx b/src/time.cxx index cc426a072..103779790 100644 --- a/src/time.cxx +++ b/src/time.cxx @@ -38,7 +38,7 @@ inline char * year_into_buf(char *begin, char *end, std::chrono::year const &value) { int const y{value}; - if (y == int{(std::chrono::year::min)()}) + if (y == int{(std::chrono::year::min)()}) [[unlikely]] { // This is an evil special case: C++ year -32767 translates to 32768 BC, // which is a number we can't fit into a short. At the moment postgres @@ -46,7 +46,6 @@ year_into_buf(char *begin, char *end, std::chrono::year const &value) constexpr int oldest{-32767}; static_assert(int{(std::chrono::year::min)()} == oldest); constexpr auto hardcoded{"32768"sv}; - PQXX_UNLIKELY begin += hardcoded.copy(begin, std::size(hardcoded)); } else @@ -63,9 +62,8 @@ year_into_buf(char *begin, char *end, std::chrono::year const &value) // won't be able to deduce the date format correctly. However on output // it always writes years as at least 4 digits, and we'll do the same. // Dates and times are a dirty, dirty business. - if (absy < thousand) + if (absy < thousand) [[unlikely]] { - PQXX_UNLIKELY *begin++ = '0'; if (absy < hundred) *begin++ = '0'; @@ -190,11 +188,8 @@ char *string_traits::into_buf( begin = month_into_buf(begin, value.month()); *begin++ = '-'; begin = day_into_buf(begin, value.day()); - if (int{value.year()} <= 0) - { - PQXX_UNLIKELY + if (int{value.year()} <= 0) [[unlikely]] begin += s_bc.copy(begin, std::size(s_bc)); - } *begin++ = '\0'; return begin; } @@ -208,9 +203,8 @@ string_traits::from_string(std::string_view text) if (std::size(text) < 9) throw conversion_error{make_parse_error(text)}; bool const is_bc{text.ends_with(s_bc)}; - if (is_bc) - PQXX_UNLIKELY - text = text.substr(0, std::size(text) - std::size(s_bc)); + if (is_bc) [[unlikely]] + text = text.substr(0, std::size(text) - std::size(s_bc)); auto const ymsep{find_year_month_separator(text)}; if ((std::size(text) - ymsep) != 6) throw conversion_error{make_parse_error(text)}; diff --git a/src/transaction_base.cxx b/src/transaction_base.cxx index 242158f46..3972d0a3c 100644 --- a/src/transaction_base.cxx +++ b/src/transaction_base.cxx @@ -60,10 +60,9 @@ pqxx::transaction_base::~transaction_base() { try { - if (not std::empty(m_pending_error)) - PQXX_UNLIKELY - process_notice( - internal::concat("UNPROCESSED ERROR: ", m_pending_error, "\n")); + if (not std::empty(m_pending_error)) [[unlikely]] + process_notice( + internal::concat("UNPROCESSED ERROR: ", m_pending_error, "\n")); if (m_registered) { @@ -353,11 +352,10 @@ void pqxx::transaction_base::close() noexcept if (m_status != status::active) return; - if (m_focus != nullptr) - PQXX_UNLIKELY - m_conn.process_notice(internal::concat( - "Closing ", description(), " with ", m_focus->description(), - " still open.\n")); + if (m_focus != nullptr) [[unlikely]] + m_conn.process_notice(internal::concat( + "Closing ", description(), " with ", m_focus->description(), + " still open.\n")); try { @@ -455,8 +453,7 @@ void pqxx::transaction_base::register_pending_error(zview err) noexcept { try { - PQXX_UNLIKELY - process_notice("UNABLE TO PROCESS ERROR\n"); + [[unlikely]] process_notice("UNABLE TO PROCESS ERROR\n"); // TODO: Make at least an attempt to append a newline. process_notice(e.what()); process_notice("ERROR WAS:\n"); @@ -481,7 +478,6 @@ void pqxx::transaction_base::register_pending_error(std::string &&err) noexcept { try { - PQXX_UNLIKELY process_notice("UNABLE TO PROCESS ERROR\n"); // TODO: Make at least an attempt to append a newline. process_notice(e.what()); diff --git a/src/util.cxx b/src/util.cxx index 48aac1316..7367544ad 100644 --- a/src/util.cxx +++ b/src/util.cxx @@ -81,9 +81,8 @@ void pqxx::internal::check_unique_unregister( void const *old_guest, std::string_view old_class, std::string_view old_name, void const *new_guest, std::string_view new_class, std::string_view new_name) { - if (new_guest != old_guest) + if (new_guest != old_guest) [[unlikely]] { - PQXX_UNLIKELY if (new_guest == nullptr) throw usage_error{concat( "Expected to close ", describe_object(old_class, old_name), @@ -120,12 +119,14 @@ constexpr int ten{10}; /// Translate a hex digit to a nibble. Return -1 if it's not a valid digit. constexpr int nibble(int c) noexcept { - if (c >= '0' and c <= '9') - PQXX_LIKELY - return c - '0'; - else if (c >= 'a' and c <= 'f') return ten + (c - 'a'); - else if (c >= 'A' and c <= 'F') return ten + (c - 'A'); - else return -1; + if (c >= '0' and c <= '9') [[likely]] + return c - '0'; + else if (c >= 'a' and c <= 'f') + return ten + (c - 'a'); + else if (c >= 'A' and c <= 'F') + return ten + (c - 'A'); + else + return -1; } } // namespace From ae9f1eafa4e326fc8411b4d0d1ee3222762e0824 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 01:50:47 +0100 Subject: [PATCH 05/64] Assume support for `std::span`. --- cmake/pqxx_cxx_feature_checks.cmake | 4 --- config-tests/PQXX_HAVE_SPAN.cxx | 11 -------- configitems | 1 - configure | 40 --------------------------- cxx_features.txt | 1 - include/pqxx/blob.hxx | 8 ++---- include/pqxx/config.h.in | 3 -- include/pqxx/connection.hxx | 8 ++---- include/pqxx/internal/conversions.hxx | 8 +----- pqxx_cxx_feature_checks.ac | 10 ------- test/unit/test_blob.cxx | 4 --- test/unit/test_escape.cxx | 4 +-- 12 files changed, 8 insertions(+), 94 deletions(-) delete mode 100644 config-tests/PQXX_HAVE_SPAN.cxx diff --git a/cmake/pqxx_cxx_feature_checks.cmake b/cmake/pqxx_cxx_feature_checks.cmake index d9ac004c1..794c5706f 100644 --- a/cmake/pqxx_cxx_feature_checks.cmake +++ b/cmake/pqxx_cxx_feature_checks.cmake @@ -43,10 +43,6 @@ try_compile( PQXX_HAVE_SOURCE_LOCATION ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_SOURCE_LOCATION.cxx ) -try_compile( - PQXX_HAVE_SPAN ${PROJECT_BINARY_DIR} - SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_SPAN.cxx -) try_compile( PQXX_HAVE_SSIZE ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_SSIZE.cxx diff --git a/config-tests/PQXX_HAVE_SPAN.cxx b/config-tests/PQXX_HAVE_SPAN.cxx deleted file mode 100644 index e733a4a0d..000000000 --- a/config-tests/PQXX_HAVE_SPAN.cxx +++ /dev/null @@ -1,11 +0,0 @@ -// Feature check for 'PQXX_HAVE_SPAN'. -// Generated by generate_cxx_checks.py. -#include -#if !defined(__cpp_lib_span) -# error "No PQXX_HAVE_SPAN: __cpp_lib_span is not set." -#endif -#if !__cpp_lib_span -# error "No PQXX_HAVE_SPAN: __cpp_lib_span is false." -#endif - -int main() {} diff --git a/configitems b/configitems index bbac7c1f2..51916d221 100644 --- a/configitems +++ b/configitems @@ -15,7 +15,6 @@ PQXX_HAVE_PATH public compiler PQXX_HAVE_POLL internal compiler PQXX_HAVE_SLEEP_FOR internal compiler PQXX_HAVE_SOURCE_LOCATION public compiler -PQXX_HAVE_SPAN public compiler PQXX_HAVE_SSIZE public compiler PQXX_HAVE_STRERROR_R public compiler PQXX_HAVE_STRERROR_S public compiler diff --git a/configure b/configure index 57860a64a..6e7a2e03f 100755 --- a/configure +++ b/configure @@ -17738,46 +17738,6 @@ fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_SOURCE_LOCATION" >&5 printf "%s\n" "$PQXX_HAVE_SOURCE_LOCATION" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_SPAN" >&5 -printf %s "checking PQXX_HAVE_SPAN... " >&6; } -PQXX_HAVE_SPAN=yes -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -// Feature check for 'PQXX_HAVE_SPAN'. - -// Generated by generate_cxx_checks.py. - -#include - -#if !defined(__cpp_lib_span) - -# error "No PQXX_HAVE_SPAN: __cpp_lib_span is not set." - -#endif - -#if !__cpp_lib_span - -# error "No PQXX_HAVE_SPAN: __cpp_lib_span is false." - -#endif - - - -int main() {} - - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO" -then : - -printf "%s\n" "#define PQXX_HAVE_SPAN 1" >>confdefs.h - -else $as_nop - PQXX_HAVE_SPAN=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_SPAN" >&5 -printf "%s\n" "$PQXX_HAVE_SPAN" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_SSIZE" >&5 printf %s "checking PQXX_HAVE_SSIZE... " >&6; } PQXX_HAVE_SSIZE=yes diff --git a/cxx_features.txt b/cxx_features.txt index c9986486a..d656c8d8d 100644 --- a/cxx_features.txt +++ b/cxx_features.txt @@ -15,5 +15,4 @@ PQXX_HAVE_CONCEPTS __cpp_concepts PQXX_HAVE_MULTIDIM __cpp_multidimensional_subscript PQXX_HAVE_SOURCE_LOCATION __cpp_lib_source_location -PQXX_HAVE_SPAN __cpp_lib_span PQXX_HAVE_SSIZE __cpp_lib_ssize diff --git a/include/pqxx/blob.hxx b/include/pqxx/blob.hxx index 9a8621dee..c4a776aff 100644 --- a/include/pqxx/blob.hxx +++ b/include/pqxx/blob.hxx @@ -106,7 +106,6 @@ public: */ std::size_t read(bytes &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. @@ -118,9 +117,8 @@ public: { 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) +#if defined(PQXX_HAVE_CONCEPTS) /// 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. @@ -131,7 +129,7 @@ public: { return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; } -#else // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN +#else // PQXX_HAVE_CONCEPTS /// 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`. @@ -149,7 +147,7 @@ public: { return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; } -#endif // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN +#endif // PQXX_HAVE_CONCEPTS #if defined(PQXX_HAVE_CONCEPTS) /// Write `data` to large object, at the current position. diff --git a/include/pqxx/config.h.in b/include/pqxx/config.h.in index 26253f1a3..b37533332 100644 --- a/include/pqxx/config.h.in +++ b/include/pqxx/config.h.in @@ -90,9 +90,6 @@ /* Define if this feature is available. */ #undef PQXX_HAVE_SOURCE_LOCATION -/* Define if this feature is available. */ -#undef PQXX_HAVE_SPAN - /* Define if this feature is available. */ #undef PQXX_HAVE_SSIZE diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index 78b1ebc73..a3b7609d5 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -841,7 +841,6 @@ public: return esc(std::string_view{text}); } -#if defined(PQXX_HAVE_SPAN) /// Escape string for use as SQL string literal, into `buffer`. /** Use this variant when you want to re-use the same buffer across multiple * calls. If that's not the case, or convenience and simplicity are more @@ -866,7 +865,6 @@ public: auto const data{buffer.data()}; return {data, esc_to_buf(text, data)}; } -#endif /// Escape string for use as SQL string literal on this connection. /** @warning This is meant for text strings only. It cannot contain bytes @@ -883,7 +881,7 @@ public: } #endif -#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) +#if defined(PQXX_HAVE_CONCEPTS) /// Escape binary string for use as SQL string literal, into `buffer`. /** Use this variant when you want to re-use the same buffer across multiple * calls. If that's not the case, or convenience and simplicity are more @@ -923,11 +921,9 @@ public: /** You can also just use @ref esc with a binary string. */ [[nodiscard]] std::string esc_raw(bytes_view) const; -#if defined(PQXX_HAVE_SPAN) /// Escape binary string for use as SQL string literal, into `buffer`. /** You can also just use @ref esc with a binary string. */ [[nodiscard]] std::string esc_raw(bytes_view, std::span buffer) const; -#endif #if defined(PQXX_HAVE_CONCEPTS) /// Escape binary string for use as SQL string literal on this connection. @@ -939,7 +935,7 @@ public: } #endif -#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) +#if defined(PQXX_HAVE_CONCEPTS) /// Escape binary string for use as SQL string literal, into `buffer`. template [[nodiscard]] zview esc_raw(DATA const &data, std::span buffer) const diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index 57bc370e9..a5177fa5e 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -4,11 +4,7 @@ #include #include #include - -#if defined(PQXX_HAVE_SPAN) && __has_include() -# include -#endif - +#include #include #include #include @@ -1181,7 +1177,6 @@ inline constexpr format param_format(std::vector const &) template inline constexpr bool is_sql_array>{true}; -#if defined(PQXX_HAVE_SPAN) && __has_include() template struct nullness> : no_null> {}; @@ -1209,7 +1204,6 @@ inline constexpr format param_format(std::span const &) template inline constexpr bool is_sql_array>{true}; -#endif template diff --git a/pqxx_cxx_feature_checks.ac b/pqxx_cxx_feature_checks.ac index fd91792a3..86f28157c 100644 --- a/pqxx_cxx_feature_checks.ac +++ b/pqxx_cxx_feature_checks.ac @@ -109,16 +109,6 @@ AC_COMPILE_IFELSE( [Define if this feature is available.]), PQXX_HAVE_SOURCE_LOCATION=no) AC_MSG_RESULT($PQXX_HAVE_SOURCE_LOCATION) -AC_MSG_CHECKING([PQXX_HAVE_SPAN]) -PQXX_HAVE_SPAN=yes -AC_COMPILE_IFELSE( - [read_test(PQXX_HAVE_SPAN.cxx)], - AC_DEFINE( - [PQXX_HAVE_SPAN], - 1, - [Define if this feature is available.]), - PQXX_HAVE_SPAN=no) -AC_MSG_RESULT($PQXX_HAVE_SPAN) AC_MSG_CHECKING([PQXX_HAVE_SSIZE]) PQXX_HAVE_SSIZE=yes AC_COMPILE_IFELSE( diff --git a/test/unit/test_blob.cxx b/test/unit/test_blob.cxx index 846ec14ad..fb95767e4 100644 --- a/test/unit/test_blob.cxx +++ b/test/unit/test_blob.cxx @@ -183,7 +183,6 @@ template inline unsigned byte_val(BYTE val) void test_blob_read_span() { -#if defined(PQXX_HAVE_SPAN) pqxx::bytes const data{std::byte{'u'}, std::byte{'v'}, std::byte{'w'}, std::byte{'x'}, std::byte{'y'}, std::byte{'z'}}; @@ -230,7 +229,6 @@ void test_blob_read_span() output2 = b.read(vec_buf); PQXX_CHECK_EQUAL(std::size(output2), 1u, "Weird things happened at EOF."); PQXX_CHECK_EQUAL(byte_val(output2[0]), byte_val('z'), "Bad data at EOF."); -#endif // PQXX_HAVE_SPAN } @@ -286,7 +284,6 @@ void test_blob_write_appends_at_insertion_point() void test_blob_writes_span() { -#if defined(PQXX_HAVE_SPAN) pqxx::connection cx; pqxx::work tx{cx}; constexpr char content[]{"gfbltk"}; @@ -307,7 +304,6 @@ void test_blob_writes_span() byte_val(out[0]), byte_val('f'), "Data did not come back right."); PQXX_CHECK_EQUAL( byte_val(out[2]), byte_val('l'), "Data started right, ended wrong!"); -#endif // PQXX_HAVE_SPAN } diff --git a/test/unit/test_escape.cxx b/test/unit/test_escape.cxx index 956583578..d88570ce4 100644 --- a/test/unit/test_escape.cxx +++ b/test/unit/test_escape.cxx @@ -177,7 +177,7 @@ void test_esc_escapes_into_buffer() void test_esc_accepts_various_types() { -#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) +#if defined(PQXX_HAVE_CONCEPTS) pqxx::connection cx; pqxx::work tx{cx}; @@ -197,7 +197,7 @@ void test_esc_accepts_various_types() void test_binary_esc_checks_buffer_length() { -#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) +#if defined(PQXX_HAVE_CONCEPTS) pqxx::connection cx; pqxx::work tx{cx}; From 24360212637884ee4374e9a9226594348dbed4e6 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 02:03:58 +0100 Subject: [PATCH 06/64] Assume support for `std::ssize()`. --- cmake/pqxx_cxx_feature_checks.cmake | 4 --- config-tests/PQXX_HAVE_SSIZE.cxx | 11 -------- configitems | 1 - configure | 40 ----------------------------- cxx_features.txt | 1 - include/pqxx/config.h.in | 3 --- include/pqxx/params.hxx | 5 ++-- include/pqxx/range.hxx | 2 +- include/pqxx/util.hxx | 12 --------- pqxx_cxx_feature_checks.ac | 10 -------- src/connection.cxx | 2 +- src/params.cxx | 2 +- src/util.cxx | 2 ++ test/unit/test_array.cxx | 3 +-- 14 files changed, 8 insertions(+), 90 deletions(-) delete mode 100644 config-tests/PQXX_HAVE_SSIZE.cxx diff --git a/cmake/pqxx_cxx_feature_checks.cmake b/cmake/pqxx_cxx_feature_checks.cmake index 794c5706f..bd01142c6 100644 --- a/cmake/pqxx_cxx_feature_checks.cmake +++ b/cmake/pqxx_cxx_feature_checks.cmake @@ -43,10 +43,6 @@ try_compile( PQXX_HAVE_SOURCE_LOCATION ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_SOURCE_LOCATION.cxx ) -try_compile( - PQXX_HAVE_SSIZE ${PROJECT_BINARY_DIR} - SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_SSIZE.cxx -) try_compile( PQXX_HAVE_STRERROR_R ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_STRERROR_R.cxx diff --git a/config-tests/PQXX_HAVE_SSIZE.cxx b/config-tests/PQXX_HAVE_SSIZE.cxx deleted file mode 100644 index 1c95aae54..000000000 --- a/config-tests/PQXX_HAVE_SSIZE.cxx +++ /dev/null @@ -1,11 +0,0 @@ -// Feature check for 'PQXX_HAVE_SSIZE'. -// Generated by generate_cxx_checks.py. -#include -#if !defined(__cpp_lib_ssize) -# error "No PQXX_HAVE_SSIZE: __cpp_lib_ssize is not set." -#endif -#if !__cpp_lib_ssize -# error "No PQXX_HAVE_SSIZE: __cpp_lib_ssize is false." -#endif - -int main() {} diff --git a/configitems b/configitems index 51916d221..e4f653b8a 100644 --- a/configitems +++ b/configitems @@ -15,7 +15,6 @@ PQXX_HAVE_PATH public compiler PQXX_HAVE_POLL internal compiler PQXX_HAVE_SLEEP_FOR internal compiler PQXX_HAVE_SOURCE_LOCATION public compiler -PQXX_HAVE_SSIZE public compiler PQXX_HAVE_STRERROR_R public compiler PQXX_HAVE_STRERROR_S public compiler PQXX_HAVE_THREAD_LOCAL internal compiler diff --git a/configure b/configure index 6e7a2e03f..66a55e2e8 100755 --- a/configure +++ b/configure @@ -17738,46 +17738,6 @@ fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_SOURCE_LOCATION" >&5 printf "%s\n" "$PQXX_HAVE_SOURCE_LOCATION" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_SSIZE" >&5 -printf %s "checking PQXX_HAVE_SSIZE... " >&6; } -PQXX_HAVE_SSIZE=yes -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -// Feature check for 'PQXX_HAVE_SSIZE'. - -// Generated by generate_cxx_checks.py. - -#include - -#if !defined(__cpp_lib_ssize) - -# error "No PQXX_HAVE_SSIZE: __cpp_lib_ssize is not set." - -#endif - -#if !__cpp_lib_ssize - -# error "No PQXX_HAVE_SSIZE: __cpp_lib_ssize is false." - -#endif - - - -int main() {} - - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO" -then : - -printf "%s\n" "#define PQXX_HAVE_SSIZE 1" >>confdefs.h - -else $as_nop - PQXX_HAVE_SSIZE=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_SSIZE" >&5 -printf "%s\n" "$PQXX_HAVE_SSIZE" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_STRERROR_R" >&5 printf %s "checking PQXX_HAVE_STRERROR_R... " >&6; } PQXX_HAVE_STRERROR_R=yes diff --git a/cxx_features.txt b/cxx_features.txt index d656c8d8d..464415362 100644 --- a/cxx_features.txt +++ b/cxx_features.txt @@ -15,4 +15,3 @@ PQXX_HAVE_CONCEPTS __cpp_concepts PQXX_HAVE_MULTIDIM __cpp_multidimensional_subscript PQXX_HAVE_SOURCE_LOCATION __cpp_lib_source_location -PQXX_HAVE_SSIZE __cpp_lib_ssize diff --git a/include/pqxx/config.h.in b/include/pqxx/config.h.in index b37533332..b07152fbc 100644 --- a/include/pqxx/config.h.in +++ b/include/pqxx/config.h.in @@ -90,9 +90,6 @@ /* Define if this feature is available. */ #undef PQXX_HAVE_SOURCE_LOCATION -/* Define if this feature is available. */ -#undef PQXX_HAVE_SSIZE - /* Define if this feature is available. */ #undef PQXX_HAVE_STRERROR_R diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index 78d794a82..b14117974 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -52,16 +52,15 @@ public: // C++20: constexpr. /// Get the number of parameters currently in this `params`. - [[nodiscard]] auto size() const noexcept { return m_params.size(); } + [[nodiscard]] constexpr 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); } + [[nodiscard]] constexpr auto ssize() const { return std::ssize(m_params); } /// Append a null value. void append() &; diff --git a/include/pqxx/range.hxx b/include/pqxx/range.hxx index d689c9b9a..ef5afa8ff 100644 --- a/include/pqxx/range.hxx +++ b/include/pqxx/range.hxx @@ -413,7 +413,7 @@ template struct string_traits> { if (value.empty()) { - if ((end - begin) <= internal::ssize(s_empty)) + if ((end - begin) <= std::ssize(s_empty)) throw conversion_overrun{s_overrun.c_str()}; char *here = begin + s_empty.copy(begin, std::size(s_empty)); *here++ = '\0'; diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index 6fb4126c8..f1b1ce1f8 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -489,18 +489,6 @@ unesc_bin(std::string_view escaped_data, std::byte buffer[]); bytes PQXX_LIBEXPORT unesc_bin(std::string_view escaped_data); -/// Transitional: std::ssize(), or custom implementation if not available. -template auto ssize(T const &c) -{ -#if defined(PQXX_HAVE_SSIZE) - return std::ssize(c); -#else - using signed_t = std::make_signed_t; - return static_cast(std::size(c)); -#endif // PQXX_HAVE_SSIZe -} - - /// Helper for determining a function's parameter types. /** This function has no definition. It's not meant to be actually called. * It's just there for pattern-matching in the compiler, so we can use its diff --git a/pqxx_cxx_feature_checks.ac b/pqxx_cxx_feature_checks.ac index 86f28157c..540d4e4ba 100644 --- a/pqxx_cxx_feature_checks.ac +++ b/pqxx_cxx_feature_checks.ac @@ -109,16 +109,6 @@ AC_COMPILE_IFELSE( [Define if this feature is available.]), PQXX_HAVE_SOURCE_LOCATION=no) AC_MSG_RESULT($PQXX_HAVE_SOURCE_LOCATION) -AC_MSG_CHECKING([PQXX_HAVE_SSIZE]) -PQXX_HAVE_SSIZE=yes -AC_COMPILE_IFELSE( - [read_test(PQXX_HAVE_SSIZE.cxx)], - AC_DEFINE( - [PQXX_HAVE_SSIZE], - 1, - [Define if this feature is available.]), - PQXX_HAVE_SSIZE=no) -AC_MSG_RESULT($PQXX_HAVE_SSIZE) AC_MSG_CHECKING([PQXX_HAVE_STRERROR_R]) PQXX_HAVE_STRERROR_R=yes AC_COMPILE_IFELSE( diff --git a/src/connection.cxx b/src/connection.cxx index c95e13c77..1d2ad8dfc 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -909,7 +909,7 @@ void pqxx::connection::write_copy_line(std::string_view line) { static std::string const err_prefix{"Error writing to table: "}; auto const size{check_cast( - internal::ssize(line), "Line in stream_to is too long to process."sv)}; + std::ssize(line), "Line in stream_to is too long to process."sv)}; if (PQputCopyData(m_conn, line.data(), size) <= 0) [[unlikely]] throw failure{err_prefix + err_msg()}; if (PQputCopyData(m_conn, "\n", 1) <= 0) [[unlikely]] diff --git a/src/params.cxx b/src/params.cxx index ad1e38d4d..68c24583e 100644 --- a/src/params.cxx +++ b/src/params.cxx @@ -110,7 +110,7 @@ pqxx::internal::c_params pqxx::params::make_c_params() const { p.values.push_back(reinterpret_cast(std::data(value))); p.lengths.push_back( - check_cast(internal::ssize(value), s_overflow)); + check_cast(std::ssize(value), s_overflow)); } p.formats.push_back(param_format(value)); diff --git a/src/util.cxx b/src/util.cxx index 7367544ad..1b7cc0735 100644 --- a/src/util.cxx +++ b/src/util.cxx @@ -109,6 +109,8 @@ constexpr std::array hex_digits{ /// Translate a number (must be between 0 and 16 exclusive) to a hex digit. constexpr char hex_digit(int c) noexcept { + PQXX_ASSUME(c >= 0); + PQXX_ASSUME(c < std::ssize(hex_digits)); return hex_digits.at(static_cast(c)); } diff --git a/test/unit/test_array.cxx b/test/unit/test_array.cxx index ac1c47a63..c8861f2ac 100644 --- a/test/unit/test_array.cxx +++ b/test/unit/test_array.cxx @@ -730,8 +730,7 @@ void test_array_iterates_in_row_major_order() PQXX_CHECK_EQUAL(*array.crbegin(), 9, "Bad crbegin()."); PQXX_CHECK_EQUAL(*(array.crend() - 1), 1, "Bad crend()."); PQXX_CHECK_EQUAL(std::size(array), 9u, "Bad array size."); - // C++20: Use std::ssize() instead. - PQXX_CHECK_EQUAL(array.ssize(), 9, "Bad array ssize()."); + PQXX_CHECK_EQUAL(std::ssize(array), 9, "Bad array ssize()."); PQXX_CHECK_EQUAL(array.front(), 1, "Bad front()."); PQXX_CHECK_EQUAL(array.back(), 9, "Bad back()."); } From 975ec3903186daddd6a26820d9cfa5093dcf17e7 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 02:17:32 +0100 Subject: [PATCH 07/64] Assume `` & ``. --- include/pqxx/blob.hxx | 11 ++--------- include/pqxx/connection.hxx | 6 +----- include/pqxx/params.hxx | 1 - include/pqxx/strconv.hxx | 6 +----- include/pqxx/transaction_base.hxx | 3 +-- include/pqxx/types.hxx | 5 +---- 6 files changed, 6 insertions(+), 26 deletions(-) diff --git a/include/pqxx/blob.hxx b/include/pqxx/blob.hxx index c4a776aff..0f65dd0b1 100644 --- a/include/pqxx/blob.hxx +++ b/include/pqxx/blob.hxx @@ -23,15 +23,8 @@ # include #endif -// C++20: Assume support. -#if __has_include() -# include -#endif - -// C++20: Assume support. -#if __has_include() -# include -#endif +#include +#include #include "pqxx/dbtransaction.hxx" diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index a3b7609d5..58c369d3d 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -24,15 +24,11 @@ #include #include #include +#include #include #include #include -// Double-check in order to suppress an overzealous Visual C++ warning (#418). -#if defined(PQXX_HAVE_CONCEPTS) && __has_include() -# include -#endif - #include "pqxx/errorhandler.hxx" #include "pqxx/except.hxx" #include "pqxx/internal/concat.hxx" diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index b14117974..aeb19c75d 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -50,7 +50,6 @@ public: */ void reserve(std::size_t n) &; - // C++20: constexpr. /// Get the number of parameters currently in this `params`. [[nodiscard]] constexpr auto size() const noexcept { return m_params.size(); } diff --git a/include/pqxx/strconv.hxx b/include/pqxx/strconv.hxx index fa61b4d30..40bff6c1b 100644 --- a/include/pqxx/strconv.hxx +++ b/include/pqxx/strconv.hxx @@ -19,15 +19,11 @@ #include #include #include +#include #include #include #include -// C++20: Assume support. -#if __has_include() -# include -#endif - #include "pqxx/except.hxx" #include "pqxx/util.hxx" #include "pqxx/zview.hxx" diff --git a/include/pqxx/transaction_base.hxx b/include/pqxx/transaction_base.hxx index fdda1b4ee..8fedd71f6 100644 --- a/include/pqxx/transaction_base.hxx +++ b/include/pqxx/transaction_base.hxx @@ -1042,9 +1042,8 @@ public: [[deprecated("Read variables using SQL SHOW statements.")]] std::string get_variable(std::string_view); - // C++20: constexpr. /// Transaction name, if you passed one to the constructor; or empty string. - [[nodiscard]] std::string_view name() const & noexcept { return m_name; } + [[nodiscard]] constexpr std::string_view name() const & noexcept { return m_name; } protected: /// Create a transaction (to be called by implementation classes only). diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index a1514be38..9858672e7 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -16,10 +16,7 @@ #include #include #include - -#if defined(PQXX_HAVE_CONCEPTS) && __has_include() -# include -#endif +#include namespace pqxx From 549d452d119d0c613938fda7624f0375545530ec Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 02:19:30 +0100 Subject: [PATCH 08/64] Remove ``. --- include/pqxx/internal/header-pre.hxx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/pqxx/internal/header-pre.hxx b/include/pqxx/internal/header-pre.hxx index 0b1eb80e3..35f6df45d 100644 --- a/include/pqxx/internal/header-pre.hxx +++ b/include/pqxx/internal/header-pre.hxx @@ -63,13 +63,6 @@ # define PQXX_CPLUSPLUS __cplusplus #endif -// C++20: No longer needed. -// Enable ISO-646 alternative operaotr representations: "and" instead of "&&" -// etc. on older compilers. C++20 removes this header. -#if PQXX_CPLUSPLUS <= 201703L && __has_include() -# include -#endif - #if defined(PQXX_HAVE_GCC_PURE) /// Declare function "pure": no side effects, only reads globals and its args. # define PQXX_PURE __attribute__((pure)) From 64554e1c654e1afd54aa51dc2f608364a5f0af54 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 02:32:08 +0100 Subject: [PATCH 09/64] Various little C++20 updates. --- include/pqxx/connection.hxx | 1 - include/pqxx/zview.hxx | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index 58c369d3d..d56e7534e 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -819,7 +819,6 @@ public: //@} - // C++20: constexpr. Breaks ABI. /// Suffix unique number to name to make it unique within session context. /** Used internally to generate identifiers for SQL objects (such as cursors * and nested transactions) based on a given human-readable base name. diff --git a/include/pqxx/zview.hxx b/include/pqxx/zview.hxx index a35460e97..b40f95839 100644 --- a/include/pqxx/zview.hxx +++ b/include/pqxx/zview.hxx @@ -64,9 +64,8 @@ public: std::string_view(std::forward(args)...) {} - // C++20: constexpr. /// @warning There's an implicit conversion from `std::string`. - zview(std::string const &str) noexcept : + constexpr zview(std::string const &str) noexcept : std::string_view{str.c_str(), str.size()} {} @@ -157,9 +156,8 @@ inline constexpr char const *as_c_string(pqxx::zview str) noexcept { return str.c_str(); } -// C++20: Make this constexpr. /// Get a raw C string pointer. -inline char const *as_c_string(std::string const &str) noexcept +inline constexpr char const *as_c_string(std::string const &str) noexcept { return str.c_str(); } From c8966deb23361ee718dfff8a4577340c23e247e3 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 02:37:47 +0100 Subject: [PATCH 10/64] Raise baseline C++ version to C++20. --- configure | 6 +++--- configure.ac | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/configure b/configure index 66a55e2e8..6e984fed0 100755 --- a/configure +++ b/configure @@ -17061,7 +17061,7 @@ add_compiler_opts() { # It's tempting to use Autoconf Archive's AX_CXX_COMPILE_STDCXX_17 for this, -# but it's 2022 and the C++20 equivalent isn't quite ready for use. +# but it's not getting a lot of maintenance. # Seems simpler and more reliable for the user to arrange for the desired # language versions by setting the appropriate option for their compiler. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for sufficient C++ language/library level" >&5 @@ -17070,8 +17070,8 @@ sufficient_cxx=yes cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ - #if __cplusplus < 201611L - #error "Need C++17 or better." + #if __cplusplus < 202002L + #error "Need C++20 or better." #endif _ACEOF diff --git a/configure.ac b/configure.ac index 064ab2243..8d105a8c1 100644 --- a/configure.ac +++ b/configure.ac @@ -92,15 +92,15 @@ add_compiler_opts() { # It's tempting to use Autoconf Archive's AX_CXX_COMPILE_STDCXX_17 for this, -# but it's 2022 and the C++20 equivalent isn't quite ready for use. +# but it's not getting a lot of maintenance. # Seems simpler and more reliable for the user to arrange for the desired # language versions by setting the appropriate option for their compiler. AC_MSG_CHECKING([for sufficient C++ language/library level]) sufficient_cxx=yes AC_COMPILE_IFELSE( [AC_LANG_SOURCE([ - #if __cplusplus < 201611L - #error "Need C++17 or better." + #if __cplusplus < 202002L + #error "Need C++20 or better." #endif ])], sufficient_cxx=yes, From 0a98a8e4b8073e16e9781f7b3c43bdc1c3cdfe67 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 02:42:11 +0100 Subject: [PATCH 11/64] Format. --- include/pqxx/params.hxx | 5 ++++- include/pqxx/transaction_base.hxx | 5 ++++- src/params.cxx | 3 +-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index aeb19c75d..ad76cf06b 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -51,7 +51,10 @@ public: void reserve(std::size_t n) &; /// Get the number of parameters currently in this `params`. - [[nodiscard]] constexpr auto size() const noexcept { return m_params.size(); } + [[nodiscard]] constexpr auto size() const noexcept + { + return m_params.size(); + } /// Get the number of parameters (signed). /** Unlike `size()`, this is not yet `noexcept`. That's because C++17's diff --git a/include/pqxx/transaction_base.hxx b/include/pqxx/transaction_base.hxx index 8fedd71f6..5d9599c2f 100644 --- a/include/pqxx/transaction_base.hxx +++ b/include/pqxx/transaction_base.hxx @@ -1043,7 +1043,10 @@ public: std::string get_variable(std::string_view); /// Transaction name, if you passed one to the constructor; or empty string. - [[nodiscard]] constexpr std::string_view name() const & noexcept { return m_name; } + [[nodiscard]] constexpr std::string_view name() const & noexcept + { + return m_name; + } protected: /// Create a transaction (to be called by implementation classes only). diff --git a/src/params.cxx b/src/params.cxx index 68c24583e..9c19de05a 100644 --- a/src/params.cxx +++ b/src/params.cxx @@ -109,8 +109,7 @@ pqxx::internal::c_params pqxx::params::make_c_params() const else { p.values.push_back(reinterpret_cast(std::data(value))); - p.lengths.push_back( - check_cast(std::ssize(value), s_overflow)); + p.lengths.push_back(check_cast(std::ssize(value), s_overflow)); } p.formats.push_back(param_format(value)); From 5c46ebfe3abb480d25994227ff7e875953614ffe Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 02:50:33 +0100 Subject: [PATCH 12/64] Drop some workarounds for missing concepts. --- include/pqxx/connection.hxx | 4 ++-- include/pqxx/params.hxx | 2 +- include/pqxx/stream_to.hxx | 4 ++-- include/pqxx/types.hxx | 43 ------------------------------------- 4 files changed, 5 insertions(+), 48 deletions(-) diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index d56e7534e..6348b2406 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -1001,7 +1001,7 @@ public: * yourself. It's a bit of extra work, but it can in rare cases let you * eliminate some duplicate work in quoting them repeatedly. */ - template + template inline std::string quote_columns(STRINGS const &columns) const; // TODO: Make "into buffer" variant to eliminate a string allocation. @@ -1472,7 +1472,7 @@ template inline std::string connection::quote(T const &t) const } -template +template inline std::string connection::quote_columns(STRINGS const &columns) const { return separated_list( diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index ad76cf06b..54799e911 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -146,7 +146,7 @@ public: } /// Append all elements of `range` as parameters. - template void append_multi(RANGE const &range) & + template void append_multi(RANGE const &range) & { #if defined(PQXX_HAVE_CONCEPTS) if constexpr (std::ranges::sized_range) diff --git a/include/pqxx/stream_to.hxx b/include/pqxx/stream_to.hxx index 6c4685307..79abc5723 100644 --- a/include/pqxx/stream_to.hxx +++ b/include/pqxx/stream_to.hxx @@ -134,7 +134,7 @@ public: * @param path A @ref table_path designating the target table. * @param columns The columns to which the stream should write. */ - template + template static stream_to table(transaction_base &tx, table_path path, COLUMNS const &columns) { @@ -151,7 +151,7 @@ public: * @param path A @ref table_path designating the target table. * @param columns The columns to which the stream should write. */ - template + template static stream_to table(transaction_base &tx, std::string_view path, COLUMNS const &columns) { diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index 9858672e7..03ded3115 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -112,49 +112,6 @@ concept potential_binary = #endif // PQXX_HAVE_CONCEPTS -// C++20: Retire these compatibility definitions. -#if defined(PQXX_HAVE_CONCEPTS) - -/// Template argument type for a range. -/** This is a concept, so only available in C++20 or better. In pre-C++20 - * environments it's just an alias for @ref typename. - */ -# define PQXX_RANGE_ARG std::ranges::range - -/// Template argument type for @ref char_string. -/** This is a concept, so only available in C++20 or better. In pre-C++20 - * environments it's just an alias for @ref typename. - */ -# define PQXX_CHAR_STRING_ARG pqxx::char_string - -/// Template argument type for @ref char_strings -/** This is a concept, so only available in C++20 or better. In pre-C++20 - * environments it's just an alias for @ref typename. - */ -# define PQXX_CHAR_STRINGS_ARG pqxx::char_strings - -#else // PQXX_HAVE_CONCEPTS - -/// Template argument type for a range. -/** This is a concept, so only available in C++20 or better. In pre-C++20 - * environments it's just an alias for @ref typename. - */ -# define PQXX_RANGE_ARG typename - -/// Template argument type for @ref char_string. -/** This is a concept, so only available in C++20 or better. In pre-C++20 - * environments it's just an alias for @ref typename. - */ -# define PQXX_CHAR_STRING_ARG typename - -/// Template argument type for @ref char_strings -/** This is a concept, so only available in C++20 or better. In pre-C++20 - * environments it's just an alias for @ref typename. - */ -# define PQXX_CHAR_STRINGS_ARG typename - -#endif // PQXX_HAVE_CONCEPTS - /// Marker for @ref stream_from constructors: "stream from table." /** @deprecated Use @ref stream_from::table() instead. */ From 0e323915b81d5457b5567146f0b86fd07564f4f3 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 02:54:28 +0100 Subject: [PATCH 13/64] More workarounds for no concepts. --- include/pqxx/blob.hxx | 46 ------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/include/pqxx/blob.hxx b/include/pqxx/blob.hxx index 0f65dd0b1..9621fd44e 100644 --- a/include/pqxx/blob.hxx +++ b/include/pqxx/blob.hxx @@ -111,7 +111,6 @@ public: return buf.subspan(0, raw_read(std::data(buf), std::size(buf))); } -#if defined(PQXX_HAVE_CONCEPTS) /// 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. @@ -122,27 +121,7 @@ public: { return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; } -#else // PQXX_HAVE_CONCEPTS - /// 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`. - * - * 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 bytes_view read(std::vector &buf) - { - return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; - } -#endif // PQXX_HAVE_CONCEPTS -#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 @@ -166,31 +145,6 @@ public: { 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 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 From 492e9854495f5954e21f0289daaad29e423a313f Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 02:56:54 +0100 Subject: [PATCH 14/64] Bump CircleCI C++ version. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 936eb6e78..956e515a1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,7 +46,7 @@ jobs: --enable-maintainer-mode \ --enable-audit \ --enable-shared --disable-static \ - CXXFLAGS='-O3 -std=c++17' \ + CXXFLAGS='-O3 -std=c++20' \ CXX=clang++ - store_artifacts: path: config.log From 316038c56d52102f62dcf2aba3bd0731be6a34d7 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 03:01:38 +0100 Subject: [PATCH 15/64] Bump C++ version in github workflow. --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1852df5b6..678f363a5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -48,7 +48,7 @@ jobs: createdb "$(whoami)" # Using clang because currently the gcc build fails with an # address sanitizer (asan) link error. - ./configure --enable-maintainer-mode --enable-audit --disable-static CXXFLAGS='-O1 -std=c++17' CXX=clang++ + ./configure --enable-maintainer-mode --enable-audit --disable-static CXXFLAGS='-O1 -std=c++20' CXX=clang++ make -j4 check || (cat test-suite.log && exit 1 || true) - name: Perform CodeQL Analysis From aa06444a1b201574703a23b05d8d8f202aa005ae Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 12:27:40 +0100 Subject: [PATCH 16/64] Update error message to say C++20 is minimum. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8d105a8c1..d144a6160 100644 --- a/configure.ac +++ b/configure.ac @@ -108,7 +108,7 @@ AC_COMPILE_IFELSE( AC_MSG_RESULT($sufficient_cxx) if test "$sufficient_cxx" != "yes" then - AC_MSG_ERROR([This libpqxx version needs at least C++17.]) + AC_MSG_ERROR([This libpqxx version needs at least C++20.]) fi From 50f126340148a29d979be7a1c8d32c9874f7b52d Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 12:44:23 +0100 Subject: [PATCH 17/64] Reflect raised baseline in more places. --- CMakeLists.txt | 2 +- README.md | 14 ++------------ configure.ac | 2 +- include/pqxx/params.hxx | 1 + include/pqxx/util.hxx | 15 ++++++--------- requirements.json | 2 +- src/strconv.cxx | 2 +- src/time.cxx | 2 -- tools/test_all.py | 2 +- 9 files changed, 14 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d4ef4133..82fec4622 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ project( ) if(NOT "${CMAKE_CXX_STANDARD}") - set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD 20) endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/README.md b/README.md index 49c54f1a2..2d58de3ec 100644 --- a/README.md +++ b/README.md @@ -31,18 +31,8 @@ commits in `master`. For example, to get version 7.1.1: Upgrade notes ------------- -**The 7.x versions require at least C++17.** Make sure your compiler is up to -date. For libpqxx 8.x you will need at least C++20. - -Also, **7.0 makes some breaking changes in rarely used APIs:** - -* There is just a single `connection` class. It connects immediately. -* Custom `connection` classes are no longer supported. -* It's no longer possible to reactivate a connection once it's been closed. -* The API for defining string conversions has changed. - -If you're defining your own type conversions, **7.1 requires one additional -field in your `nullness` traits.** +**The 8.x versions require at least C++20.** Make sure your compiler is up to +date. Building libpqxx diff --git a/configure.ac b/configure.ac index d144a6160..654ce3b6f 100644 --- a/configure.ac +++ b/configure.ac @@ -91,7 +91,7 @@ add_compiler_opts() { } -# It's tempting to use Autoconf Archive's AX_CXX_COMPILE_STDCXX_17 for this, +# It's tempting to use Autoconf Archive's AX_CXX_COMPILE_STDCXX_* for this, # but it's not getting a lot of maintenance. # Seems simpler and more reliable for the user to arrange for the desired # language versions by setting the appropriate option for their compiler. diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index 54799e911..732176469 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -56,6 +56,7 @@ public: return m_params.size(); } + // C++20: noexcept. /// 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 diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index f1b1ce1f8..57ac93225 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -328,20 +328,17 @@ using bytes_view = std::conditional< /// Cast binary data to a type that libpqxx will recognise as binary. /** There are many different formats for storing binary data in memory. You * may have yours as a `std::string`, or a `std::vector`, or one of - * many other types. + * many other types. In libpqxx we commend a container of `std::byte`. * - * But for libpqxx to recognise your data as binary, it needs to be a - * `pqxx::bytes`, or a `pqxx::bytes_view`; or in C++20 or better, any - * contiguous block of `std::byte`. + * For libpqxx to recognise your data as binary, it needs to be a + * `pqxx::bytes`, or a `pqxx::bytes_view`; but any contiguous block of + * `std::byte` should do. * * Use `binary_cast` as a convenience helper to cast your data as a * `pqxx::bytes_view`. * - * @warning There are two things you should be aware of! First, the data must - * be contiguous in memory. In C++20 the compiler will enforce this, but in - * C++17 it's your own problem. Second, you must keep the object where you - * store the actual data alive for as long as you might use this function's - * return value. + * @warning You must keep the storage holding the actual data alive for as + * long as you might use this function's return value. */ template bytes_view binary_cast(TYPE const &data) diff --git a/requirements.json b/requirements.json index e4373be4c..0e2d0531c 100644 --- a/requirements.json +++ b/requirements.json @@ -1,6 +1,6 @@ { "description": "Minimum versions needed of various things.", - "c++": "17", + "c++": "20", "libpq": "12.0", "postgresql": "12.0", "gcc": "9", diff --git a/src/strconv.cxx b/src/strconv.cxx index 734d2cc7d..0a491f494 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -96,7 +96,7 @@ template constexpr inline char *bottom_to_buf(char *end) // any modern-day system I can think of, a signed type's bottom value // has no positive equivalent. Luckily the C++ standards committee can't // think of any exceptions either, so it's the required representation as - // of C++20. We'll assume it right now, while still on C++17. + // of C++20. static_assert(-(bottom + 1) == top); // The unsigned version of T does have the unsigned version of bottom. diff --git a/src/time.cxx b/src/time.cxx index 103779790..e216c1ade 100644 --- a/src/time.cxx +++ b/src/time.cxx @@ -10,8 +10,6 @@ #include "pqxx/internal/header-post.hxx" -// std::chrono::year_month_day is C++20, so let's worry a bit less about C++17 -// compatibility in this file. #if defined(PQXX_HAVE_YEAR_MONTH_DAY) namespace { diff --git a/tools/test_all.py b/tools/test_all.py index 9d6c02f44..9881fae69 100755 --- a/tools/test_all.py +++ b/tools/test_all.py @@ -55,7 +55,7 @@ CLANG = [f'clang++-{ver}' for ver in CLANG_VERSIONS] CXX = GCC + CLANG -DIALECTS = ['17', '20', '2b'] +DIALECTS = ['20', '23'] STDLIB = ( '', From 3f2aacad33ce3c823d183ae1576444d82cc37e43 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 14:58:05 +0100 Subject: [PATCH 18/64] Regenerate `configure`. --- config/Makefile.in | 2 +- configure | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/Makefile.in b/config/Makefile.in index 51586bda2..9b1454986 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -124,7 +124,7 @@ am__can_run_installinfo = \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \ - config.sub install-sh ltmain.sh missing mkinstalldirs + config.sub depcomp install-sh ltmain.sh missing mkinstalldirs DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ diff --git a/configure b/configure index 6e984fed0..2e7b96cd1 100755 --- a/configure +++ b/configure @@ -17060,7 +17060,7 @@ add_compiler_opts() { } -# It's tempting to use Autoconf Archive's AX_CXX_COMPILE_STDCXX_17 for this, +# It's tempting to use Autoconf Archive's AX_CXX_COMPILE_STDCXX_* for this, # but it's not getting a lot of maintenance. # Seems simpler and more reliable for the user to arrange for the desired # language versions by setting the appropriate option for their compiler. @@ -17086,7 +17086,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext printf "%s\n" "$sufficient_cxx" >&6; } if test "$sufficient_cxx" != "yes" then - as_fn_error $? "This libpqxx version needs at least C++17." "$LINENO" 5 + as_fn_error $? "This libpqxx version needs at least C++20." "$LINENO" 5 fi From a5fe9c5ff78fcaf442b243c0e9cb0ecd86be7527 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 15:33:39 +0100 Subject: [PATCH 19/64] Retire `binarystring`. This type has been deprecated since 7.2.0, more than 4 years ago. --- NEWS | 4 +- config/Makefile.in | 2 +- include/CMakeLists.txt | 1 - include/Makefile.am | 1 - include/Makefile.in | 1 - include/pqxx/binarystring | 6 - include/pqxx/binarystring.hxx | 235 ------------------ include/pqxx/connection.hxx | 4 +- .../pqxx/internal/statement_parameters.hxx | 1 - include/pqxx/params.hxx | 6 - include/pqxx/pqxx | 1 - include/pqxx/transaction_base.hxx | 6 - include/pqxx/types.hxx | 1 - src/Makefile.am | 1 - src/Makefile.in | 13 +- src/binarystring.cxx | 110 -------- src/connection.cxx | 7 - src/params.cxx | 6 - test/Makefile.am | 1 - test/Makefile.in | 18 +- test/unit/test_binarystring.cxx | 216 ---------------- test/unit/test_prepared_statement.cxx | 10 - 22 files changed, 14 insertions(+), 637 deletions(-) delete mode 100644 include/pqxx/binarystring delete mode 100644 include/pqxx/binarystring.hxx delete mode 100644 src/binarystring.cxx delete mode 100644 test/unit/test_binarystring.cxx diff --git a/NEWS b/NEWS index 8d5c4062e..3588f21c1 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ 8.0.0 - C++20 is now the oldest C++ version that libpqxx supports. - - Compiler must support integral conversions in `charconv`. + - Assume compiler supports integral conversions in `charconv`. + - Assume compiler supports spans, ranges, and `cmp_less` etc. + - Retired `binarystring` and its headers. 7.10.1 - Fix string conversion buffer budget for arrays containing nulls. (#921) - Remove `-fanalyzer` option again; gcc is still broken. diff --git a/config/Makefile.in b/config/Makefile.in index 9b1454986..51586bda2 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -124,7 +124,7 @@ am__can_run_installinfo = \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \ - config.sub depcomp install-sh ltmain.sh missing mkinstalldirs + config.sub install-sh ltmain.sh missing mkinstalldirs DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 263dd2e9e..6a1746150 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -19,7 +19,6 @@ install( PATTERN *.hxx # TODO: Is there any way to do this with CMake's globbing? PATTERN array - PATTERN binarystring PATTERN blob PATTERN composite PATTERN connection diff --git a/include/Makefile.am b/include/Makefile.am index 5ac0d968d..307006b57 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -2,7 +2,6 @@ SUBDIRS = pqxx nobase_include_HEADERS= pqxx/pqxx \ pqxx/array pqxx/array.hxx \ - pqxx/binarystring pqxx/binarystring.hxx \ pqxx/blob pqxx/blob.hxx \ pqxx/composite pqxx/composite.hxx \ pqxx/connection pqxx/connection.hxx \ diff --git a/include/Makefile.in b/include/Makefile.in index beced7222..788aef9ab 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -350,7 +350,6 @@ with_postgres_lib = @with_postgres_lib@ SUBDIRS = pqxx nobase_include_HEADERS = pqxx/pqxx \ pqxx/array pqxx/array.hxx \ - pqxx/binarystring pqxx/binarystring.hxx \ pqxx/blob pqxx/blob.hxx \ pqxx/composite pqxx/composite.hxx \ pqxx/connection pqxx/connection.hxx \ diff --git a/include/pqxx/binarystring b/include/pqxx/binarystring deleted file mode 100644 index 77551d9f7..000000000 --- a/include/pqxx/binarystring +++ /dev/null @@ -1,6 +0,0 @@ -/** 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" diff --git a/include/pqxx/binarystring.hxx b/include/pqxx/binarystring.hxx deleted file mode 100644 index fe113a42d..000000000 --- a/include/pqxx/binarystring.hxx +++ /dev/null @@ -1,235 +0,0 @@ -/* Deprecated representation for raw, binary data. - * - * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/binarystring instead. - * - * Copyright (c) 2000-2025, 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 , not ." -#endif - -#include -#include -#include - -#include "pqxx/result.hxx" -#include "pqxx/strconv.hxx" - -namespace pqxx -{ -class binarystring; -template<> struct string_traits; - - -/// Binary data corresponding to PostgreSQL's "BYTEA" binary-string type. -/** @ingroup escaping-functions - * @deprecated Use @c bytes and @c bytes_view 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 not thread-safe. 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 = 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; - - [[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 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(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(get()); - } - - /// Read data as a @c bytes_view. - [[nodiscard]] pqxx::bytes_view bytes_view() const - { - return pqxx::bytes_view{bytes(), size()}; - } - -private: - std::shared_ptr m_buf; - size_type m_size{0}; -}; - - -template<> struct nullness : no_null -{}; - - -/// 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 -{ - 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 (std::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 buf{ - new unsigned char[size], [](unsigned char const *x) { delete[] x; }}; - pqxx::internal::unesc_bin(text, reinterpret_cast(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 diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index 6348b2406..ed8a884cc 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -33,6 +33,7 @@ #include "pqxx/except.hxx" #include "pqxx/internal/concat.hxx" #include "pqxx/params.hxx" +#include "pqxx/result.hxx" #include "pqxx/separated_list.hxx" #include "pqxx/strconv.hxx" #include "pqxx/types.hxx" @@ -1012,9 +1013,6 @@ public: template [[nodiscard]] inline std::string quote(T const &t) const; - [[deprecated("Use std::byte for binary data.")]] std::string - quote(binarystring const &) const; - // TODO: Make "into buffer" variant to eliminate a string allocation. /// Escape and quote binary data for use as a BYTEA value in SQL statement. [[nodiscard]] std::string quote(bytes_view bytes) const; diff --git a/include/pqxx/internal/statement_parameters.hxx b/include/pqxx/internal/statement_parameters.hxx index d002b6b17..3a44bb774 100644 --- a/include/pqxx/internal/statement_parameters.hxx +++ b/include/pqxx/internal/statement_parameters.hxx @@ -18,7 +18,6 @@ #include #include -#include "pqxx/binarystring.hxx" #include "pqxx/strconv.hxx" #include "pqxx/util.hxx" diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index 732176469..f28d43c6c 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -109,12 +109,6 @@ public: /// Append a non-null binary parameter. void append(bytes &&) &; - /// @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 void append(pqxx::internal::dynamic_params const &value) & diff --git a/include/pqxx/pqxx b/include/pqxx/pqxx index c4110d837..8ed5cfd97 100644 --- a/include/pqxx/pqxx +++ b/include/pqxx/pqxx @@ -2,7 +2,6 @@ #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" diff --git a/include/pqxx/transaction_base.hxx b/include/pqxx/transaction_base.hxx index 5d9599c2f..312e78eed 100644 --- a/include/pqxx/transaction_base.hxx +++ b/include/pqxx/transaction_base.hxx @@ -259,12 +259,6 @@ public: return conn().quote(t); } - [[deprecated("Use bytes instead of binarystring.")]] std::string - quote(binarystring const &t) const - { - return conn().quote(t.bytes_view()); - } - /// Binary-escape and quote a binary string for use as an SQL constant. [[deprecated("Use quote(pqxx::bytes_view).")]] std::string quote_raw(unsigned char const bin[], std::size_t len) const diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index 03ded3115..a0f1f7d5e 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -42,7 +42,6 @@ using large_object_size_type = int64_t; // Forward declarations, to help break compilation dependencies. // These won't necessarily include all classes in libpqxx. -class binarystring; class connection; class const_result_iterator; class const_reverse_result_iterator; diff --git a/src/Makefile.am b/src/Makefile.am index dfd520941..f41224fa2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,6 @@ lib_LTLIBRARIES = libpqxx.la libpqxx_la_SOURCES = \ array.cxx \ - binarystring.cxx \ blob.cxx \ connection.cxx \ cursor.cxx \ diff --git a/src/Makefile.in b/src/Makefile.in index 49dad8007..b79c9b007 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -134,9 +134,9 @@ am__uninstall_files_from_dir = { \ am__installdirs = "$(DESTDIR)$(libdir)" LTLIBRARIES = $(lib_LTLIBRARIES) libpqxx_la_LIBADD = -am_libpqxx_la_OBJECTS = array.lo binarystring.lo blob.lo connection.lo \ - cursor.lo encodings.lo errorhandler.lo except.lo field.lo \ - largeobject.lo notification.lo params.lo pipeline.lo result.lo \ +am_libpqxx_la_OBJECTS = array.lo blob.lo connection.lo cursor.lo \ + encodings.lo errorhandler.lo except.lo field.lo largeobject.lo \ + notification.lo params.lo pipeline.lo result.lo \ robusttransaction.lo sql_cursor.lo strconv.lo stream_from.lo \ stream_to.lo subtransaction.lo time.lo transaction.lo \ transaction_base.lo row.lo util.lo version.lo wait.lo @@ -162,8 +162,7 @@ am__v_at_0 = @ am__v_at_1 = depcomp = $(SHELL) $(top_srcdir)/config/depcomp am__maybe_remake_depfiles = depfiles -am__depfiles_remade = ./$(DEPDIR)/array.Plo \ - ./$(DEPDIR)/binarystring.Plo ./$(DEPDIR)/blob.Plo \ +am__depfiles_remade = ./$(DEPDIR)/array.Plo ./$(DEPDIR)/blob.Plo \ ./$(DEPDIR)/connection.Plo ./$(DEPDIR)/cursor.Plo \ ./$(DEPDIR)/encodings.Plo ./$(DEPDIR)/errorhandler.Plo \ ./$(DEPDIR)/except.Plo ./$(DEPDIR)/field.Plo \ @@ -357,7 +356,6 @@ with_postgres_lib = @with_postgres_lib@ lib_LTLIBRARIES = libpqxx.la libpqxx_la_SOURCES = \ array.cxx \ - binarystring.cxx \ blob.cxx \ connection.cxx \ cursor.cxx \ @@ -476,7 +474,6 @@ distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/array.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/binarystring.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blob.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connection.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cursor.Plo@am__quote@ # am--include-marker @@ -668,7 +665,6 @@ clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ distclean: distclean-am -rm -f ./$(DEPDIR)/array.Plo - -rm -f ./$(DEPDIR)/binarystring.Plo -rm -f ./$(DEPDIR)/blob.Plo -rm -f ./$(DEPDIR)/connection.Plo -rm -f ./$(DEPDIR)/cursor.Plo @@ -740,7 +736,6 @@ installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/array.Plo - -rm -f ./$(DEPDIR)/binarystring.Plo -rm -f ./$(DEPDIR)/blob.Plo -rm -f ./$(DEPDIR)/connection.Plo -rm -f ./$(DEPDIR)/cursor.Plo diff --git a/src/binarystring.cxx b/src/binarystring.cxx deleted file mode 100644 index 1814b4677..000000000 --- a/src/binarystring.cxx +++ /dev/null @@ -1,110 +0,0 @@ -/** Implementation of bytea (binary string) conversions. - * - * Copyright (c) 2000-2025, Jeroen T. Vermeulen. - * - * See COPYING for copyright license. If you did not receive a file called - * COPYING with this source code, please notify the distributor of this - * mistake, or contact the author. - */ -#include "pqxx-source.hxx" - -#include -#include -#include -#include -#include -#include - -extern "C" -{ -#include -} - -#include "pqxx/internal/header-pre.hxx" - -#include "pqxx/binarystring.hxx" -#include "pqxx/field.hxx" -#include "pqxx/strconv.hxx" - -#include "pqxx/internal/header-post.hxx" - - -namespace -{ -/// Copy data to a heap-allocated buffer. -std::shared_ptr - PQXX_COLD copy_to_buffer(void const *data, std::size_t len) -{ - std::shared_ptr ptr{ - // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc) - static_cast(malloc(len + 1)), std::free}; - if (not ptr) - throw std::bad_alloc{}; - ptr.get()[len] = '\0'; - std::memcpy(ptr.get(), data, len); - return ptr; -} -} // namespace - - -PQXX_COLD pqxx::binarystring::binarystring(field const &F) -{ - unsigned char const *data{ - reinterpret_cast(F.c_str())}; - m_buf = std::shared_ptr{ - PQunescapeBytea(data, &m_size), pqxx::internal::pq::pqfreemem}; - if (m_buf == nullptr) - throw std::bad_alloc{}; -} - - -pqxx::binarystring::binarystring(std::string_view s) : - m_buf{copy_to_buffer(std::data(s), std::size(s))}, m_size{std::size(s)} -{} - - -pqxx::binarystring::binarystring(void const *binary_data, std::size_t len) : - m_buf{copy_to_buffer(binary_data, len)}, m_size{len} -{} - - -bool pqxx::binarystring::operator==(binarystring const &rhs) const noexcept -{ - return (std::size(rhs) == size()) and - (std::memcmp(data(), std::data(rhs), size()) == 0); -} - - -pqxx::binarystring & -pqxx::binarystring::operator=(binarystring const &rhs) = default; - -PQXX_COLD pqxx::binarystring::const_reference -pqxx::binarystring::at(size_type n) const -{ - if (n >= m_size) - { - if (m_size == 0) - throw std::out_of_range{"Accessing empty binarystring"}; - throw std::out_of_range{ - "binarystring index out of range: " + to_string(n) + - " (should be below " + to_string(m_size) + ")"}; - } - return data()[n]; -} - - -PQXX_COLD void pqxx::binarystring::swap(binarystring &rhs) -{ - m_buf.swap(rhs.m_buf); - - // This part very obviously can't go wrong, so do it last - auto const s{m_size}; - m_size = rhs.m_size; - rhs.m_size = s; -} - - -std::string pqxx::binarystring::str() const -{ - return std::string{get(), m_size}; -} diff --git a/src/connection.cxx b/src/connection.cxx index 1d2ad8dfc..c1a36424f 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -46,7 +46,6 @@ extern "C" #include "pqxx/internal/header-pre.hxx" -#include "pqxx/binarystring.hxx" #include "pqxx/internal/wait.hxx" #include "pqxx/nontransaction.hxx" #include "pqxx/notification.hxx" @@ -1023,12 +1022,6 @@ std::string pqxx::connection::quote_raw(bytes_view bytes) const } -std::string PQXX_COLD pqxx::connection::quote(binarystring const &b) const -{ - return quote(b.bytes_view()); -} - - std::string pqxx::connection::quote(bytes_view b) const { return internal::concat("'", esc_raw(b), "'::bytea"); diff --git a/src/params.cxx b/src/params.cxx index 9c19de05a..6ba77d887 100644 --- a/src/params.cxx +++ b/src/params.cxx @@ -78,12 +78,6 @@ void pqxx::params::append(bytes &&value) & } -void PQXX_COLD pqxx::params::append(binarystring const &value) & -{ - m_params.emplace_back(value.bytes_view()); -} - - void pqxx::params::append(params &&value) & { this->reserve(std::size(value.m_params) + std::size(this->m_params)); diff --git a/test/Makefile.am b/test/Makefile.am index c6a150aa4..b00413d6e 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -79,7 +79,6 @@ runner_SOURCES = \ test89.cxx \ test90.cxx \ unit/test_array.cxx \ - unit/test_binarystring.cxx \ unit/test_blob.cxx \ unit/test_cancel_query.cxx \ unit/test_column.cxx \ diff --git a/test/Makefile.in b/test/Makefile.in index c73c756c2..2f8d43afd 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -139,10 +139,10 @@ am_runner_OBJECTS = test00.$(OBJEXT) test01.$(OBJEXT) test02.$(OBJEXT) \ test78.$(OBJEXT) test79.$(OBJEXT) test82.$(OBJEXT) \ test84.$(OBJEXT) test87.$(OBJEXT) test88.$(OBJEXT) \ test89.$(OBJEXT) test90.$(OBJEXT) unit/test_array.$(OBJEXT) \ - unit/test_binarystring.$(OBJEXT) unit/test_blob.$(OBJEXT) \ - unit/test_cancel_query.$(OBJEXT) unit/test_column.$(OBJEXT) \ - unit/test_composite.$(OBJEXT) unit/test_connection.$(OBJEXT) \ - unit/test_cursor.$(OBJEXT) unit/test_encodings.$(OBJEXT) \ + unit/test_blob.$(OBJEXT) unit/test_cancel_query.$(OBJEXT) \ + unit/test_column.$(OBJEXT) unit/test_composite.$(OBJEXT) \ + unit/test_connection.$(OBJEXT) unit/test_cursor.$(OBJEXT) \ + unit/test_encodings.$(OBJEXT) \ unit/test_error_verbosity.$(OBJEXT) \ unit/test_errorhandler.$(OBJEXT) unit/test_escape.$(OBJEXT) \ unit/test_exceptions.$(OBJEXT) unit/test_field.$(OBJEXT) \ @@ -210,9 +210,7 @@ am__depfiles_remade = ./$(DEPDIR)/runner.Po ./$(DEPDIR)/test00.Po \ ./$(DEPDIR)/test82.Po ./$(DEPDIR)/test84.Po \ ./$(DEPDIR)/test87.Po ./$(DEPDIR)/test88.Po \ ./$(DEPDIR)/test89.Po ./$(DEPDIR)/test90.Po \ - unit/$(DEPDIR)/test_array.Po \ - unit/$(DEPDIR)/test_binarystring.Po \ - unit/$(DEPDIR)/test_blob.Po \ + unit/$(DEPDIR)/test_array.Po unit/$(DEPDIR)/test_blob.Po \ unit/$(DEPDIR)/test_cancel_query.Po \ unit/$(DEPDIR)/test_column.Po unit/$(DEPDIR)/test_composite.Po \ unit/$(DEPDIR)/test_connection.Po \ @@ -511,7 +509,6 @@ runner_SOURCES = \ test89.cxx \ test90.cxx \ unit/test_array.cxx \ - unit/test_binarystring.cxx \ unit/test_blob.cxx \ unit/test_cancel_query.cxx \ unit/test_column.cxx \ @@ -608,8 +605,6 @@ unit/$(DEPDIR)/$(am__dirstamp): @: > unit/$(DEPDIR)/$(am__dirstamp) unit/test_array.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) -unit/test_binarystring.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) unit/test_blob.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test_cancel_query.$(OBJEXT): unit/$(am__dirstamp) \ @@ -751,7 +746,6 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test89.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test90.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_array.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_binarystring.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_blob.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_cancel_query.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_column.Po@am__quote@ # am--include-marker @@ -1098,7 +1092,6 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/test89.Po -rm -f ./$(DEPDIR)/test90.Po -rm -f unit/$(DEPDIR)/test_array.Po - -rm -f unit/$(DEPDIR)/test_binarystring.Po -rm -f unit/$(DEPDIR)/test_blob.Po -rm -f unit/$(DEPDIR)/test_cancel_query.Po -rm -f unit/$(DEPDIR)/test_column.Po @@ -1230,7 +1223,6 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/test89.Po -rm -f ./$(DEPDIR)/test90.Po -rm -f unit/$(DEPDIR)/test_array.Po - -rm -f unit/$(DEPDIR)/test_binarystring.Po -rm -f unit/$(DEPDIR)/test_blob.Po -rm -f unit/$(DEPDIR)/test_cancel_query.Po -rm -f unit/$(DEPDIR)/test_column.Po diff --git a/test/unit/test_binarystring.cxx b/test/unit/test_binarystring.cxx deleted file mode 100644 index fb2449ad5..000000000 --- a/test/unit/test_binarystring.cxx +++ /dev/null @@ -1,216 +0,0 @@ -#include -#include -#include - -#include "../test_helpers.hxx" -#include "../test_types.hxx" - - -namespace -{ -pqxx::binarystring -make_binarystring(pqxx::transaction_base &T, std::string content) -{ -#include "pqxx/internal/ignore-deprecated-pre.hxx" - return pqxx::binarystring( - T.exec("SELECT " + T.quote_raw(content)).one_field()); -#include "pqxx/internal/ignore-deprecated-post.hxx" -} - - -void test_binarystring() -{ - pqxx::connection cx; - pqxx::work tx{cx}; - auto b{make_binarystring(tx, "")}; - PQXX_CHECK(std::empty(b), "Empty binarystring is not empty."); - PQXX_CHECK_EQUAL(b.str(), "", "Empty binarystring doesn't work."); - PQXX_CHECK_EQUAL(std::size(b), 0u, "Empty binarystring has nonzero size."); - PQXX_CHECK_EQUAL(b.length(), 0u, "Length/size mismatch."); - PQXX_CHECK(std::begin(b) == std::end(b), "Empty binarystring iterates."); - PQXX_CHECK( - std::cbegin(b) == std::begin(b), "Wrong cbegin for empty binarystring."); - PQXX_CHECK( - std::rbegin(b) == std::rend(b), "Empty binarystring reverse-iterates."); - PQXX_CHECK( - std::crbegin(b) == std::rbegin(b), - "Wrong crbegin for empty binarystring."); - PQXX_CHECK_THROWS( - b.at(0), std::out_of_range, "Empty binarystring accepts at()."); - - b = make_binarystring(tx, "z"); - PQXX_CHECK_EQUAL(b.str(), "z", "Basic nonempty binarystring is broken."); - PQXX_CHECK(not std::empty(b), "Nonempty binarystring is empty."); - PQXX_CHECK_EQUAL(std::size(b), 1u, "Bad binarystring size."); - PQXX_CHECK_EQUAL(b.length(), 1u, "Length/size mismatch."); - PQXX_CHECK( - std::begin(b) != std::end(b), "Nonempty binarystring does not iterate."); - PQXX_CHECK( - std::rbegin(b) != std::rend(b), - "Nonempty binarystring does not reverse-iterate."); - PQXX_CHECK(std::begin(b) + 1 == std::end(b), "Bad iteration."); - PQXX_CHECK(std::rbegin(b) + 1 == std::rend(b), "Bad reverse iteration."); - PQXX_CHECK(std::cbegin(b) == std::begin(b), "Wrong cbegin."); - PQXX_CHECK(std::cend(b) == std::end(b), "Wrong cend."); - PQXX_CHECK(std::crbegin(b) == std::rbegin(b), "Wrong crbegin."); - PQXX_CHECK(std::crend(b) == std::rend(b), "Wrong crend."); - PQXX_CHECK(b.front() == 'z', "Unexpected front()."); - PQXX_CHECK(b.back() == 'z', "Unexpected back()."); - PQXX_CHECK(b.at(0) == 'z', "Unexpected data at index 0."); - PQXX_CHECK_THROWS( - b.at(1), std::out_of_range, "Failed to catch range error."); - - std::string const simple{"ab"}; - b = make_binarystring(tx, simple); - PQXX_CHECK_EQUAL( - b.str(), simple, "Binary (un)escaping went wrong somewhere."); - PQXX_CHECK_EQUAL( - std::size(b), std::size(simple), "Escaping confuses length."); - - std::string const simple_escaped{tx.esc_raw(pqxx::bytes_view{ - reinterpret_cast(std::data(simple)), - std::size(simple)})}; - for (auto c : simple_escaped) - { - auto const uc{static_cast(c)}; - PQXX_CHECK(uc <= 127, "Non-ASCII byte in escaped string."); - } - -#include "pqxx/internal/ignore-deprecated-pre.hxx" - PQXX_CHECK_EQUAL( - tx.quote_raw( - reinterpret_cast(simple.c_str()), - std::size(simple)), - tx.quote(b), "quote_raw is broken"); - PQXX_CHECK_EQUAL( - tx.quote(b), tx.quote_raw(simple), "Binary quoting is broken."); - PQXX_CHECK_EQUAL( - pqxx::binarystring( - tx.query_value("SELECT $1", pqxx::params{b})) - .str(), - simple, "Binary string is not idempotent."); -#include "pqxx/internal/ignore-deprecated-post.hxx" - - std::string const bytes("\x01\x23\x23\xa1\x2b\x0c\xff"); - b = make_binarystring(tx, bytes); - PQXX_CHECK_EQUAL(b.str(), bytes, "Binary data breaks (un)escaping."); - - std::string const nully("a\0b", 3); - b = make_binarystring(tx, nully); - PQXX_CHECK_EQUAL(b.str(), nully, "Nul byte broke binary (un)escaping."); - PQXX_CHECK_EQUAL(std::size(b), 3u, "Nul byte broke binarystring size."); - - b = make_binarystring(tx, "foo"); - PQXX_CHECK_EQUAL(std::string(b.get(), 3), "foo", "get() appears broken."); - - auto b1{make_binarystring(tx, "1")}, b2{make_binarystring(tx, "2")}; - PQXX_CHECK_NOT_EQUAL(b1.get(), b2.get(), "Madness rules."); - PQXX_CHECK_NOT_EQUAL(b1.str(), b2.str(), "Logic has no more meaning."); - b1.swap(b2); - PQXX_CHECK_NOT_EQUAL(b1.str(), b2.str(), "swap() equalized binarystrings."); - PQXX_CHECK_NOT_EQUAL(b1.str(), "1", "swap() did not happen."); - PQXX_CHECK_EQUAL(b1.str(), "2", "swap() is broken."); - PQXX_CHECK_EQUAL(b2.str(), "1", "swap() went insane."); - - b = make_binarystring(tx, "bar"); - b.swap(b); - PQXX_CHECK_EQUAL(b.str(), "bar", "Self-swap confuses binarystring."); - - b = make_binarystring(tx, "\\x"); - PQXX_CHECK_EQUAL(b.str(), "\\x", "Hex-escape header confused (un)escaping."); -} - - -void test_binarystring_conversion() -{ - constexpr char bytes[]{"f\to\0o\n\0"}; - std::string_view const data{bytes, std::size(bytes) - 1}; -#include "pqxx/internal/ignore-deprecated-pre.hxx" - pqxx::binarystring bin{data}; -#include "pqxx/internal/ignore-deprecated-post.hxx" - auto const escaped{pqxx::to_string(bin)}; - PQXX_CHECK_EQUAL( - escaped, std::string_view{"\\x66096f006f0a00"}, "Unexpected hex escape."); - auto const restored{pqxx::from_string(escaped)}; - PQXX_CHECK_EQUAL( - std::size(restored), std::size(data), "Unescaping produced wrong length."); -} - - -void test_binarystring_stream() -{ - constexpr char bytes[]{"a\tb\0c"}; - std::string_view const data{bytes, std::size(bytes) - 1}; -#include "pqxx/internal/ignore-deprecated-pre.hxx" - pqxx::binarystring bin{data}; -#include "pqxx/internal/ignore-deprecated-post.hxx" - - pqxx::connection cx; - pqxx::transaction tx{cx}; - tx.exec("CREATE TEMP TABLE pqxxbinstream(id integer, bin bytea)").no_rows(); - - auto to{pqxx::stream_to::table(tx, {"pqxxbinstream"})}; - to.write_values(0, bin); - to.complete(); - - auto ptr{reinterpret_cast(std::data(data))}; - auto expect{tx.quote(pqxx::bytes_view{ptr, std::size(data)})}; - PQXX_CHECK( - tx.query_value("SELECT bin = " + expect + " FROM pqxxbinstream"), - "binarystring did not stream_to properly."); - PQXX_CHECK_EQUAL( - tx.query_value("SELECT octet_length(bin) FROM pqxxbinstream"), - std::size(data), "Did the terminating zero break the bytea?"); -} - - -void test_binarystring_array_stream() -{ - // This test won't compile on clang in maintainer mode. For some reason, - // clang seems to ignore the ignore-deprecated headers in just this one - // function, where we create the vector of binarystring. -#if !defined(__clang__) - pqxx::connection cx; - pqxx::transaction tx{cx}; - tx.exec("CREATE TEMP TABLE pqxxbinstream(id integer, vec bytea[])") - .no_rows(); - - constexpr char bytes1[]{"a\tb\0c"}, bytes2[]{"1\0.2"}; - std::string_view const data1{bytes1}, data2{bytes2}; -# include "pqxx/internal/ignore-deprecated-pre.hxx" - pqxx::binarystring bin1{data1}, bin2{data2}; - std::vector const vec{bin1, bin2}; -# include "pqxx/internal/ignore-deprecated-post.hxx" - - auto to{pqxx::stream_to::table(tx, {"pqxxbinstream"})}; - to.write_values(0, vec); - to.complete(); - - PQXX_CHECK_EQUAL( - tx.query_value( - "SELECT array_length(vec, 1) FROM pqxxbinstream"), - std::size(vec), "Array came out with wrong length."); - - auto ptr1{reinterpret_cast(std::data(data1))}, - ptr2{reinterpret_cast(std::data(data2))}; - auto expect1{tx.quote(pqxx::bytes_view{ptr1, std::size(data1)})}, - expect2{tx.quote(pqxx::bytes_view{ptr2, std::size(data2)})}; - PQXX_CHECK( - tx.query_value("SELECT vec[1] = " + expect1 + " FROM pqxxbinstream"), - "Bytea in array came out wrong."); - PQXX_CHECK( - tx.query_value("SELECT vec[2] = " + expect2 + " FROM pqxxbinstream"), - "First bytea in array worked, but second did not."); - PQXX_CHECK_EQUAL( - tx.query_value( - "SELECT octet_length(vec[1]) FROM pqxxbinstream"), - std::size(data1), "Bytea length broke inside array."); -#endif // __clang__ -} - - -PQXX_REGISTER_TEST(test_binarystring); -PQXX_REGISTER_TEST(test_binarystring_conversion); -PQXX_REGISTER_TEST(test_binarystring_stream); -PQXX_REGISTER_TEST(test_binarystring_array_stream); -} // namespace diff --git a/test/unit/test_prepared_statement.cxx b/test/unit/test_prepared_statement.cxx index 5a307b974..9d435e4ea 100644 --- a/test/unit/test_prepared_statement.cxx +++ b/test/unit/test_prepared_statement.cxx @@ -177,16 +177,6 @@ void test_binary() constexpr char raw_bytes[]{"Binary\0bytes'\"with\tweird\xff bytes"}; std::string const input{raw_bytes, std::size(raw_bytes)}; -#include "pqxx/internal/ignore-deprecated-pre.hxx" - { - pqxx::binarystring const bin{input}; - auto rw{tx.exec(pqxx::prepped{"EchoBin"}, pqxx::params{bin}).one_row()}; - PQXX_CHECK_EQUAL( - pqxx::binarystring(rw[0]).str(), input, - "Binary string came out damaged."); - } -#include "pqxx/internal/ignore-deprecated-post.hxx" - { pqxx::bytes bytes{ reinterpret_cast(raw_bytes), std::size(raw_bytes)}; From beae65001f026b6250e410ba75ab016e84e3ea58 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 23 Dec 2024 15:50:47 +0100 Subject: [PATCH 20/64] Retire result row slicing. This was a long-forgotten feature that somebody thought might be a good idea, around 20 years ago. It's only been deprecated for a short time, but it's so obscure that I stronly doubt that anyone has ever used it at all. Meanwhile it was weighing down a pretty basic piece of code that comes up in lots of loops, so... less is more. --- NEWS | 1 + include/pqxx/row.hxx | 23 +---- src/row.cxx | 54 ++-------- test/Makefile.am | 1 - test/Makefile.in | 10 +- test/unit/test_result_slicing.cxx | 157 ------------------------------ 6 files changed, 11 insertions(+), 235 deletions(-) delete mode 100644 test/unit/test_result_slicing.cxx diff --git a/NEWS b/NEWS index 3588f21c1..abf953e96 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ - Assume compiler supports integral conversions in `charconv`. - Assume compiler supports spans, ranges, and `cmp_less` etc. - Retired `binarystring` and its headers. + - Retired result row slicing. 7.10.1 - Fix string conversion buffer budget for arrays containing nulls. (#921) - Remove `-fanalyzer` option again; gcc is still broken. diff --git a/include/pqxx/row.hxx b/include/pqxx/row.hxx index 704b8bc2e..ab445d666 100644 --- a/include/pqxx/row.hxx +++ b/include/pqxx/row.hxx @@ -105,7 +105,7 @@ public: [[nodiscard]] constexpr size_type size() const noexcept { - return m_end - m_begin; + return m_end; } /// Row number, assuming this is a real row and not end()/rend(). @@ -194,24 +194,6 @@ public: [[deprecated("Swap iterators, not rows.")]] void swap(row &) noexcept; - /** 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; - protected: friend class const_row_iterator; friend class result; @@ -255,9 +237,6 @@ protected: */ 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; diff --git a/src/row.cxx b/src/row.cxx index 4e51efa88..63972314c 100644 --- a/src/row.cxx +++ b/src/row.cxx @@ -34,7 +34,7 @@ pqxx::row::row(result r, result::size_type index, size_type cols) noexcept : pqxx::row::const_iterator pqxx::row::begin() const noexcept { - return {*this, m_begin}; + return {*this, 0}; } @@ -58,7 +58,7 @@ pqxx::row::const_iterator pqxx::row::cend() const noexcept pqxx::row::reference pqxx::row::front() const noexcept { - return field{m_result, m_index, m_begin}; + return field{m_result, m_index, 0}; } @@ -108,7 +108,7 @@ bool pqxx::row::operator==(row const &rhs) const noexcept pqxx::row::reference pqxx::row::operator[](size_type i) const noexcept { - return field{m_result, m_index, m_begin + i}; + return field{m_result, m_index, i}; } @@ -121,21 +121,18 @@ pqxx::row::reference pqxx::row::operator[](zview col_name) const void pqxx::row::swap(row &rhs) noexcept { auto const i{m_index}; - auto const b{m_begin}; auto const e{m_end}; m_result.swap(rhs.m_result); m_index = rhs.m_index; - m_begin = rhs.m_begin; m_end = rhs.m_end; rhs.m_index = i; - rhs.m_begin = b; rhs.m_end = e; } pqxx::field pqxx::row::at(zview col_name) const { - return {m_result, m_index, m_begin + column_number(col_name)}; + return {m_result, m_index, column_number(col_name)}; } @@ -150,60 +147,25 @@ pqxx::field pqxx::row::at(pqxx::row::size_type i) const pqxx::oid pqxx::row::column_type(size_type col_num) const { - return m_result.column_type(m_begin + col_num); + return m_result.column_type(col_num); } pqxx::oid pqxx::row::column_table(size_type col_num) const { - return m_result.column_table(m_begin + col_num); + return m_result.column_table(col_num); } pqxx::row::size_type pqxx::row::table_column(size_type col_num) const { - return m_result.table_column(m_begin + col_num); + return m_result.table_column(col_num); } pqxx::row::size_type pqxx::row::column_number(zview col_name) const { - auto const n{m_result.column_number(col_name)}; - if (n >= m_end) - throw argument_error{ - "Column '" + std::string{col_name} + "' falls outside slice."}; - if (n >= m_begin) - return n - m_begin; - - // This deals with a really nasty possibility: that the column name occurs - // twice - once before the beginning of the slice, and once inside the slice. - char const *const adapted_name{m_result.column_name(n)}; - for (auto i{m_begin}; i < m_end; ++i) - if (strcmp(adapted_name, m_result.column_name(i)) == 0) - return i - m_begin; - - // Didn't find any? Recurse just to produce the same error message. - return result{}.column_number(col_name); -} - - -pqxx::row PQXX_COLD pqxx::row::slice(size_type sbegin, size_type send) const -{ - if (sbegin > send or send > size()) - throw range_error{"Invalid field range."}; - -#include "pqxx/internal/ignore-deprecated-pre.hxx" - row res{*this}; -#include "pqxx/internal/ignore-deprecated-post.hxx" - res.m_begin = m_begin + sbegin; - res.m_end = m_begin + send; - return res; -} - - -bool PQXX_COLD pqxx::row::empty() const noexcept -{ - return m_begin == m_end; + return m_result.column_number(col_name); } diff --git a/test/Makefile.am b/test/Makefile.am index b00413d6e..30d8fed12 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -101,7 +101,6 @@ runner_SOURCES = \ unit/test_range.cxx \ unit/test_read_transaction.cxx \ unit/test_result_iteration.cxx \ - unit/test_result_slicing.cxx \ unit/test_row.cxx \ unit/test_separated_list.cxx \ unit/test_simultaneous_transactions.cxx \ diff --git a/test/Makefile.in b/test/Makefile.in index 2f8d43afd..fd670227e 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -152,8 +152,7 @@ am_runner_OBJECTS = test00.$(OBJEXT) test01.$(OBJEXT) test02.$(OBJEXT) \ unit/test_notification.$(OBJEXT) unit/test_pipeline.$(OBJEXT) \ unit/test_prepared_statement.$(OBJEXT) \ unit/test_range.$(OBJEXT) unit/test_read_transaction.$(OBJEXT) \ - unit/test_result_iteration.$(OBJEXT) \ - unit/test_result_slicing.$(OBJEXT) unit/test_row.$(OBJEXT) \ + unit/test_result_iteration.$(OBJEXT) unit/test_row.$(OBJEXT) \ unit/test_separated_list.$(OBJEXT) \ unit/test_simultaneous_transactions.$(OBJEXT) \ unit/test_sql_cursor.$(OBJEXT) \ @@ -229,7 +228,6 @@ am__depfiles_remade = ./$(DEPDIR)/runner.Po ./$(DEPDIR)/test00.Po \ unit/$(DEPDIR)/test_range.Po \ unit/$(DEPDIR)/test_read_transaction.Po \ unit/$(DEPDIR)/test_result_iteration.Po \ - unit/$(DEPDIR)/test_result_slicing.Po \ unit/$(DEPDIR)/test_row.Po \ unit/$(DEPDIR)/test_separated_list.Po \ unit/$(DEPDIR)/test_simultaneous_transactions.Po \ @@ -531,7 +529,6 @@ runner_SOURCES = \ unit/test_range.cxx \ unit/test_read_transaction.cxx \ unit/test_result_iteration.cxx \ - unit/test_result_slicing.cxx \ unit/test_row.cxx \ unit/test_separated_list.cxx \ unit/test_simultaneous_transactions.cxx \ @@ -649,8 +646,6 @@ unit/test_read_transaction.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test_result_iteration.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) -unit/test_result_slicing.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) unit/test_row.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test_separated_list.$(OBJEXT): unit/$(am__dirstamp) \ @@ -768,7 +763,6 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_range.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_read_transaction.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_result_iteration.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_result_slicing.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_row.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_separated_list.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_simultaneous_transactions.Po@am__quote@ # am--include-marker @@ -1114,7 +1108,6 @@ distclean: distclean-am -rm -f unit/$(DEPDIR)/test_range.Po -rm -f unit/$(DEPDIR)/test_read_transaction.Po -rm -f unit/$(DEPDIR)/test_result_iteration.Po - -rm -f unit/$(DEPDIR)/test_result_slicing.Po -rm -f unit/$(DEPDIR)/test_row.Po -rm -f unit/$(DEPDIR)/test_separated_list.Po -rm -f unit/$(DEPDIR)/test_simultaneous_transactions.Po @@ -1245,7 +1238,6 @@ maintainer-clean: maintainer-clean-am -rm -f unit/$(DEPDIR)/test_range.Po -rm -f unit/$(DEPDIR)/test_read_transaction.Po -rm -f unit/$(DEPDIR)/test_result_iteration.Po - -rm -f unit/$(DEPDIR)/test_result_slicing.Po -rm -f unit/$(DEPDIR)/test_row.Po -rm -f unit/$(DEPDIR)/test_separated_list.Po -rm -f unit/$(DEPDIR)/test_simultaneous_transactions.Po diff --git a/test/unit/test_result_slicing.cxx b/test/unit/test_result_slicing.cxx deleted file mode 100644 index c7fb92f40..000000000 --- a/test/unit/test_result_slicing.cxx +++ /dev/null @@ -1,157 +0,0 @@ -#include - -#include "../test_helpers.hxx" - -#include "pqxx/internal/ignore-deprecated-pre.hxx" - -namespace pqxx -{ -template<> struct nullness : no_null -{}; - -template<> -struct nullness - : no_null -{}; - - -template<> struct string_traits -{ - static constexpr zview text{"[row::const_iterator]"}; - static zview to_buf(char *, char *, row::const_iterator const &) - { - return text; - } - static char *into_buf(char *begin, char *end, row::const_iterator const &) - { - if ((end - begin) <= 30) - throw conversion_overrun{"Not enough buffer for const row iterator."}; - std::memcpy(begin, text.c_str(), std::size(text) + 1); - return begin + std::size(text); - } - static constexpr std::size_t - size_buffer(row::const_iterator const &) noexcept - { - return std::size(text) + 1; - } -}; - - -template<> struct string_traits -{ - static constexpr zview text{"[row::const_reverse_iterator]"}; - static pqxx::zview - to_buf(char *, char *, row::const_reverse_iterator const &) - { - return text; - } - static char * - into_buf(char *begin, char *end, row::const_reverse_iterator const &) - { - if ((end - begin) <= 30) - throw conversion_overrun{"Not enough buffer for const row iterator."}; - std::memcpy(begin, text.c_str(), std::size(text) + 1); - return begin + std::size(text); - } - static constexpr std::size_t - size_buffer(row::const_reverse_iterator const &) noexcept - { - return 100; - } -}; -} // namespace pqxx - -namespace -{ -void test_result_slicing() -{ - pqxx::connection cx; - pqxx::work tx{cx}; - auto r{tx.exec("SELECT 1")}; - - PQXX_CHECK(not std::empty(r[0]), "A plain row shows up as empty."); - - // Empty slice at beginning of row. - pqxx::row s{r[0].slice(0, 0)}; - PQXX_CHECK(std::empty(s), "Empty slice does not show up as empty."); - PQXX_CHECK_EQUAL(std::size(s), 0, "Slicing produces wrong row size."); - PQXX_CHECK_EQUAL( - std::begin(s), std::end(s), "Slice begin()/end() are broken."); - PQXX_CHECK_EQUAL( - std::rbegin(s), std::rend(s), "Slice rbegin()/rend() are broken."); - - PQXX_CHECK_THROWS(s.at(0), pqxx::range_error, "at() does not throw."); - pqxx::row slice; - PQXX_CHECK_THROWS( - slice = r[0].slice(0, 2), pqxx::range_error, "No range check."); - pqxx::ignore_unused(slice); - PQXX_CHECK_THROWS( - slice = r[0].slice(1, 0), pqxx::range_error, "Can reverse-slice."); - pqxx::ignore_unused(slice); - - // Empty slice at end of row. - s = r[0].slice(1, 1); - PQXX_CHECK(std::empty(s), "empty() is broken."); - PQXX_CHECK_EQUAL(std::size(s), 0, "size() is broken."); - PQXX_CHECK_EQUAL(std::begin(s), std::end(s), "begin()/end() are broken."); - PQXX_CHECK_EQUAL( - std::rbegin(s), std::rend(s), "rbegin()/rend() are broken."); - - PQXX_CHECK_THROWS(s.at(0), pqxx::range_error, "at() is inconsistent."); - - // Slice that matches the entire row. - s = r[0].slice(0, 1); - PQXX_CHECK(not std::empty(s), "Nonempty slice shows up as empty."); - PQXX_CHECK_EQUAL(std::size(s), 1, "size() breaks for non-empty slice."); - PQXX_CHECK_EQUAL(std::begin(s) + 1, std::end(s), "Iteration is broken."); - PQXX_CHECK_EQUAL( - std::rbegin(s) + 1, std::rend(s), "Reverse iteration is broken."); - PQXX_CHECK_EQUAL(s.at(0).as(), 1, "Accessing a slice is broken."); - PQXX_CHECK_EQUAL(s[0].as(), 1, "operator[] is broken."); - PQXX_CHECK_THROWS(s.at(1).as(), pqxx::range_error, "at() is off."); - - // Meaningful slice at beginning of row. - r = tx.exec("SELECT 1, 2, 3"); - s = r[0].slice(0, 1); - PQXX_CHECK(not std::empty(s), "Slicing confuses empty()."); - PQXX_CHECK_THROWS( - s.at(1).as(), pqxx::range_error, "at() does not enforce slice."); - - // Meaningful slice that skips an initial column. - s = r[0].slice(1, 2); - PQXX_CHECK( - not std::empty(s), "Slicing away leading columns confuses empty()."); - PQXX_CHECK_EQUAL(s[0].as(), 2, "Slicing offset is broken."); - PQXX_CHECK_EQUAL( - std::begin(s)->as(), 2, "Iteration uses wrong offset."); - PQXX_CHECK_EQUAL( - std::begin(s) + 1, std::end(s), "Iteration has wrong range."); - PQXX_CHECK_EQUAL( - std::rbegin(s) + 1, std::rend(s), "Reverse iteration has wrong range."); - PQXX_CHECK_THROWS( - s.at(1).as(), pqxx::range_error, "Offset slicing is broken."); - - // Column names in a slice. - r = tx.exec("SELECT 1 AS one, 2 AS two, 3 AS three"); - s = r[0].slice(1, 2); - PQXX_CHECK_EQUAL(s["two"].as(), 2, "Column addressing breaks."); - PQXX_CHECK_THROWS( - pqxx::ignore_unused(s.column_number("one")), pqxx::argument_error, - "Can access column name before slice."); - PQXX_CHECK_THROWS( - pqxx::ignore_unused(s.column_number("three")), pqxx::argument_error, - "Can access column name after slice."); - PQXX_CHECK_EQUAL( - s.column_number("Two"), 0, "Column name is case sensitive."); - - // Identical column names. - r = tx.exec("SELECT 1 AS x, 2 AS x"); - s = r[0].slice(1, 2); - PQXX_CHECK_EQUAL(s["x"].as(), 2, "Identical column names break slice."); -} - - -PQXX_REGISTER_TEST(test_result_slicing); -} // namespace - -#include "pqxx/internal/ignore-deprecated-post.hxx" From 71bf33dd7bc53292cb7ed7b2c245cdcb26fafb94 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Tue, 24 Dec 2024 11:39:30 +0100 Subject: [PATCH 21/64] Docstring. --- include/pqxx/row.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pqxx/row.hxx b/include/pqxx/row.hxx index ab445d666..8467beb36 100644 --- a/include/pqxx/row.hxx +++ b/include/pqxx/row.hxx @@ -237,7 +237,7 @@ protected: */ result::size_type m_index = 0; - /// End column in slice. This row only sees lower-numbered columns. + /// Number of columns in the row. size_type m_end = 0; private: From 55ae6dd9e9d3ace7221a7d9f85984f2701a68f81 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Tue, 24 Dec 2024 15:30:57 +0100 Subject: [PATCH 22/64] Does tweaking configure cmd line work? --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 3d66102d9..758d991d6 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,7 +12,7 @@ build: - "graphviz" jobs: pre_build: - - "./configure --enable-documentation CXXFLAGS='-O0 -std=c++23'" + - "./configure CXXFLAGS='-O0 -std=c++23' --enable-documentation" - "mkdir -p doc/doxygen-html" - "cd doc && doxygen" - "mkdir -p -- $READTHEDOCS_OUTPUT/html" From 52a5b5d7cadd321b5aeed85e581c2442b3b5a13b Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 26 Dec 2024 23:28:00 +0100 Subject: [PATCH 23/64] Log versions. --- .readthedocs.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 758d991d6..58542eba0 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,6 +12,7 @@ build: - "graphviz" jobs: pre_build: + - "lsb_release -a && gcc --version" - "./configure CXXFLAGS='-O0 -std=c++23' --enable-documentation" - "mkdir -p doc/doxygen-html" - "cd doc && doxygen" From 131f39ed8992acc8a821d7c5e99775f07c90c096 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 26 Dec 2024 23:38:46 +0100 Subject: [PATCH 24/64] Try clang. --- .readthedocs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 58542eba0..6f498f0f5 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,8 +12,8 @@ build: - "graphviz" jobs: pre_build: - - "lsb_release -a && gcc --version" - - "./configure CXXFLAGS='-O0 -std=c++23' --enable-documentation" + - "lsb_release -a && g++ --version && clang++ --version" + - "./configure CXX=clang++ CXXFLAGS='-O0 -std=c++23' --enable-documentation" - "mkdir -p doc/doxygen-html" - "cd doc && doxygen" - "mkdir -p -- $READTHEDOCS_OUTPUT/html" From 272d8980e863c63b822ae07f736bc22b8ad2c32b Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 26 Dec 2024 23:40:22 +0100 Subject: [PATCH 25/64] Install clang & gcc. --- .readthedocs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 6f498f0f5..80b449c58 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,6 +9,8 @@ build: tools: python: "3.12" apt_packages: + - "clang" + - "gcc" - "graphviz" jobs: pre_build: From 45f3c61f88a94424721ae026381c1b1a463525b9 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 26 Dec 2024 23:50:13 +0100 Subject: [PATCH 26/64] Install libpq-dev in doc build. --- .readthedocs.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 80b449c58..a85e5f7c5 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,6 +12,7 @@ build: - "clang" - "gcc" - "graphviz" + - "libpq-dev" jobs: pre_build: - "lsb_release -a && g++ --version && clang++ --version" From 479b504be086a38eb74a23d1e73789178b5fdaf5 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 26 Dec 2024 23:54:32 +0100 Subject: [PATCH 27/64] Install build-essential; back to gcc. --- .readthedocs.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a85e5f7c5..1bf60d8d4 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,14 +9,13 @@ build: tools: python: "3.12" apt_packages: - - "clang" - - "gcc" + - "build-essential" - "graphviz" - "libpq-dev" jobs: pre_build: - "lsb_release -a && g++ --version && clang++ --version" - - "./configure CXX=clang++ CXXFLAGS='-O0 -std=c++23' --enable-documentation" + - "./configure CXXFLAGS='-O0 -std=c++23' --enable-documentation" - "mkdir -p doc/doxygen-html" - "cd doc && doxygen" - "mkdir -p -- $READTHEDOCS_OUTPUT/html" From a9516474618d3a3ce22c5f98b2735b976d47b908 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 26 Dec 2024 23:55:52 +0100 Subject: [PATCH 28/64] Not installing clang, so don't run it. --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 1bf60d8d4..b6aeb2b1d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -14,7 +14,7 @@ build: - "libpq-dev" jobs: pre_build: - - "lsb_release -a && g++ --version && clang++ --version" + - "lsb_release -a && g++ --version" - "./configure CXXFLAGS='-O0 -std=c++23' --enable-documentation" - "mkdir -p doc/doxygen-html" - "cd doc && doxygen" From 6109ef46fe79565ea43452afc3e96024eff62573 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sun, 29 Dec 2024 03:52:18 +0100 Subject: [PATCH 29/64] Try installing more on readthedocs. --- .readthedocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index b6aeb2b1d..139e42e35 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,9 +9,12 @@ build: tools: python: "3.12" apt_packages: + - "autoconf" + - "automake" - "build-essential" - "graphviz" - "libpq-dev" + - "make" jobs: pre_build: - "lsb_release -a && g++ --version" From d92db7ee199237a38ad8cc1d03f9aa01acd4dc7c Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 30 Dec 2024 00:34:43 +0100 Subject: [PATCH 30/64] Just one option in CXXFLAGS. --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 139e42e35..3b8b56afd 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -18,7 +18,7 @@ build: jobs: pre_build: - "lsb_release -a && g++ --version" - - "./configure CXXFLAGS='-O0 -std=c++23' --enable-documentation" + - "./configure CXXFLAGS=-std=c++23 --enable-documentation" - "mkdir -p doc/doxygen-html" - "cd doc && doxygen" - "mkdir -p -- $READTHEDOCS_OUTPUT/html" From 9e0cbbdd3bc1547db249061ac0756b7953f266e2 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 30 Dec 2024 00:37:16 +0100 Subject: [PATCH 31/64] Try double quotes. --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 3b8b56afd..86940d634 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -18,7 +18,7 @@ build: jobs: pre_build: - "lsb_release -a && g++ --version" - - "./configure CXXFLAGS=-std=c++23 --enable-documentation" + - "./configure CXXFLAGS=\"-O0 -std=c++23\" --enable-documentation" - "mkdir -p doc/doxygen-html" - "cd doc && doxygen" - "mkdir -p -- $READTHEDOCS_OUTPUT/html" From e34e2eb781aacc9eebc40c5b4c75abbf95711705 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 30 Dec 2024 00:51:18 +0100 Subject: [PATCH 32/64] Wait for notfication faster. --- config/Makefile.in | 2 +- test/unit/test_notification.cxx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/Makefile.in b/config/Makefile.in index 51586bda2..9b1454986 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -124,7 +124,7 @@ am__can_run_installinfo = \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \ - config.sub install-sh ltmain.sh missing mkinstalldirs + config.sub depcomp install-sh ltmain.sh missing mkinstalldirs DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ diff --git a/test/unit/test_notification.cxx b/test/unit/test_notification.cxx index c98a85c9b..2c38fbb88 100644 --- a/test/unit/test_notification.cxx +++ b/test/unit/test_notification.cxx @@ -57,8 +57,8 @@ void test_receive_classic( tx.commit(); int notifs{0}; - for (int i{0}; (i < 10) and (notifs == 0); - ++i, pqxx::internal::wait_for(1000u)) + for (int i{0}; (i < 100) and (notifs == 0); + ++i, pqxx::internal::wait_for(100u)) notifs = cx.get_notifs(); PQXX_CHECK_EQUAL(notifs, 1, "Got wrong number of notifications."); From e7501e05a96c12aa610c9c77bcb7ac6d1e80a0ed Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 30 Dec 2024 00:58:01 +0100 Subject: [PATCH 33/64] Drop now-redundant `test04.cxx`. --- test/Makefile.am | 1 - test/Makefile.in | 70 +++++++++++++++++++++++------------------------- test/test04.cxx | 55 ------------------------------------- 3 files changed, 33 insertions(+), 93 deletions(-) delete mode 100644 test/test04.cxx diff --git a/test/Makefile.am b/test/Makefile.am index 30d8fed12..aee6936b2 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -40,7 +40,6 @@ runner_SOURCES = \ test00.cxx \ test01.cxx \ test02.cxx \ - test04.cxx \ test07.cxx \ test10.cxx \ test11.cxx \ diff --git a/test/Makefile.in b/test/Makefile.in index fd670227e..678b6a532 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -126,19 +126,19 @@ CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = runner$(EXEEXT) am__dirstamp = $(am__leading_dot)dirstamp am_runner_OBJECTS = test00.$(OBJEXT) test01.$(OBJEXT) test02.$(OBJEXT) \ - test04.$(OBJEXT) test07.$(OBJEXT) test10.$(OBJEXT) \ - test11.$(OBJEXT) test13.$(OBJEXT) test14.$(OBJEXT) \ - test16.$(OBJEXT) test17.$(OBJEXT) test18.$(OBJEXT) \ - test20.$(OBJEXT) test21.$(OBJEXT) test26.$(OBJEXT) \ - test29.$(OBJEXT) test30.$(OBJEXT) test32.$(OBJEXT) \ - test37.$(OBJEXT) test39.$(OBJEXT) test46.$(OBJEXT) \ - test56.$(OBJEXT) test60.$(OBJEXT) test61.$(OBJEXT) \ - test62.$(OBJEXT) test69.$(OBJEXT) test70.$(OBJEXT) \ - test71.$(OBJEXT) test72.$(OBJEXT) test74.$(OBJEXT) \ - test75.$(OBJEXT) test76.$(OBJEXT) test77.$(OBJEXT) \ - test78.$(OBJEXT) test79.$(OBJEXT) test82.$(OBJEXT) \ - test84.$(OBJEXT) test87.$(OBJEXT) test88.$(OBJEXT) \ - test89.$(OBJEXT) test90.$(OBJEXT) unit/test_array.$(OBJEXT) \ + test07.$(OBJEXT) test10.$(OBJEXT) test11.$(OBJEXT) \ + test13.$(OBJEXT) test14.$(OBJEXT) test16.$(OBJEXT) \ + test17.$(OBJEXT) test18.$(OBJEXT) test20.$(OBJEXT) \ + test21.$(OBJEXT) test26.$(OBJEXT) test29.$(OBJEXT) \ + test30.$(OBJEXT) test32.$(OBJEXT) test37.$(OBJEXT) \ + test39.$(OBJEXT) test46.$(OBJEXT) test56.$(OBJEXT) \ + test60.$(OBJEXT) test61.$(OBJEXT) test62.$(OBJEXT) \ + test69.$(OBJEXT) test70.$(OBJEXT) test71.$(OBJEXT) \ + test72.$(OBJEXT) test74.$(OBJEXT) test75.$(OBJEXT) \ + test76.$(OBJEXT) test77.$(OBJEXT) test78.$(OBJEXT) \ + test79.$(OBJEXT) test82.$(OBJEXT) test84.$(OBJEXT) \ + test87.$(OBJEXT) test88.$(OBJEXT) test89.$(OBJEXT) \ + test90.$(OBJEXT) unit/test_array.$(OBJEXT) \ unit/test_blob.$(OBJEXT) unit/test_cancel_query.$(OBJEXT) \ unit/test_column.$(OBJEXT) unit/test_composite.$(OBJEXT) \ unit/test_connection.$(OBJEXT) unit/test_cursor.$(OBJEXT) \ @@ -190,26 +190,26 @@ depcomp = $(SHELL) $(top_srcdir)/config/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/runner.Po ./$(DEPDIR)/test00.Po \ ./$(DEPDIR)/test01.Po ./$(DEPDIR)/test02.Po \ - ./$(DEPDIR)/test04.Po ./$(DEPDIR)/test07.Po \ - ./$(DEPDIR)/test10.Po ./$(DEPDIR)/test11.Po \ - ./$(DEPDIR)/test13.Po ./$(DEPDIR)/test14.Po \ - ./$(DEPDIR)/test16.Po ./$(DEPDIR)/test17.Po \ - ./$(DEPDIR)/test18.Po ./$(DEPDIR)/test20.Po \ - ./$(DEPDIR)/test21.Po ./$(DEPDIR)/test26.Po \ - ./$(DEPDIR)/test29.Po ./$(DEPDIR)/test30.Po \ - ./$(DEPDIR)/test32.Po ./$(DEPDIR)/test37.Po \ - ./$(DEPDIR)/test39.Po ./$(DEPDIR)/test46.Po \ - ./$(DEPDIR)/test56.Po ./$(DEPDIR)/test60.Po \ - ./$(DEPDIR)/test61.Po ./$(DEPDIR)/test62.Po \ - ./$(DEPDIR)/test69.Po ./$(DEPDIR)/test70.Po \ - ./$(DEPDIR)/test71.Po ./$(DEPDIR)/test72.Po \ - ./$(DEPDIR)/test74.Po ./$(DEPDIR)/test75.Po \ - ./$(DEPDIR)/test76.Po ./$(DEPDIR)/test77.Po \ - ./$(DEPDIR)/test78.Po ./$(DEPDIR)/test79.Po \ - ./$(DEPDIR)/test82.Po ./$(DEPDIR)/test84.Po \ - ./$(DEPDIR)/test87.Po ./$(DEPDIR)/test88.Po \ - ./$(DEPDIR)/test89.Po ./$(DEPDIR)/test90.Po \ - unit/$(DEPDIR)/test_array.Po unit/$(DEPDIR)/test_blob.Po \ + ./$(DEPDIR)/test07.Po ./$(DEPDIR)/test10.Po \ + ./$(DEPDIR)/test11.Po ./$(DEPDIR)/test13.Po \ + ./$(DEPDIR)/test14.Po ./$(DEPDIR)/test16.Po \ + ./$(DEPDIR)/test17.Po ./$(DEPDIR)/test18.Po \ + ./$(DEPDIR)/test20.Po ./$(DEPDIR)/test21.Po \ + ./$(DEPDIR)/test26.Po ./$(DEPDIR)/test29.Po \ + ./$(DEPDIR)/test30.Po ./$(DEPDIR)/test32.Po \ + ./$(DEPDIR)/test37.Po ./$(DEPDIR)/test39.Po \ + ./$(DEPDIR)/test46.Po ./$(DEPDIR)/test56.Po \ + ./$(DEPDIR)/test60.Po ./$(DEPDIR)/test61.Po \ + ./$(DEPDIR)/test62.Po ./$(DEPDIR)/test69.Po \ + ./$(DEPDIR)/test70.Po ./$(DEPDIR)/test71.Po \ + ./$(DEPDIR)/test72.Po ./$(DEPDIR)/test74.Po \ + ./$(DEPDIR)/test75.Po ./$(DEPDIR)/test76.Po \ + ./$(DEPDIR)/test77.Po ./$(DEPDIR)/test78.Po \ + ./$(DEPDIR)/test79.Po ./$(DEPDIR)/test82.Po \ + ./$(DEPDIR)/test84.Po ./$(DEPDIR)/test87.Po \ + ./$(DEPDIR)/test88.Po ./$(DEPDIR)/test89.Po \ + ./$(DEPDIR)/test90.Po unit/$(DEPDIR)/test_array.Po \ + unit/$(DEPDIR)/test_blob.Po \ unit/$(DEPDIR)/test_cancel_query.Po \ unit/$(DEPDIR)/test_column.Po unit/$(DEPDIR)/test_composite.Po \ unit/$(DEPDIR)/test_connection.Po \ @@ -468,7 +468,6 @@ runner_SOURCES = \ test00.cxx \ test01.cxx \ test02.cxx \ - test04.cxx \ test07.cxx \ test10.cxx \ test11.cxx \ @@ -702,7 +701,6 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test00.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test01.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test02.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test04.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test07.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test10.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test11.Po@am__quote@ # am--include-marker @@ -1047,7 +1045,6 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/test00.Po -rm -f ./$(DEPDIR)/test01.Po -rm -f ./$(DEPDIR)/test02.Po - -rm -f ./$(DEPDIR)/test04.Po -rm -f ./$(DEPDIR)/test07.Po -rm -f ./$(DEPDIR)/test10.Po -rm -f ./$(DEPDIR)/test11.Po @@ -1177,7 +1174,6 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/test00.Po -rm -f ./$(DEPDIR)/test01.Po -rm -f ./$(DEPDIR)/test02.Po - -rm -f ./$(DEPDIR)/test04.Po -rm -f ./$(DEPDIR)/test07.Po -rm -f ./$(DEPDIR)/test10.Po -rm -f ./$(DEPDIR)/test11.Po diff --git a/test/test04.cxx b/test/test04.cxx deleted file mode 100644 index b59655a98..000000000 --- a/test/test04.cxx +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include -#include -#include - -#include - -#include - -#include - -#include -#include - -#include "test_helpers.hxx" - -// Example program for libpqxx. Send notification to self. - -namespace -{ -void test_004() -{ - auto const channel{"pqxx_test_notif"}; - pqxx::connection cx; - int backend_pid{0}; - cx.listen(channel, [&backend_pid](pqxx::notification n) noexcept { - backend_pid = n.backend_pid; - }); - - // Trigger our notification receiver. - pqxx::perform([&cx, &channel] { - pqxx::work tx(cx); - tx.notify(channel); - tx.commit(); - }); - - int notifs{0}; - for (int i{0}; (i < 20) and (backend_pid == 0); ++i) - { - PQXX_CHECK_EQUAL(notifs, 0, "Got unexpected notifications."); - // Sleep for one second. I'm not proud of this, but how does one inject - // a change to the built-in clock in a static language? - pqxx::internal::wait_for(1000u); - notifs = cx.get_notifs(); - } - - PQXX_CHECK_EQUAL( - backend_pid, cx.backendpid(), - "Did not get our notification from our own backend."); - PQXX_CHECK_EQUAL(notifs, 1, "Got too many notifications."); -} - - -PQXX_REGISTER_TEST(test_004); -} // namespace From 2525c94dff6ab3ca1b3bc3b2f8607c1584e77393 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 30 Dec 2024 01:13:09 +0100 Subject: [PATCH 34/64] Remove some more redundant old-style tests. --- test/Makefile.am | 3 --- test/Makefile.in | 27 +++++---------------- test/test78.cxx | 44 ---------------------------------- test/test79.cxx | 49 -------------------------------------- test/test87.cxx | 62 ------------------------------------------------ 5 files changed, 6 insertions(+), 179 deletions(-) delete mode 100644 test/test78.cxx delete mode 100644 test/test79.cxx delete mode 100644 test/test87.cxx diff --git a/test/Makefile.am b/test/Makefile.am index aee6936b2..67280a457 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -69,11 +69,8 @@ runner_SOURCES = \ test75.cxx \ test76.cxx \ test77.cxx \ - test78.cxx \ - test79.cxx \ test82.cxx \ test84.cxx \ - test87.cxx \ test88.cxx \ test89.cxx \ test90.cxx \ diff --git a/test/Makefile.in b/test/Makefile.in index 678b6a532..9a3c5521d 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -135,9 +135,8 @@ am_runner_OBJECTS = test00.$(OBJEXT) test01.$(OBJEXT) test02.$(OBJEXT) \ test60.$(OBJEXT) test61.$(OBJEXT) test62.$(OBJEXT) \ test69.$(OBJEXT) test70.$(OBJEXT) test71.$(OBJEXT) \ test72.$(OBJEXT) test74.$(OBJEXT) test75.$(OBJEXT) \ - test76.$(OBJEXT) test77.$(OBJEXT) test78.$(OBJEXT) \ - test79.$(OBJEXT) test82.$(OBJEXT) test84.$(OBJEXT) \ - test87.$(OBJEXT) test88.$(OBJEXT) test89.$(OBJEXT) \ + test76.$(OBJEXT) test77.$(OBJEXT) test82.$(OBJEXT) \ + test84.$(OBJEXT) test88.$(OBJEXT) test89.$(OBJEXT) \ test90.$(OBJEXT) unit/test_array.$(OBJEXT) \ unit/test_blob.$(OBJEXT) unit/test_cancel_query.$(OBJEXT) \ unit/test_column.$(OBJEXT) unit/test_composite.$(OBJEXT) \ @@ -204,12 +203,10 @@ am__depfiles_remade = ./$(DEPDIR)/runner.Po ./$(DEPDIR)/test00.Po \ ./$(DEPDIR)/test70.Po ./$(DEPDIR)/test71.Po \ ./$(DEPDIR)/test72.Po ./$(DEPDIR)/test74.Po \ ./$(DEPDIR)/test75.Po ./$(DEPDIR)/test76.Po \ - ./$(DEPDIR)/test77.Po ./$(DEPDIR)/test78.Po \ - ./$(DEPDIR)/test79.Po ./$(DEPDIR)/test82.Po \ - ./$(DEPDIR)/test84.Po ./$(DEPDIR)/test87.Po \ - ./$(DEPDIR)/test88.Po ./$(DEPDIR)/test89.Po \ - ./$(DEPDIR)/test90.Po unit/$(DEPDIR)/test_array.Po \ - unit/$(DEPDIR)/test_blob.Po \ + ./$(DEPDIR)/test77.Po ./$(DEPDIR)/test82.Po \ + ./$(DEPDIR)/test84.Po ./$(DEPDIR)/test88.Po \ + ./$(DEPDIR)/test89.Po ./$(DEPDIR)/test90.Po \ + unit/$(DEPDIR)/test_array.Po unit/$(DEPDIR)/test_blob.Po \ unit/$(DEPDIR)/test_cancel_query.Po \ unit/$(DEPDIR)/test_column.Po unit/$(DEPDIR)/test_composite.Po \ unit/$(DEPDIR)/test_connection.Po \ @@ -497,11 +494,8 @@ runner_SOURCES = \ test75.cxx \ test76.cxx \ test77.cxx \ - test78.cxx \ - test79.cxx \ test82.cxx \ test84.cxx \ - test87.cxx \ test88.cxx \ test89.cxx \ test90.cxx \ @@ -730,11 +724,8 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test75.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test76.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test77.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test78.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test79.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test82.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test84.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test87.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test88.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test89.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test90.Po@am__quote@ # am--include-marker @@ -1074,11 +1065,8 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/test75.Po -rm -f ./$(DEPDIR)/test76.Po -rm -f ./$(DEPDIR)/test77.Po - -rm -f ./$(DEPDIR)/test78.Po - -rm -f ./$(DEPDIR)/test79.Po -rm -f ./$(DEPDIR)/test82.Po -rm -f ./$(DEPDIR)/test84.Po - -rm -f ./$(DEPDIR)/test87.Po -rm -f ./$(DEPDIR)/test88.Po -rm -f ./$(DEPDIR)/test89.Po -rm -f ./$(DEPDIR)/test90.Po @@ -1203,11 +1191,8 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/test75.Po -rm -f ./$(DEPDIR)/test76.Po -rm -f ./$(DEPDIR)/test77.Po - -rm -f ./$(DEPDIR)/test78.Po - -rm -f ./$(DEPDIR)/test79.Po -rm -f ./$(DEPDIR)/test82.Po -rm -f ./$(DEPDIR)/test84.Po - -rm -f ./$(DEPDIR)/test87.Po -rm -f ./$(DEPDIR)/test88.Po -rm -f ./$(DEPDIR)/test89.Po -rm -f ./$(DEPDIR)/test90.Po diff --git a/test/test78.cxx b/test/test78.cxx deleted file mode 100644 index 2ef1732be..000000000 --- a/test/test78.cxx +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include - -#include -#include - -#include "test_helpers.hxx" - - -// Example program for libpqxx. Send notification to self, using a -// notification name with unusal characters, and without polling. -namespace -{ -void test_078() -{ - pqxx::connection cx; - bool done{false}; - - std::string const channel{"my listener"}; - cx.listen(channel, [&done](pqxx::notification) noexcept { done = true; }); - - pqxx::perform([&cx, &channel] { - pqxx::nontransaction tx{cx}; - tx.notify(channel); - tx.commit(); - }); - - int notifs{0}; - for (int i{0}; (i < 20) and not done; ++i) - { - PQXX_CHECK_EQUAL(notifs, 0, "Got unexpected notifications."); - std::cout << "."; - notifs = cx.await_notification(); - } - std::cout << std::endl; - - PQXX_CHECK(done, "No notification received."); - PQXX_CHECK_EQUAL(notifs, 1, "Got unexpected number of notifications."); -} -} // namespace - - -PQXX_REGISTER_TEST(test_078); diff --git a/test/test79.cxx b/test/test79.cxx deleted file mode 100644 index 764114840..000000000 --- a/test/test79.cxx +++ /dev/null @@ -1,49 +0,0 @@ -#include -#include -#include - -#include -#include - -#include "test_helpers.hxx" - - -// Example program for libpqxx. Test waiting for notification with timeout. -namespace -{ -void test_079() -{ - pqxx::connection cx; - - std::string const channel{"mylistener"}; - int backend_pid{0}; - - cx.listen(channel, [&backend_pid](pqxx::notification n) noexcept { - backend_pid = n.backend_pid; - }); - - // First see if the timeout really works: we're not expecting any notifs - int notifs{cx.await_notification(0, 1)}; - PQXX_CHECK_EQUAL(notifs, 0, "Got unexpected notification."); - - pqxx::perform([&cx, &channel] { - pqxx::work tx{cx}; - tx.notify(channel); - tx.commit(); - }); - - for (int i{0}; (i < 20) and (backend_pid == 0); ++i) - { - PQXX_CHECK_EQUAL(notifs, 0, "Got notifications, but no handler called."); - std::cout << "."; - notifs = cx.await_notification(1, 0); - } - std::cout << std::endl; - - PQXX_CHECK_EQUAL(backend_pid, cx.backendpid(), "Wrong backend."); - PQXX_CHECK_EQUAL(notifs, 1, "Got unexpected notifications."); -} -} // namespace - - -PQXX_REGISTER_TEST(test_079); diff --git a/test/test87.cxx b/test/test87.cxx deleted file mode 100644 index 628f2cbd4..000000000 --- a/test/test87.cxx +++ /dev/null @@ -1,62 +0,0 @@ -#include "pqxx/config-public-compiler.h" -#include -#include -#include -#include -#include - -#include - -#include - -#include - -#include -#include - -#include "test_helpers.hxx" - - -// Test program for libpqxx. Send notification to self, and wait on the -// socket's connection for it to come in. In a simple situation you'd use -// connection::await_notification() for this, but that won't let you wait for -// multiple sockets. -namespace -{ -void test_087() -{ - pqxx::connection cx; - - std::string const channel{"my notification"}; - int backend_pid{0}; - - cx.listen(channel, [&backend_pid](pqxx::notification n) noexcept { - backend_pid = n.backend_pid; - }); - - pqxx::perform([&cx, &channel] { - pqxx::work tx{cx}; - tx.notify(channel); - tx.commit(); - }); - - int notifs{0}; - for (int i{0}; (i < 20) and (backend_pid == 0); ++i) - { - PQXX_CHECK_EQUAL(notifs, 0, "Got unexpected notifications."); - - std::cout << "."; - - pqxx::internal::wait_fd(cx.sock(), true, false); - notifs = cx.get_notifs(); - } - std::cout << std::endl; - - PQXX_CHECK_EQUAL( - backend_pid, cx.backendpid(), "Notification came from wrong backend."); - PQXX_CHECK_EQUAL(notifs, 1, "Got unexpected number of notifications."); -} -} // namespace - - -PQXX_REGISTER_TEST(test_087); From 719ac988a85775634dc179b4da8bac2f1b8c09dd Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 16:57:55 +0100 Subject: [PATCH 35/64] Retire `connection_base` type alias. This has been deprecated for the entire 7.x cycle. --- include/pqxx/connection.hxx | 4 ---- test/unit/test_stream_to.cxx | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index ed8a884cc..e3a68c8be 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -1341,10 +1341,6 @@ private: }; -/// @deprecated Old base class for connection. They are now the same class. -using connection_base = connection; - - /// An ongoing, non-blocking stepping stone to a connection. /** Use this when you want to create a connection to the database, but without * blocking your whole thread. It is only available on systems that have diff --git a/test/unit/test_stream_to.cxx b/test/unit/test_stream_to.cxx index 12e0eb2aa..91e52a247 100644 --- a/test/unit/test_stream_to.cxx +++ b/test/unit/test_stream_to.cxx @@ -306,7 +306,7 @@ void test_container_stream_to() tx.commit(); } -void test_variant_fold(pqxx::connection_base &connection) +void test_variant_fold(pqxx::connection &connection) { pqxx::work tx{connection}; auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})}; From 8799eab69962cac78ff2b5509c7208e29046a224 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 16:59:20 +0100 Subject: [PATCH 36/64] Tweaks. --- NEWS | 9 +++++++-- include/pqxx/util.hxx | 6 +++--- src/util.cxx | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index abf953e96..629af007b 100644 --- a/NEWS +++ b/NEWS @@ -1,9 +1,14 @@ 8.0.0 - C++20 is now the oldest C++ version that libpqxx supports. + - Retired `binarystring` and its headers. Use `blob` instead. + - Retired result row slicing. + - Retired `connection_base` type alias. Use `connection`. + - Assume compiler supports concepts. - Assume compiler supports integral conversions in `charconv`. - Assume compiler supports spans, ranges, and `cmp_less` etc. - - Retired `binarystring` and its headers. - - Retired result row slicing. + - Assume compiler supports `[[likely]]` & `[[unlikely]]`. + - Assume compiler supports `ssize()`. + - Assume compiler supports ISO-646 without needing `` header. 7.10.1 - Fix string conversion buffer budget for arrays containing nulls. (#921) - Remove `-fanalyzer` option again; gcc is still broken. diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index 57ac93225..9f1d27e4c 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -239,8 +239,8 @@ struct byte_char_traits : std::char_traits } /// Deliberately undefined: "guess" the length of an array of bytes. - /* This is nonsense: we can't determine the length of a random sequence of - * bytes. There is no terminating zero like there is for C strings. + /* This would be nonsense: we can't determine the length of a random sequence + * of bytes. There is no terminating zero like there is for C strings. * * But `std::char_traits` requires us to provide this function, so we * declare it without defining it. @@ -330,7 +330,7 @@ using bytes_view = std::conditional< * may have yours as a `std::string`, or a `std::vector`, or one of * many other types. In libpqxx we commend a container of `std::byte`. * - * For libpqxx to recognise your data as binary, it needs to be a + * For libpqxx to recognise your data as binary, we recommend using a * `pqxx::bytes`, or a `pqxx::bytes_view`; but any contiguous block of * `std::byte` should do. * diff --git a/src/util.cxx b/src/util.cxx index 1b7cc0735..57283c52f 100644 --- a/src/util.cxx +++ b/src/util.cxx @@ -125,9 +125,9 @@ constexpr int nibble(int c) noexcept return c - '0'; else if (c >= 'a' and c <= 'f') return ten + (c - 'a'); - else if (c >= 'A' and c <= 'F') + else [[unlikely]] if (c >= 'A' and c <= 'F') return ten + (c - 'A'); - else + else [[unlikely]] return -1; } } // namespace From 3fabf3e4129dbd7224975d3207f22c1052ffc321 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 17:09:11 +0100 Subject: [PATCH 37/64] Retired `pqxx::encrypt_password()`. Use `pqxx::connection::encrypt_password()` instead. --- NEWS | 1 + include/pqxx/connection.hxx | 17 ----------------- src/connection.cxx | 9 --------- 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/NEWS b/NEWS index 629af007b..bbc851456 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ - Retired `binarystring` and its headers. Use `blob` instead. - Retired result row slicing. - Retired `connection_base` type alias. Use `connection`. + - Retired `pqxx::encrypt_password()`. Use the ones in `pqxx::connection`. - Assume compiler supports concepts. - Assume compiler supports integral conversions in `charconv`. - Assume compiler supports spans, ranges, and `cmp_less` etc. diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index e3a68c8be..3c5e124f4 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -1498,22 +1498,5 @@ inline connection::connection(MAPPING const ¶ms) init(std::data(keys), std::data(values)); } #endif // PQXX_HAVE_CONCEPTS - - -/// Encrypt a password. @deprecated Use connection::encrypt_password instead. -[[nodiscard, - deprecated("Use connection::encrypt_password instead.")]] std::string - PQXX_LIBEXPORT - encrypt_password(char const user[], char const password[]); - -/// Encrypt password. @deprecated Use connection::encrypt_password instead. -[[nodiscard, - deprecated("Use connection::encrypt_password instead.")]] inline std::string -encrypt_password(zview user, zview password) -{ -#include "pqxx/internal/ignore-deprecated-pre.hxx" - return encrypt_password(user.c_str(), password.c_str()); -#include "pqxx/internal/ignore-deprecated-post.hxx" -} } // namespace pqxx #endif diff --git a/src/connection.cxx b/src/connection.cxx index c1a36424f..3dc24d45d 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -105,15 +105,6 @@ void PQXX_COLD PQXX_LIBEXPORT pqxx::internal::skip_init_ssl(int skips) noexcept } -std::string PQXX_COLD -pqxx::encrypt_password(char const user[], char const password[]) -{ - std::unique_ptr const p{ - PQencryptPassword(password, user), pqxx::internal::pq::pqfreemem}; - return {p.get()}; -} - - pqxx::connection::connection(connection &&rhs) : m_conn{rhs.m_conn}, m_notice_waiters{std::move(rhs.m_notice_waiters)}, From dbdbafe4535c6d465ab0271a58dee245f3b8cb9c Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 17:39:35 +0100 Subject: [PATCH 38/64] Retire `pqxx::prepare::dynamic_params`. Use `pqxx::params` instead. --- NEWS | 1 + .../pqxx/internal/statement_parameters.hxx | 50 ------------------- include/pqxx/params.hxx | 44 ---------------- 3 files changed, 1 insertion(+), 94 deletions(-) diff --git a/NEWS b/NEWS index bbc851456..24f202c2d 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ - Retired result row slicing. - Retired `connection_base` type alias. Use `connection`. - Retired `pqxx::encrypt_password()`. Use the ones in `pqxx::connection`. + - Retired `pqxx::prepare::dynamic_params`. Use `pqxx::params` instead. - Assume compiler supports concepts. - Assume compiler supports integral conversions in `charconv`. - Assume compiler supports spans, ranges, and `cmp_less` etc. diff --git a/include/pqxx/internal/statement_parameters.hxx b/include/pqxx/internal/statement_parameters.hxx index 3a44bb774..1e1b2319f 100644 --- a/include/pqxx/internal/statement_parameters.hxx +++ b/include/pqxx/internal/statement_parameters.hxx @@ -29,56 +29,6 @@ constexpr inline auto const iterator_identity{ [](decltype(*std::declval()) x) { return x; }}; -/// @deprecated Use @ref params instead. -template)> -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) - {} - - /// 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 - 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 - 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()) value) const - -> decltype(std::declval()(value)) - { - return m_accessor(value); - } - -private: - IT const m_begin, m_end; - ACCESSOR m_accessor = iterator_identity; -}; - - /// Internal type: encode statement parameters. /** Compiles arguments for prepared statements and parameterised queries into * a format that can be passed into libpq. diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index f28d43c6c..6b685e7c9 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -109,13 +109,6 @@ public: /// Append a non-null binary parameter. void append(bytes &&) &; - /// Append all parameters from value. - template - void append(pqxx::internal::dynamic_params const &value) & - { - for (auto ¶m : value) append(value.access(param)); - } - void append(params const &value) &; void append(params &&value) &; @@ -275,41 +268,4 @@ private: std::array::digits10 + 3> m_buf; }; } // namespace pqxx - - -/// @deprecated The new @ref params class replaces all of this. -namespace pqxx::prepare -{ -/// @deprecated Use @ref params instead. -template -[[deprecated("Use the params class instead.")]] constexpr inline auto -make_dynamic_params(IT begin, IT end) -{ - return pqxx::internal::dynamic_params(begin, end); -} - - -/// @deprecated Use @ref params instead. -template -[[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{container}; -#include "pqxx/internal/ignore-deprecated-post.hxx" -} - - -/// @deprecated Use @ref params instead. -template -[[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{container, accessor}; -#include "pqxx/internal/ignore-deprecated-post.hxx" -} -} // namespace pqxx::prepare #endif From 837707380521676dce41c8f8aafbfd7def3b3341 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 17:49:02 +0100 Subject: [PATCH 39/64] Retire deprecated `stream_to` constructors. Use the factory functions instead. --- NEWS | 5 +++-- include/pqxx/stream_to.hxx | 29 ----------------------------- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/NEWS b/NEWS index 24f202c2d..20455c0b5 100644 --- a/NEWS +++ b/NEWS @@ -1,10 +1,11 @@ 8.0.0 - C++20 is now the oldest C++ version that libpqxx supports. - Retired `binarystring` and its headers. Use `blob` instead. - - Retired result row slicing. - Retired `connection_base` type alias. Use `connection`. - Retired `pqxx::encrypt_password()`. Use the ones in `pqxx::connection`. - - Retired `pqxx::prepare::dynamic_params`. Use `pqxx::params` instead. + - Retired `pqxx::prepare::dynamic_params`. Use `pqxx::params`. + - Retired deprecated `stream_to` constructors. Use factory functions. + - Retired result row slicing. - Assume compiler supports concepts. - Assume compiler supports integral conversions in `charconv`. - Assume compiler supports spans, ranges, and `cmp_less` etc. diff --git a/include/pqxx/stream_to.hxx b/include/pqxx/stream_to.hxx index 79abc5723..bde579f21 100644 --- a/include/pqxx/stream_to.hxx +++ b/include/pqxx/stream_to.hxx @@ -238,28 +238,6 @@ public: write_buffer(); } - /// 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 - [[deprecated("Use table() or raw_table() factory.")]] stream_to( - transaction_base &, std::string_view table_name, Columns const &columns); - private: /// Stream a pre-quoted table name and columns list. stream_to( @@ -458,12 +436,5 @@ private: constexpr static std::string_view s_classname{"stream_to"}; }; - - -template -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)} -{} } // namespace pqxx #endif From ee7ef8ccf334a386a7e6d87be15a33de24c4f7df Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 18:05:17 +0100 Subject: [PATCH 40/64] Retire deprecated binary unescaping/quoting. Use `unesc_bin()` and `quote(bytes_view)` instead. --- NEWS | 2 ++ include/pqxx/transaction_base.hxx | 35 ------------------------------- src/transaction_base.cxx | 6 ------ 3 files changed, 2 insertions(+), 41 deletions(-) diff --git a/NEWS b/NEWS index 20455c0b5..c0b6a3e88 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ - Retired `pqxx::encrypt_password()`. Use the ones in `pqxx::connection`. - Retired `pqxx::prepare::dynamic_params`. Use `pqxx::params`. - Retired deprecated `stream_to` constructors. Use factory functions. + - Retired `transaction_base::unesc_raw()`. Use `unesc_bin()`. + - Retired `transaction_base::quote_raw()`. Use `quote()` with `bytes_view`. - Retired result row slicing. - Assume compiler supports concepts. - Assume compiler supports integral conversions in `charconv`. diff --git a/include/pqxx/transaction_base.hxx b/include/pqxx/transaction_base.hxx index 312e78eed..4bec22bdf 100644 --- a/include/pqxx/transaction_base.hxx +++ b/include/pqxx/transaction_base.hxx @@ -213,36 +213,12 @@ public: return conn().esc_raw(std::forward(args)...); } - /// Unescape binary data, e.g. from a `bytea` field. - /** Takes a binary string as escaped by PostgreSQL, and returns a restored - * copy of the original binary data. - */ - [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string - unesc_raw(zview text) const - { -#include "pqxx/internal/ignore-deprecated-pre.hxx" - return conn().unesc_raw(text); -#include "pqxx/internal/ignore-deprecated-post.hxx" - } - /// Unescape binary data, e.g. from a `bytea` field. /** Takes a binary string as escaped by PostgreSQL, and returns a restored * copy of the original binary data. */ [[nodiscard]] bytes unesc_bin(zview text) { return conn().unesc_bin(text); } - /// Unescape binary data, e.g. from a `bytea` field. - /** Takes a binary string as escaped by PostgreSQL, and returns a restored - * copy of the original binary data. - */ - [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string - unesc_raw(char const *text) const - { -#include "pqxx/internal/ignore-deprecated-pre.hxx" - return conn().unesc_raw(text); -#include "pqxx/internal/ignore-deprecated-post.hxx" - } - /// Unescape binary data, e.g. from a `bytea` field. /** Takes a binary string as escaped by PostgreSQL, and returns a restored * copy of the original binary data. @@ -259,17 +235,6 @@ public: return conn().quote(t); } - /// Binary-escape and quote a binary string for use as an SQL constant. - [[deprecated("Use quote(pqxx::bytes_view).")]] std::string - quote_raw(unsigned char const bin[], std::size_t len) const - { - return quote(binary_cast(bin, len)); - } - - /// Binary-escape and quote a binary string for use as an SQL constant. - [[deprecated("Use quote(pqxx::bytes_view).")]] std::string - quote_raw(zview bin) const; - #if defined(PQXX_HAVE_CONCEPTS) /// Binary-escape and quote a binary string for use as an SQL constant. /** For binary data you can also just use @ref quote(data). */ diff --git a/src/transaction_base.cxx b/src/transaction_base.cxx index 3972d0a3c..51dfddd77 100644 --- a/src/transaction_base.cxx +++ b/src/transaction_base.cxx @@ -213,12 +213,6 @@ void pqxx::transaction_base::abort() } -std::string PQXX_COLD pqxx::transaction_base::quote_raw(zview bin) const -{ - return conn().quote(binary_cast(bin)); -} - - namespace { /// Guard command execution against clashes with pipelines and such. From ddda717c3a2f53681385ad463f655237490e616d Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 18:12:45 +0100 Subject: [PATCH 41/64] Split off deprecated `desc` further. --- include/pqxx/transaction_base.hxx | 8 +------- src/transaction_base.cxx | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/include/pqxx/transaction_base.hxx b/include/pqxx/transaction_base.hxx index 4bec22bdf..1d12f1557 100644 --- a/include/pqxx/transaction_base.hxx +++ b/include/pqxx/transaction_base.hxx @@ -298,7 +298,6 @@ public: /// Execute a command. /** * @param query Query or command to execute. - * @param desc Optional identifier for query, to help pinpoint SQL errors. * @return A result set describing the query's or command's result. */ [[deprecated("The desc parameter is going away.")]] @@ -316,12 +315,7 @@ public: * @param query Query or command to execute. * @return A result set describing the query's or command's result. */ - result exec(std::string_view query) - { -#include "pqxx/internal/ignore-deprecated-pre.hxx" - return exec(query, std::string_view{}); -#include "pqxx/internal/ignore-deprecated-post.hxx" - } + result exec(std::string_view query); /// Execute a command. /** diff --git a/src/transaction_base.cxx b/src/transaction_base.cxx index 51dfddd77..75dad4667 100644 --- a/src/transaction_base.cxx +++ b/src/transaction_base.cxx @@ -238,6 +238,31 @@ class PQXX_PRIVATE command : pqxx::transaction_focus }; } // namespace +pqxx::result +pqxx::transaction_base::exec(std::string_view query) +{ + check_pending_error(); + + command const cmd{*this, {}}; + + switch (m_status) + { + case status::active: break; + + case status::committed: + case status::aborted: + case status::in_doubt: + // TODO: Pass query. + throw usage_error{ + "Could not execute command: transaction is already closed."}; + + default: PQXX_UNREACHABLE; + } + + return direct_exec(query); +} + + pqxx::result pqxx::transaction_base::exec(std::string_view query, std::string_view desc) { From b1bcde795b325d74c83a1e3bd0fddd1c3cf5caca Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 18:52:07 +0100 Subject: [PATCH 42/64] Use `std::remove_cvref_t`. --- NEWS | 1 + include/pqxx/internal/conversions.hxx | 8 ++++---- include/pqxx/internal/stream_query.hxx | 2 +- include/pqxx/params.hxx | 2 +- include/pqxx/row.hxx | 2 +- include/pqxx/separated_list.hxx | 2 +- include/pqxx/strconv.hxx | 6 +++--- include/pqxx/stream_from.hxx | 2 +- include/pqxx/types.hxx | 13 +++++++------ include/pqxx/util.hxx | 10 +++++----- include/pqxx/zview.hxx | 4 ++-- src/params.cxx | 2 +- 12 files changed, 28 insertions(+), 26 deletions(-) diff --git a/NEWS b/NEWS index c0b6a3e88..f1b0f1e35 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ - Assume compiler supports concepts. - Assume compiler supports integral conversions in `charconv`. - Assume compiler supports spans, ranges, and `cmp_less` etc. + - Assume compiler supports `std::remove_cvref_t`. - Assume compiler supports `[[likely]]` & `[[unlikely]]`. - Assume compiler supports `ssize()`. - Assume compiler supports ISO-646 without needing `` header. diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index a5177fa5e..3f07a3db7 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -334,7 +334,7 @@ template struct nullness> return value.valueless_by_exception() or std::visit( [](auto const &i) noexcept { - return nullness>::is_null(i); + return nullness>::is_null(i); }, value); } @@ -357,7 +357,7 @@ template struct string_traits> { return std::visit( [begin, end](auto const &i) { - return string_traits>::into_buf(begin, end, i); + return string_traits>::into_buf(begin, end, i); }, value); } @@ -365,7 +365,7 @@ template struct string_traits> { return std::visit( [begin, end](auto const &i) { - return string_traits>::to_buf(begin, end, i); + return string_traits>::to_buf(begin, end, i); }, value); } @@ -1031,7 +1031,7 @@ namespace pqxx::internal template struct array_string_traits { private: - using elt_type = strip_t>; + using elt_type = std::remove_cvref_t>; using elt_traits = string_traits; static constexpr zview s_null{"NULL"}; diff --git a/include/pqxx/internal/stream_query.hxx b/include/pqxx/internal/stream_query.hxx index 2aa1f8964..3cd73699b 100644 --- a/include/pqxx/internal/stream_query.hxx +++ b/include/pqxx/internal/stream_query.hxx @@ -268,7 +268,7 @@ private: template TARGET parse_field(zview line, std::size_t &offset, char *&write) { - using field_type = strip_t; + using field_type = std::remove_cvref_t; using nullity = nullness; assert(offset <= std::size(line)); diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index 6b685e7c9..4ac35c50e 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -118,7 +118,7 @@ public: template void append(TYPE const &value) & { // TODO: Pool storage for multiple string conversions in one buffer? - if constexpr (nullness>::always_null) + if constexpr (nullness>::always_null) { ignore_unused(value); m_params.emplace_back(); diff --git a/include/pqxx/row.hxx b/include/pqxx/row.hxx index 8467beb36..abeb20b90 100644 --- a/include/pqxx/row.hxx +++ b/include/pqxx/row.hxx @@ -550,7 +550,7 @@ const_row_iterator::operator-(const_row_iterator const &i) const noexcept template inline void row::extract_value(Tuple &t) const { - using field_type = strip_t(t))>; + using field_type = std::remove_cvref_t(t))>; field const f{m_result, m_index, index}; std::get(t) = from_string(f); } diff --git a/include/pqxx/separated_list.hxx b/include/pqxx/separated_list.hxx index a76ef00c0..8e4d659eb 100644 --- a/include/pqxx/separated_list.hxx +++ b/include/pqxx/separated_list.hxx @@ -53,7 +53,7 @@ separated_list(std::string_view sep, ITER begin, ITER end, ACCESS access) 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; + using elt_type = std::remove_cvref_t; using traits = string_traits; std::size_t budget{0}; diff --git a/include/pqxx/strconv.hxx b/include/pqxx/strconv.hxx index 40bff6c1b..5aa71ab70 100644 --- a/include/pqxx/strconv.hxx +++ b/include/pqxx/strconv.hxx @@ -510,7 +510,7 @@ inline void into_string(TYPE const &value, std::string &out); template [[nodiscard]] inline constexpr bool is_null(TYPE const &value) noexcept { - return nullness>::is_null(value); + return nullness>::is_null(value); } @@ -521,7 +521,7 @@ template template [[nodiscard]] inline std::size_t size_buffer(TYPE const &...value) noexcept { - return (string_traits>::size_buffer(value) + ...); + return (string_traits>::size_buffer(value) + ...); } @@ -602,7 +602,7 @@ inline zview generic_to_buf(char *begin, char *end, TYPE const &value) */ template concept binary = std::ranges::contiguous_range and - std::is_same_v>, std::byte>; + std::is_same_v>, std::byte>; #endif //@} } // namespace pqxx diff --git a/include/pqxx/stream_from.hxx b/include/pqxx/stream_from.hxx index ab538fa68..09f927263 100644 --- a/include/pqxx/stream_from.hxx +++ b/include/pqxx/stream_from.hxx @@ -343,7 +343,7 @@ template inline stream_from &stream_from::operator>>(Tuple &t) template inline void stream_from::extract_value(Tuple &t) const { - using field_type = strip_t(t))>; + using field_type = std::remove_cvref_t(t))>; using nullity = nullness; assert(index < std::size(m_fields)); if constexpr (nullity::always_null) diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index a0f1f7d5e..a04892a01 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -17,6 +17,7 @@ #include #include #include +#include namespace pqxx @@ -70,10 +71,10 @@ enum class format : int /// Remove any constness, volatile, and reference-ness from a type. -/** @deprecated In C++20 we'll replace this with std::remove_cvref. +/** @deprecated Use `std::remove_cvref` instead. */ template -using strip_t = std::remove_cv_t>; +using strip_t = std::remove_cvref_t; #if defined(PQXX_HAVE_CONCEPTS) @@ -82,14 +83,14 @@ using strip_t = std::remove_cv_t>; * which we may or may not end up using for this. */ template -using value_type = strip_t()))>; +using value_type = std::remove_cvref_t()))>; #else // PQXX_HAVE_CONCEPTS /// The type of a container's elements. /** At the time of writing there's a similar thing in `std::experimental`, * which we may or may not end up using for this. */ template -using value_type = strip_t()))>; +using value_type = std::remove_cvref_t()))>; #endif // PQXX_HAVE_CONCEPTS @@ -97,12 +98,12 @@ using value_type = strip_t()))>; /// Concept: Any type that we can read as a string of `char`. template concept char_string = std::ranges::contiguous_range and - std::same_as>, char>; + std::same_as>, char>; /// Concept: Anything we can iterate to get things we can read as strings. template concept char_strings = - std::ranges::range and char_string>>; + std::ranges::range and char_string>>; /// Concept: Anything we might want to treat as binary data. template diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index 9f1d27e4c..721e92b45 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -347,7 +347,7 @@ bytes_view binary_cast(TYPE const &data) // C++20: Use std::as_bytes. return { reinterpret_cast( - const_cast const *>( + const_cast const *>( std::data(data))), std::size(data)}; } @@ -539,15 +539,15 @@ template using args_t = decltype(args_f(std::declval())); -/// Helper: Apply `strip_t` to each of a tuple type's component types. +/// Apply `std::remove_cvref_t` to each of a tuple type's component types. /** This function has no definition. It is not meant to be called, only to be * used to deduce the right types. */ template -std::tuple...> strip_types(std::tuple const &); +std::tuple...> strip_types(std::tuple const &); -/// Take a tuple type and apply @ref strip_t to its component types. +/// Take a tuple type and apply std::remove_cvref_t to its component types. template using strip_types_t = decltype(strip_types(std::declval())); @@ -591,7 +591,7 @@ error_string(int err_num, std::array &buffer) # else auto const err_result{strerror_r(err_num, std::data(buffer), BYTES)}; # endif - if constexpr (std::is_same_v, char *>) + if constexpr (std::is_same_v, char *>) { // GNU version of strerror_r; returns the error string, which may or may // not reside within buffer. diff --git a/include/pqxx/zview.hxx b/include/pqxx/zview.hxx index b40f95839..546a058a6 100644 --- a/include/pqxx/zview.hxx +++ b/include/pqxx/zview.hxx @@ -131,8 +131,8 @@ namespace pqxx::internal * support each of these individually. */ template -concept ZString = std::is_convertible_v, char const *> or - std::is_convertible_v, zview> or +concept ZString = std::is_convertible_v, char const *> or + std::is_convertible_v, zview> or std::is_convertible_v; } // namespace pqxx::internal #endif // PQXX_HAVE_CONCEPTS diff --git a/src/params.cxx b/src/params.cxx index 6ba77d887..aa8350967 100644 --- a/src/params.cxx +++ b/src/params.cxx @@ -93,7 +93,7 @@ pqxx::internal::c_params pqxx::params::make_c_params() const for (auto const ¶m : m_params) std::visit( [&p](auto const &value) { - using T = strip_t; + using T = std::remove_cvref_t; if constexpr (std::is_same_v) { From dc914df904e6cc90a70a559062835df17fa5ebc4 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 19:33:54 +0100 Subject: [PATCH 43/64] Retire more deprecated quoting/escaping. --- include/pqxx/connection.hxx | 38 ------------------------------------- src/connection.cxx | 38 ------------------------------------- 2 files changed, 76 deletions(-) diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index 3c5e124f4..87fc713ae 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -909,10 +909,6 @@ public: } #endif - /// Escape binary string for use as SQL string literal on this connection. - [[deprecated("Use std::byte for binary data.")]] std::string - esc_raw(unsigned char const bin[], std::size_t len) const; - /// Escape binary string for use as SQL string literal on this connection. /** You can also just use @ref esc with a binary string. */ [[nodiscard]] std::string esc_raw(bytes_view) const; @@ -1046,40 +1042,6 @@ public: */ [[nodiscard]] std::string esc_like(std::string_view text, char escape_char = '\\') const; - - /// Escape string for use as SQL string literal on this connection. - /** @warning This accepts a length, and it does not require a terminating - * zero byte. But if there is a zero byte, escaping stops there even if - * it's not at the end of the string! - */ - [[deprecated("Use std::string_view or pqxx:zview.")]] std::string - esc(char const text[], std::size_t maxlen) const - { - return esc(std::string_view{text, maxlen}); - } - - /// Unescape binary data, e.g. from a `bytea` field. - /** Takes a binary string as escaped by PostgreSQL, and returns a restored - * copy of the original binary data. - */ - [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string - unesc_raw(zview text) const - { -#include "pqxx/internal/ignore-deprecated-pre.hxx" - return unesc_raw(text.c_str()); -#include "pqxx/internal/ignore-deprecated-post.hxx" - } - - /// Unescape binary data, e.g. from a `bytea` field. - /** Takes a binary string as escaped by PostgreSQL, and returns a restored - * copy of the original binary data. - */ - [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string - unesc_raw(char const text[]) const; - - /// Escape and quote a string of binary data. - [[deprecated("Use quote(bytes_view).")]] std::string - quote_raw(unsigned char const bin[], std::size_t len) const; //@} /// Attempt to cancel the ongoing query, if any. diff --git a/src/connection.cxx b/src/connection.cxx index 3dc24d45d..539b337d4 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -963,50 +963,12 @@ std::string pqxx::connection::esc(std::string_view text) const } -std::string PQXX_COLD -pqxx::connection::esc_raw(unsigned char const bin[], std::size_t len) const -{ - return pqxx::internal::esc_bin(binary_cast(bin, len)); -} - - std::string pqxx::connection::esc_raw(bytes_view bin) const { return pqxx::internal::esc_bin(bin); } -std::string PQXX_COLD pqxx::connection::unesc_raw(char const text[]) const -{ - if (text[0] == '\\' and text[1] == 'x') - { - // Hex-escaped format. - std::string buf; - buf.resize(pqxx::internal::size_unesc_bin(std::strlen(text))); - pqxx::internal::unesc_bin( - std::string_view{text}, reinterpret_cast(buf.data())); - return buf; - } - else - { - // Legacy escape format. - // TODO: Remove legacy support. - std::size_t len{}; - auto bytes{reinterpret_cast(text)}; - std::unique_ptr const ptr{ - PQunescapeBytea(bytes, &len), pqxx::internal::pq::pqfreemem}; - return std::string{ptr.get(), ptr.get() + len}; - } -} - - -std::string PQXX_COLD -pqxx::connection::quote_raw(unsigned char const bin[], std::size_t len) const -{ - return internal::concat("'", esc_raw(binary_cast(bin, len)), "'::bytea"); -} - - std::string pqxx::connection::quote_raw(bytes_view bytes) const { return internal::concat("'", esc_raw(bytes), "'::bytea"); From bb433a8978e3d8a2db9482527cc7f004f48b21b6 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 19:43:00 +0100 Subject: [PATCH 44/64] Make deprecated `field` ctors public. These constructors have been documented as "do not use" in various ways for a long time. More generally, I'm settling on a deprecation horizon of 3 years. It's been so long since 7.0 came out that it's just not reasonable to keep everything that was deprecated since then. Instead, I'm now retiring things that were deprecated in 7.6. --- NEWS | 1 + include/pqxx/field.hxx | 13 ++++--------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index f1b0f1e35..b5b9055b4 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ - Retired `transaction_base::unesc_raw()`. Use `unesc_bin()`. - Retired `transaction_base::quote_raw()`. Use `quote()` with `bytes_view`. - Retired result row slicing. + - Deprecated `field` constructors are no longer publicly accessible. - Assume compiler supports concepts. - Assume compiler supports integral conversions in `charconv`. - Assume compiler supports spans, ranges, and `cmp_less` etc. diff --git a/include/pqxx/field.hxx b/include/pqxx/field.hxx index 84f645611..e1fe709c3 100644 --- a/include/pqxx/field.hxx +++ b/include/pqxx/field.hxx @@ -272,21 +272,16 @@ public: } //@} - /// Constructor. Do not call this yourself; libpqxx will do it for you. +protected: /** 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; + 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; + /// Constructor. + field() noexcept = default; - -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; } From 3615ba583ce221e4cd3946c788ae8ad213b5c417 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 19:52:23 +0100 Subject: [PATCH 45/64] Remove a pair of "ignore deprecations" headers. Not clear why those were there in the first place. --- include/pqxx/internal/result_iterator.hxx | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/pqxx/internal/result_iterator.hxx b/include/pqxx/internal/result_iterator.hxx index 874f3b2a3..d7e799a7d 100644 --- a/include/pqxx/internal/result_iterator.hxx +++ b/include/pqxx/internal/result_iterator.hxx @@ -40,7 +40,6 @@ public: 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. @@ -50,7 +49,6 @@ public: /// Begin iterating a @ref row. const_result_iterator(row const &t) noexcept : row{t} {} -#include "pqxx/internal/ignore-deprecated-post.hxx" /** * @name Dereferencing operators From 1185e579adeaeb5061ca07826164fbc802dbad17 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 20:03:57 +0100 Subject: [PATCH 46/64] Remove unneeded ignore-deprecated includes. --- include/pqxx/internal/result_iterator.hxx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/include/pqxx/internal/result_iterator.hxx b/include/pqxx/internal/result_iterator.hxx index d7e799a7d..7b911b5ae 100644 --- a/include/pqxx/internal/result_iterator.hxx +++ b/include/pqxx/internal/result_iterator.hxx @@ -67,10 +67,8 @@ public: /// 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" //@} /** @@ -91,17 +89,13 @@ public: //@{ 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; } From ee5b52266c7b352e4e250b2a2938d57f0d0dc3ec Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 20:11:43 +0100 Subject: [PATCH 47/64] Remove another unneeded ignore-deprecated. --- include/pqxx/row.hxx | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/pqxx/row.hxx b/include/pqxx/row.hxx index abeb20b90..4f0c53193 100644 --- a/include/pqxx/row.hxx +++ b/include/pqxx/row.hxx @@ -276,9 +276,7 @@ public: using difference_type = row_difference_type; using reference = field; -#include "pqxx/internal/ignore-deprecated-pre.hxx" const_row_iterator() noexcept = 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} {} From 749ab4a2dd0c33b0357b3d1f09401e0b0e634cfc Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 21:25:30 +0100 Subject: [PATCH 48/64] Assume concepts support. --- cmake/pqxx_cxx_feature_checks.cmake | 4 --- config-tests/PQXX_HAVE_CONCEPTS.cxx | 11 -------- config/Makefile.in | 2 +- configitems | 1 - configure | 40 --------------------------- cxx_features.txt | 1 - include/pqxx/config.h.in | 3 -- include/pqxx/connection.hxx | 21 +------------- include/pqxx/internal/conversions.hxx | 2 -- include/pqxx/params.hxx | 4 --- include/pqxx/strconv.hxx | 2 -- include/pqxx/stream_to.hxx | 2 -- include/pqxx/transaction_base.hxx | 2 -- include/pqxx/types.hxx | 11 -------- include/pqxx/util.hxx | 17 +++--------- include/pqxx/zview.hxx | 2 -- pqxx_cxx_feature_checks.ac | 10 ------- test/unit/test_connection.cxx | 4 --- test/unit/test_escape.cxx | 6 ---- test/unit/test_prepared_statement.cxx | 2 -- test/unit/test_stream_to.cxx | 5 ---- 21 files changed, 6 insertions(+), 146 deletions(-) delete mode 100644 config-tests/PQXX_HAVE_CONCEPTS.cxx diff --git a/cmake/pqxx_cxx_feature_checks.cmake b/cmake/pqxx_cxx_feature_checks.cmake index bd01142c6..b6fbe7e36 100644 --- a/cmake/pqxx_cxx_feature_checks.cmake +++ b/cmake/pqxx_cxx_feature_checks.cmake @@ -7,10 +7,6 @@ try_compile( PQXX_HAVE_CHARCONV_FLOAT ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_CHARCONV_FLOAT.cxx ) -try_compile( - PQXX_HAVE_CONCEPTS ${PROJECT_BINARY_DIR} - SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_CONCEPTS.cxx -) try_compile( PQXX_HAVE_CXA_DEMANGLE ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_CXA_DEMANGLE.cxx diff --git a/config-tests/PQXX_HAVE_CONCEPTS.cxx b/config-tests/PQXX_HAVE_CONCEPTS.cxx deleted file mode 100644 index 2b028fd81..000000000 --- a/config-tests/PQXX_HAVE_CONCEPTS.cxx +++ /dev/null @@ -1,11 +0,0 @@ -// Feature check for 'PQXX_HAVE_CONCEPTS'. -// Generated by generate_cxx_checks.py. -#include -#if !defined(__cpp_concepts) -# error "No PQXX_HAVE_CONCEPTS: __cpp_concepts is not set." -#endif -#if !__cpp_concepts -# error "No PQXX_HAVE_CONCEPTS: __cpp_concepts is false." -#endif - -int main() {} diff --git a/config/Makefile.in b/config/Makefile.in index 9b1454986..51586bda2 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -124,7 +124,7 @@ am__can_run_installinfo = \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \ - config.sub depcomp install-sh ltmain.sh missing mkinstalldirs + config.sub install-sh ltmain.sh missing mkinstalldirs DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ diff --git a/configitems b/configitems index e4f653b8a..8208cd679 100644 --- a/configitems +++ b/configitems @@ -6,7 +6,6 @@ PACKAGE_TARNAME internal autotools PACKAGE_VERSION internal autotools PQXX_HAVE_ASSUME public compiler PQXX_HAVE_CHARCONV_FLOAT internal compiler -PQXX_HAVE_CONCEPTS public compiler PQXX_HAVE_CXA_DEMANGLE internal compiler PQXX_HAVE_GCC_PURE public compiler PQXX_HAVE_GCC_VISIBILITY public compiler diff --git a/configure b/configure index 2e7b96cd1..2e4d1dd41 100755 --- a/configure +++ b/configure @@ -17324,46 +17324,6 @@ fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_CHARCONV_FLOAT" >&5 printf "%s\n" "$PQXX_HAVE_CHARCONV_FLOAT" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_CONCEPTS" >&5 -printf %s "checking PQXX_HAVE_CONCEPTS... " >&6; } -PQXX_HAVE_CONCEPTS=yes -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -// Feature check for 'PQXX_HAVE_CONCEPTS'. - -// Generated by generate_cxx_checks.py. - -#include - -#if !defined(__cpp_concepts) - -# error "No PQXX_HAVE_CONCEPTS: __cpp_concepts is not set." - -#endif - -#if !__cpp_concepts - -# error "No PQXX_HAVE_CONCEPTS: __cpp_concepts is false." - -#endif - - - -int main() {} - - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO" -then : - -printf "%s\n" "#define PQXX_HAVE_CONCEPTS 1" >>confdefs.h - -else $as_nop - PQXX_HAVE_CONCEPTS=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_CONCEPTS" >&5 -printf "%s\n" "$PQXX_HAVE_CONCEPTS" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_CXA_DEMANGLE" >&5 printf %s "checking PQXX_HAVE_CXA_DEMANGLE... " >&6; } PQXX_HAVE_CXA_DEMANGLE=yes diff --git a/cxx_features.txt b/cxx_features.txt index 464415362..3b722f97e 100644 --- a/cxx_features.txt +++ b/cxx_features.txt @@ -12,6 +12,5 @@ # Remember to enter each of these in configitems as well, or they won't # end up in the actual configuration headers. -PQXX_HAVE_CONCEPTS __cpp_concepts PQXX_HAVE_MULTIDIM __cpp_multidimensional_subscript PQXX_HAVE_SOURCE_LOCATION __cpp_lib_source_location diff --git a/include/pqxx/config.h.in b/include/pqxx/config.h.in index b07152fbc..b79862a6b 100644 --- a/include/pqxx/config.h.in +++ b/include/pqxx/config.h.in @@ -63,9 +63,6 @@ /* Define if this feature is available. */ #undef PQXX_HAVE_CHARCONV_FLOAT -/* Define if this feature is available. */ -#undef PQXX_HAVE_CONCEPTS - /* Define if this feature is available. */ #undef PQXX_HAVE_CXA_DEMANGLE diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index 87fc713ae..3c4c33e4c 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -77,7 +77,6 @@ namespace pqxx::internal { class sql_cursor; -#if defined(PQXX_HAVE_CONCEPTS) /// Concept: T is a range of pairs of zero-terminated strings. template concept ZKey_ZValues = std::ranges::input_range and requires(T t) { @@ -85,7 +84,6 @@ concept ZKey_ZValues = std::ranges::input_range and requires(T t) { { std::get<0>(*std::cbegin(t)) } -> ZString; { std::get<1>(*std::cbegin(t)) } -> ZString; } and std::tuple_size_v::value_type> == 2; -#endif // PQXX_HAVE_CONCEPTS /// Control OpenSSL/crypto library initialisation. @@ -298,12 +296,8 @@ public: */ connection(connection &&rhs); -#if defined(PQXX_HAVE_CONCEPTS) /// Connect to a database, passing options as a range of key/value pairs. - /** @warning Experimental. Requires C++20 "concepts" support. Define - * `PQXX_HAVE_CONCEPTS` to enable it. - * - * There's no need to escape the parameter values. + /** There's no need to escape the parameter values. * * See the PostgreSQL libpq documentation for the full list of possible * options: @@ -317,7 +311,6 @@ public: */ template inline connection(MAPPING const ¶ms); -#endif // PQXX_HAVE_CONCEPTS ~connection() { @@ -868,16 +861,13 @@ public: */ [[nodiscard]] std::string esc(std::string_view text) const; -#if defined(PQXX_HAVE_CONCEPTS) /// Escape binary string for use as SQL string literal on this connection. /** This is identical to `esc_raw(data)`. */ template [[nodiscard]] std::string esc(DATA const &data) const { return esc_raw(data); } -#endif -#if defined(PQXX_HAVE_CONCEPTS) /// Escape binary string for use as SQL string literal, into `buffer`. /** Use this variant when you want to re-use the same buffer across multiple * calls. If that's not the case, or convenience and simplicity are more @@ -907,7 +897,6 @@ public: internal::esc_bin(view, out); return zview{out, needed - 1}; } -#endif /// Escape binary string for use as SQL string literal on this connection. /** You can also just use @ref esc with a binary string. */ @@ -917,7 +906,6 @@ public: /** You can also just use @ref esc with a binary string. */ [[nodiscard]] std::string esc_raw(bytes_view, std::span buffer) const; -#if defined(PQXX_HAVE_CONCEPTS) /// Escape binary string for use as SQL string literal on this connection. /** You can also just use @ref esc with a binary string. */ template @@ -925,16 +913,13 @@ public: { return esc_raw(bytes_view{std::data(data), std::size(data)}); } -#endif -#if defined(PQXX_HAVE_CONCEPTS) /// Escape binary string for use as SQL string literal, into `buffer`. template [[nodiscard]] zview esc_raw(DATA const &data, std::span buffer) const { return this->esc(binary_cast(data), buffer); } -#endif // TODO: Make "into buffer" variant to eliminate a string allocation. /// Unescape binary data, e.g. from a `bytea` field. @@ -956,7 +941,6 @@ public: /// Escape and quote a string of binary data. std::string quote_raw(bytes_view) const; -#if defined(PQXX_HAVE_CONCEPTS) /// Escape and quote a string of binary data. /** You can also just use @ref quote with binary data. */ template @@ -964,7 +948,6 @@ public: { return quote_raw(bytes_view{std::data(data), std::size(data)}); } -#endif // TODO: Make "into buffer" variant to eliminate a string allocation. /// Escape and quote an SQL identifier for use in a query. @@ -1437,7 +1420,6 @@ inline std::string connection::quote_columns(STRINGS const &columns) const } -#if defined(PQXX_HAVE_CONCEPTS) template inline connection::connection(MAPPING const ¶ms) { @@ -1459,6 +1441,5 @@ inline connection::connection(MAPPING const ¶ms) values.push_back(nullptr); init(std::data(keys), std::data(values)); } -#endif // PQXX_HAVE_CONCEPTS } // namespace pqxx #endif diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index 3f07a3db7..89a84f89b 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -895,7 +895,6 @@ template<> struct nullness : no_null {}; -#if defined(PQXX_HAVE_CONCEPTS) template struct nullness : no_null {}; @@ -940,7 +939,6 @@ template struct string_traits return buf; } }; -#endif // PQXX_HAVE_CONCEPTS template<> struct string_traits diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index 4ac35c50e..5212cd6a8 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -95,7 +95,6 @@ public: */ void append(bytes 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. @@ -104,7 +103,6 @@ public: { append(bytes_view{std::data(data), std::size(data)}); } -#endif // PQXX_HAVE_CONCEPTS /// Append a non-null binary parameter. void append(bytes &&) &; @@ -136,10 +134,8 @@ public: /// Append all elements of `range` as parameters. template void append_multi(RANGE const &range) & { -#if defined(PQXX_HAVE_CONCEPTS) if constexpr (std::ranges::sized_range) reserve(std::size(*this) + std::size(range)); -#endif for (auto &value : range) append(value); } diff --git a/include/pqxx/strconv.hxx b/include/pqxx/strconv.hxx index 5aa71ab70..9781a6dcb 100644 --- a/include/pqxx/strconv.hxx +++ b/include/pqxx/strconv.hxx @@ -592,7 +592,6 @@ inline zview generic_to_buf(char *begin, char *end, TYPE const &value) } -#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. * @@ -603,7 +602,6 @@ inline zview generic_to_buf(char *begin, char *end, TYPE const &value) template concept binary = std::ranges::contiguous_range and std::is_same_v>, std::byte>; -#endif //@} } // namespace pqxx diff --git a/include/pqxx/stream_to.hxx b/include/pqxx/stream_to.hxx index bde579f21..37e86c27f 100644 --- a/include/pqxx/stream_to.hxx +++ b/include/pqxx/stream_to.hxx @@ -125,7 +125,6 @@ public: return raw_table(tx, cx.quote_table(path), cx.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. @@ -157,7 +156,6 @@ public: { return stream_to::raw_table(tx, path, tx.conn().quote_columns(columns)); } -#endif // PQXX_HAVE_CONCEPTS explicit stream_to(stream_to &&other) : // (This first step only moves the transaction_focus base-class diff --git a/include/pqxx/transaction_base.hxx b/include/pqxx/transaction_base.hxx index 1d12f1557..19942e01e 100644 --- a/include/pqxx/transaction_base.hxx +++ b/include/pqxx/transaction_base.hxx @@ -235,7 +235,6 @@ public: return conn().quote(t); } -#if defined(PQXX_HAVE_CONCEPTS) /// Binary-escape and quote a binary string for use as an SQL constant. /** For binary data you can also just use @ref quote(data). */ template @@ -243,7 +242,6 @@ public: { return conn().quote_raw(data); } -#endif /// Escape an SQL identifier for use in a query. [[nodiscard]] std::string quote_name(std::string_view identifier) const diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index a04892a01..674b9ed83 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -77,24 +77,14 @@ template using strip_t = std::remove_cvref_t; -#if defined(PQXX_HAVE_CONCEPTS) /// The type of a container's elements. /** At the time of writing there's a similar thing in `std::experimental`, * which we may or may not end up using for this. */ template using value_type = std::remove_cvref_t()))>; -#else // PQXX_HAVE_CONCEPTS -/// The type of a container's elements. -/** At the time of writing there's a similar thing in `std::experimental`, - * which we may or may not end up using for this. - */ -template -using value_type = std::remove_cvref_t()))>; -#endif // PQXX_HAVE_CONCEPTS -#if defined(PQXX_HAVE_CONCEPTS) /// Concept: Any type that we can read as a string of `char`. template concept char_string = std::ranges::contiguous_range and @@ -109,7 +99,6 @@ concept char_strings = template concept potential_binary = std::ranges::contiguous_range and (sizeof(value_type) == 1); -#endif // PQXX_HAVE_CONCEPTS /// Marker for @ref stream_from constructors: "stream from table." diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index 721e92b45..efb36f59f 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -214,12 +214,6 @@ struct PQXX_LIBEXPORT thread_safety_model [[nodiscard]] PQXX_LIBEXPORT thread_safety_model describe_thread_safety(); -#if defined(PQXX_HAVE_CONCEPTS) -# define PQXX_POTENTIAL_BINARY_ARG pqxx::potential_binary -#else -# define PQXX_POTENTIAL_BINARY_ARG typename -#endif - /// Custom `std::char_trast` if the compiler does not provide one. /** Needed if the standard library lacks a generic implementation or a * specialisation for std::byte. They aren't strictly required to provide @@ -340,7 +334,7 @@ using bytes_view = std::conditional< * @warning You must keep the storage holding the actual data alive for as * long as you might use this function's return value. */ -template +template bytes_view binary_cast(TYPE const &data) { static_assert(sizeof(value_type) == 1); @@ -353,13 +347,10 @@ bytes_view binary_cast(TYPE const &data) } -#if defined(PQXX_HAVE_CONCEPTS) +/// A type one byte in size. template concept char_sized = (sizeof(CHAR) == 1); -# define PQXX_CHAR_SIZED_ARG char_sized -#else -# define PQXX_CHAR_SIZED_ARG typename -#endif + /// Construct a type that libpqxx will recognise as binary. /** Takes a data pointer and a size, without being too strict about their @@ -368,7 +359,7 @@ concept char_sized = (sizeof(CHAR) == 1); * This makes it a little easier to turn binary data, in whatever form you * happen to have it, into binary data as libpqxx understands it. */ -template +template bytes_view binary_cast(CHAR const *data, SIZE size) { static_assert(sizeof(CHAR) == 1); diff --git a/include/pqxx/zview.hxx b/include/pqxx/zview.hxx index 546a058a6..de1110521 100644 --- a/include/pqxx/zview.hxx +++ b/include/pqxx/zview.hxx @@ -114,7 +114,6 @@ constexpr zview operator"" _zv(char const str[], std::size_t len) noexcept } // namespace pqxx -#if defined(PQXX_HAVE_CONCEPTS) /// A zview is a view. template<> inline constexpr bool std::ranges::enable_view{true}; @@ -135,7 +134,6 @@ concept ZString = std::is_convertible_v, char const *> or std::is_convertible_v, zview> or std::is_convertible_v; } // namespace pqxx::internal -#endif // PQXX_HAVE_CONCEPTS namespace pqxx::internal diff --git a/pqxx_cxx_feature_checks.ac b/pqxx_cxx_feature_checks.ac index 540d4e4ba..4ffe172a3 100644 --- a/pqxx_cxx_feature_checks.ac +++ b/pqxx_cxx_feature_checks.ac @@ -19,16 +19,6 @@ AC_COMPILE_IFELSE( [Define if this feature is available.]), PQXX_HAVE_CHARCONV_FLOAT=no) AC_MSG_RESULT($PQXX_HAVE_CHARCONV_FLOAT) -AC_MSG_CHECKING([PQXX_HAVE_CONCEPTS]) -PQXX_HAVE_CONCEPTS=yes -AC_COMPILE_IFELSE( - [read_test(PQXX_HAVE_CONCEPTS.cxx)], - AC_DEFINE( - [PQXX_HAVE_CONCEPTS], - 1, - [Define if this feature is available.]), - PQXX_HAVE_CONCEPTS=no) -AC_MSG_RESULT($PQXX_HAVE_CONCEPTS) AC_MSG_CHECKING([PQXX_HAVE_CXA_DEMANGLE]) PQXX_HAVE_CXA_DEMANGLE=yes AC_COMPILE_IFELSE( diff --git a/test/unit/test_connection.cxx b/test/unit/test_connection.cxx index 4a88e8efb..e9a52809d 100644 --- a/test/unit/test_connection.cxx +++ b/test/unit/test_connection.cxx @@ -105,7 +105,6 @@ void test_connection_string() } -#if defined(PQXX_HAVE_CONCEPTS) template std::size_t length(STR const &str) { return std::size(str); @@ -116,12 +115,10 @@ std::size_t length(char const str[]) { return std::strlen(str); } -#endif // PQXX_HAVE_CONCEPTS template void test_params_type() { -#if defined(PQXX_HAVE_CONCEPTS) using item_t = std::remove_reference_t< decltype(*std::declval>())>; using key_t = decltype(std::get<0>(std::declval())); @@ -160,7 +157,6 @@ template void test_params_type() "Could not find value for '" + std::string{value} + "' in connection string: " + connstr); } -#endif // PQXX_HAVE_CONCEPTS } diff --git a/test/unit/test_escape.cxx b/test/unit/test_escape.cxx index d88570ce4..1966f48f3 100644 --- a/test/unit/test_escape.cxx +++ b/test/unit/test_escape.cxx @@ -157,7 +157,6 @@ void test_escaping() void test_esc_escapes_into_buffer() { -#if defined(PQXX_HAVE_CONCEPTS) pqxx::connection cx; pqxx::work tx{cx}; @@ -171,13 +170,11 @@ void test_esc_escapes_into_buffer() pqxx::bytes const data{std::byte{0x22}, std::byte{0x43}}; auto escaped_data(tx.esc(data, buffer)); PQXX_CHECK_EQUAL(escaped_data, "\\x2243", "Binary data escaped wrong."); -#endif } void test_esc_accepts_various_types() { -#if defined(PQXX_HAVE_CONCEPTS) pqxx::connection cx; pqxx::work tx{cx}; @@ -191,13 +188,11 @@ void test_esc_accepts_various_types() std::vector const data{std::byte{0x23}, std::byte{0x44}}; auto escaped_data(tx.esc(data, buffer)); PQXX_CHECK_EQUAL(escaped_data, "\\x2344", "Binary data escaped wrong."); -#endif } void test_binary_esc_checks_buffer_length() { -#if defined(PQXX_HAVE_CONCEPTS) pqxx::connection cx; pqxx::work tx{cx}; @@ -216,7 +211,6 @@ void test_binary_esc_checks_buffer_length() PQXX_CHECK_THROWS( pqxx::ignore_unused(tx.esc(bin, buf)), pqxx::range_error, "Didn't get expected exception from escape overrun."); -#endif } diff --git a/test/unit/test_prepared_statement.cxx b/test/unit/test_prepared_statement.cxx index 9d435e4ea..54596a54a 100644 --- a/test/unit/test_prepared_statement.cxx +++ b/test/unit/test_prepared_statement.cxx @@ -216,7 +216,6 @@ void test_binary() input, "Binary string as shared_ptr-to-optional went wrong."); } -#if defined(PQXX_HAVE_CONCEPTS) // By the way, it doesn't have to be a pqxx::bytes. Any contiguous range // will do. { @@ -228,7 +227,6 @@ void test_binary() PQXX_CHECK_EQUAL(static_cast(oval[0]), int('x'), "Wrong data."); PQXX_CHECK_EQUAL(static_cast(oval[1]), int('v'), "Wrong data."); } -#endif } diff --git a/test/unit/test_stream_to.cxx b/test/unit/test_stream_to.cxx index 91e52a247..20efef55f 100644 --- a/test/unit/test_stream_to.cxx +++ b/test/unit/test_stream_to.cxx @@ -406,12 +406,7 @@ void test_stream_to_factory_with_dynamic_columns() tx.exec("CREATE TEMP TABLE pqxx_stream_to(a integer, b varchar)").no_rows(); std::vector columns{"a", "b"}; -#if defined(PQXX_HAVE_CONCEPTS) auto stream{pqxx::stream_to::table(tx, {"pqxx_stream_to"}, columns)}; -#else - auto stream{pqxx::stream_to::raw_table( - tx, cx.quote_table({"pqxx_stream_to"}), cx.quote_columns(columns))}; -#endif stream.write_values(4, "four"); stream.complete(); From e606cceedfd925cb7290d32841092a08c3fe49e1 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 21:29:40 +0100 Subject: [PATCH 49/64] Format. --- include/pqxx/internal/conversions.hxx | 6 ++++-- include/pqxx/row.hxx | 5 +---- include/pqxx/strconv.hxx | 5 +++-- include/pqxx/types.hxx | 15 ++++++++------- include/pqxx/util.hxx | 9 +++++---- include/pqxx/zview.hxx | 7 ++++--- src/transaction_base.cxx | 3 +-- 7 files changed, 26 insertions(+), 24 deletions(-) diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index 89a84f89b..844f81cbe 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -357,7 +357,8 @@ template struct string_traits> { return std::visit( [begin, end](auto const &i) { - return string_traits>::into_buf(begin, end, i); + return string_traits>::into_buf( + begin, end, i); }, value); } @@ -365,7 +366,8 @@ template struct string_traits> { return std::visit( [begin, end](auto const &i) { - return string_traits>::to_buf(begin, end, i); + return string_traits>::to_buf( + begin, end, i); }, value); } diff --git a/include/pqxx/row.hxx b/include/pqxx/row.hxx index 4f0c53193..d8ac841f2 100644 --- a/include/pqxx/row.hxx +++ b/include/pqxx/row.hxx @@ -103,10 +103,7 @@ public: */ reference at(zview col_name) const; - [[nodiscard]] constexpr size_type size() const noexcept - { - return m_end; - } + [[nodiscard]] constexpr size_type size() const noexcept { return m_end; } /// Row number, assuming this is a real row and not end()/rend(). [[nodiscard]] constexpr result::size_type rownumber() const noexcept diff --git a/include/pqxx/strconv.hxx b/include/pqxx/strconv.hxx index 9781a6dcb..3034c517e 100644 --- a/include/pqxx/strconv.hxx +++ b/include/pqxx/strconv.hxx @@ -600,8 +600,9 @@ inline zview generic_to_buf(char *begin, char *end, TYPE const &value) * we can reference them by a pointer. */ template -concept binary = std::ranges::contiguous_range and - std::is_same_v>, std::byte>; +concept binary = + std::ranges::contiguous_range and + std::is_same_v>, std::byte>; //@} } // namespace pqxx diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index 674b9ed83..28185fb36 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -73,8 +73,7 @@ enum class format : int /// Remove any constness, volatile, and reference-ness from a type. /** @deprecated Use `std::remove_cvref` instead. */ -template -using strip_t = std::remove_cvref_t; +template using strip_t = std::remove_cvref_t; /// The type of a container's elements. @@ -82,18 +81,20 @@ using strip_t = std::remove_cvref_t; * which we may or may not end up using for this. */ template -using value_type = std::remove_cvref_t()))>; +using value_type = + std::remove_cvref_t()))>; /// Concept: Any type that we can read as a string of `char`. template -concept char_string = std::ranges::contiguous_range and - std::same_as>, char>; +concept char_string = + std::ranges::contiguous_range and + std::same_as>, char>; /// Concept: Anything we can iterate to get things we can read as strings. template -concept char_strings = - std::ranges::range and char_string>>; +concept char_strings = std::ranges::range and + char_string>>; /// Concept: Anything we might want to treat as binary data. template diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index efb36f59f..7cb17e62e 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -334,8 +334,7 @@ using bytes_view = std::conditional< * @warning You must keep the storage holding the actual data alive for as * long as you might use this function's return value. */ -template -bytes_view binary_cast(TYPE const &data) +template bytes_view binary_cast(TYPE const &data) { static_assert(sizeof(value_type) == 1); // C++20: Use std::as_bytes. @@ -535,7 +534,8 @@ using args_t = decltype(args_f(std::declval())); * used to deduce the right types. */ template -std::tuple...> strip_types(std::tuple const &); +std::tuple...> +strip_types(std::tuple const &); /// Take a tuple type and apply std::remove_cvref_t to its component types. @@ -582,7 +582,8 @@ error_string(int err_num, std::array &buffer) # else auto const err_result{strerror_r(err_num, std::data(buffer), BYTES)}; # endif - if constexpr (std::is_same_v, char *>) + if constexpr (std::is_same_v< + std::remove_cvref_t, char *>) { // GNU version of strerror_r; returns the error string, which may or may // not reside within buffer. diff --git a/include/pqxx/zview.hxx b/include/pqxx/zview.hxx index de1110521..94083f686 100644 --- a/include/pqxx/zview.hxx +++ b/include/pqxx/zview.hxx @@ -130,9 +130,10 @@ namespace pqxx::internal * support each of these individually. */ template -concept ZString = std::is_convertible_v, char const *> or - std::is_convertible_v, zview> or - std::is_convertible_v; +concept ZString = + std::is_convertible_v, char const *> or + std::is_convertible_v, zview> or + std::is_convertible_v; } // namespace pqxx::internal diff --git a/src/transaction_base.cxx b/src/transaction_base.cxx index 75dad4667..c82c8162a 100644 --- a/src/transaction_base.cxx +++ b/src/transaction_base.cxx @@ -238,8 +238,7 @@ class PQXX_PRIVATE command : pqxx::transaction_focus }; } // namespace -pqxx::result -pqxx::transaction_base::exec(std::string_view query) +pqxx::result pqxx::transaction_base::exec(std::string_view query) { check_pending_error(); From 9e4eb644df10aa6440633056e1dd5922716ec8d4 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 21:36:02 +0100 Subject: [PATCH 50/64] Cosmetic. --- include/pqxx/strconv.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pqxx/strconv.hxx b/include/pqxx/strconv.hxx index 3034c517e..590b58c59 100644 --- a/include/pqxx/strconv.hxx +++ b/include/pqxx/strconv.hxx @@ -599,7 +599,7 @@ inline zview generic_to_buf(char *begin, char *end, TYPE const &value) * is a @c std::byte, and they must all be laid out contiguously in memory so * we can reference them by a pointer. */ -template +template concept binary = std::ranges::contiguous_range and std::is_same_v>, std::byte>; From eaf8d8811ce1d88556a2dc94014a57d9b9c13bfc Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 22:35:30 +0100 Subject: [PATCH 51/64] Work through some C++20 notes. --- include/pqxx/connection.hxx | 2 -- include/pqxx/doc/binary-data.md | 4 ++-- include/pqxx/internal/conversions.hxx | 4 ++-- include/pqxx/params.hxx | 1 - include/pqxx/prepared_statement.hxx | 4 ++-- src/connection.cxx | 8 ++++++-- src/encodings.cxx | 11 ++++------- src/sql_cursor.cxx | 1 - src/strconv.cxx | 6 +++--- test/unit/test_range.cxx | 7 +------ tools/generate_cxx_checks.py | 2 +- 11 files changed, 21 insertions(+), 29 deletions(-) diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index 3c4c33e4c..27a77a673 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -1058,8 +1058,6 @@ public: */ void set_verbosity(error_verbosity verbosity) & noexcept; - // C++20: Use std::callable. - /// Set a notice handler to the connection. /** When a notice comes in (a warning or error message), the connection or * result object on which it happens will call the notice handler, passing diff --git a/include/pqxx/doc/binary-data.md b/include/pqxx/doc/binary-data.md index 7c85cf4b8..57d37e5a1 100644 --- a/include/pqxx/doc/binary-data.md +++ b/include/pqxx/doc/binary-data.md @@ -9,8 +9,8 @@ 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 `pqxx::bytes` or `pqxx::bytes_view`; or if you're building in C++20 or -better, anything that's a block of contiguous `std::byte` in memory. +either `pqxx::bytes` or `pqxx::bytes_view`, or anything else 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 diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index 844f81cbe..25f3678e4 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -103,9 +103,9 @@ inline char *generic_into_buf(char *begin, char *end, T const &value) } -// C++20: Guard with concept? +// TODO: Define type_traits directly? /// String traits for builtin integral types (though not bool). -template struct integral_traits +template struct integral_traits { static constexpr bool converts_to_string{true}; static constexpr bool converts_from_string{true}; diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index 5212cd6a8..9b684be0c 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -56,7 +56,6 @@ public: return m_params.size(); } - // C++20: noexcept. /// 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 diff --git a/include/pqxx/prepared_statement.hxx b/include/pqxx/prepared_statement.hxx index e17b2b207..b3a89c149 100644 --- a/include/pqxx/prepared_statement.hxx +++ b/include/pqxx/prepared_statement.hxx @@ -39,8 +39,8 @@ namespace pqxx * zero. If you need a zero byte, you're dealing with binary strings, not * regular strings. Represent binary strings on the SQL side as `BYTEA` * (or as large objects). On the C++ side, use types like `pqxx::bytes` or - * `pqxx::bytes_view` or (in C++20) `std::vector`. Also, consider - * large objects on the SQL side and @ref blob on the C++ side. + * `pqxx::bytes_view` or `std::vector`. Also, consider large + * objects on the SQL side and @ref blob on the C++ side. * * @warning Passing the wrong number of parameters to a prepared or * parameterised statement will _break the connection._ The usual exception diff --git a/src/connection.cxx b/src/connection.cxx index 539b337d4..bddd7a0e0 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -639,9 +639,13 @@ int pqxx::connection::get_notifs() } auto const handler{m_notification_handlers.find(N->relname)}; - // C++20: Use "dot notation" to initialise struct fields. if (handler != std::end(m_notification_handlers)) - (handler->second)(notification{*this, channel, N->extra, N->be_pid}); + (handler->second)(notification{ + .conn=*this, + .channel=channel, + .payload=N->extra, + .backend_pid=N->be_pid, + }); N.reset(); } diff --git a/src/encodings.cxx b/src/encodings.cxx index f583f644b..fee1e585a 100644 --- a/src/encodings.cxx +++ b/src/encodings.cxx @@ -65,8 +65,7 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) return pqxx::internal::encoding_group::BIG5; [[unlikely]] break; case 'E': - // C++20: Use string_view::starts_with(). - if ((sz >= 6u) and (encoding_name.substr(0, 4) == "EUC_"sv)) + if (encoding_name.starts_with("EUC_"sv)) { auto const subtype{encoding_name.substr(4)}; static constexpr std::array subtypes{ @@ -90,8 +89,7 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) [[unlikely]] break; case 'I': // We know iso-8859-X, where 5 <= X < 9. They're all monobyte encodings. - // C++20: Use string_view::starts_with(). - if ((sz == 10) and (encoding_name.substr(0, 9) == "ISO_8859_"sv)) + if (encoding_name.starts_with("ISO_8859_"sv)) { char const subtype{encoding_name[9]}; if (('5' <= subtype) and (subtype < '9')) @@ -108,8 +106,7 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) [[unlikely]] break; case 'L': // We know LATIN1 through LATIN10. - // C++20: Use string_view::starts_with(). - if (encoding_name.substr(0, 5) == "LATIN"sv) + if (encoding_name.starts_with("LATIN"sv)) { auto const subtype{encoding_name.substr(5)}; if (subtype.size() == 1) @@ -140,7 +137,7 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) if (encoding_name == "UHC"sv) return pqxx::internal::encoding_group::UHC; else if (encoding_name == "UTF8"sv) - return pqxx::internal::encoding_group::UTF8; + [[likely]] return pqxx::internal::encoding_group::UTF8; [[unlikely]] break; case 'W': if (encoding_name.substr(0, 3) == "WIN"sv) diff --git a/src/sql_cursor.cxx b/src/sql_cursor.cxx index 2bc84b403..4a7c2537c 100644 --- a/src/sql_cursor.cxx +++ b/src/sql_cursor.cxx @@ -66,7 +66,6 @@ find_query_end(std::string_view query, pqxx::internal::encoding_group enc) if (enc == pqxx::internal::encoding_group::MONOBYTE) { // This is an encoding where we can scan backwards from the end. - // C++20: Use string_view::ends_with() and sub-view. while (end > 0 and useless_trail(query[end - 1])) --end; } else diff --git a/src/strconv.cxx b/src/strconv.cxx index 0a491f494..109dd8cbc 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -146,7 +146,7 @@ inline char *wrap_to_chars(char *begin, char *end, T const &value) namespace pqxx::internal { -template +template // NOLINTNEXTLINE(readability-non-const-parameter) zview integral_traits::to_buf(char *begin, char *end, T const &value) { @@ -189,7 +189,7 @@ template zview integral_traits::to_buf( char *, char *, unsigned long long const &); -template +template char *integral_traits::into_buf(char *begin, char *end, T const &value) { // This is exactly what to_chars is good at. Trust standard library @@ -541,7 +541,7 @@ template std::string to_string_float(T value) namespace pqxx::internal { -template T integral_traits::from_string(std::string_view text) +template T integral_traits::from_string(std::string_view text) { return from_string_arithmetic(text); } diff --git a/test/unit/test_range.cxx b/test/unit/test_range.cxx index 4df6f5520..0272a889b 100644 --- a/test/unit/test_range.cxx +++ b/test/unit/test_range.cxx @@ -543,11 +543,7 @@ void test_range_conversion() constexpr void test_range_is_constexpr() { -// Test compile-time operations. -// -// A few things in the standard library need to be constexpr for this to work, -// so we only test it in C++20. -#if __cplusplus >= 202002L + // Test compile-time operations. using range = pqxx::range; using ibound = pqxx::inclusive_bound; @@ -556,7 +552,6 @@ constexpr void test_range_is_constexpr() static_assert(oneone == oneone); static_assert(oneone != onethree); static_assert(onethree.contains(oneone)); -#endif } diff --git a/tools/generate_cxx_checks.py b/tools/generate_cxx_checks.py index 878743679..7ac888b7e 100755 --- a/tools/generate_cxx_checks.py +++ b/tools/generate_cxx_checks.py @@ -3,7 +3,7 @@ """Generate autoconf/CMake checks for C++ feature check macros. Produces feature checks for those features that we can detect based on just -the C++20 feature check macros. +the C++ feature check macros. Reads the test libpqxx feature macro names, as well as the C++ feature check macros that control them, from cxx_features.txt in the source tree. From a55748447db103501e64e960f06be3aa0bab10e7 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 2 Jan 2025 22:42:34 +0100 Subject: [PATCH 52/64] Apply some more standard concepts. --- include/pqxx/internal/conversions.hxx | 4 ++-- src/strconv.cxx | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index 25f3678e4..7694d70d5 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -124,9 +125,8 @@ template struct integral_traits }; -// C++20: Guard with concept? /// String traits for builtin floating-point types. -template struct float_traits +template struct float_traits { static constexpr bool converts_to_string{true}; static constexpr bool converts_from_string{true}; diff --git a/src/strconv.cxx b/src/strconv.cxx index 109dd8cbc..e08d0365a 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -437,7 +437,7 @@ inline T PQXX_COLD from_string_awful_float(std::string_view text) namespace pqxx::internal { /// Floating-point to_buf implemented in terms of to_string. -template +template zview float_traits::to_buf(char *begin, char *end, T const &value) { #if defined(PQXX_HAVE_CHARCONV_FLOAT) @@ -476,7 +476,7 @@ template zview float_traits::to_buf(char *, char *, long double const &); -template +template char *float_traits::into_buf(char *begin, char *end, T const &value) { #if defined(PQXX_HAVE_CHARCONV_FLOAT) @@ -559,7 +559,8 @@ template unsigned long long integral_traits::from_string(std::string_view); -template T float_traits::from_string(std::string_view text) +template +T float_traits::from_string(std::string_view text) { #if defined(PQXX_HAVE_CHARCONV_FLOAT) return from_string_arithmetic(text); From a150eb91330c6b0228f00977cdba38a1a78825ec Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 9 Jan 2025 01:05:44 +0100 Subject: [PATCH 53/64] Assume concepts (update from master). --- config/Makefile.in | 2 +- test/unit/test_result_iteration.cxx | 2 -- test/unit/test_row.cxx | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/config/Makefile.in b/config/Makefile.in index 51586bda2..9b1454986 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -124,7 +124,7 @@ am__can_run_installinfo = \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \ - config.sub install-sh ltmain.sh missing mkinstalldirs + config.sub depcomp install-sh ltmain.sh missing mkinstalldirs DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ diff --git a/test/unit/test_result_iteration.cxx b/test/unit/test_result_iteration.cxx index 495ff1eff..ff444e28b 100644 --- a/test/unit/test_result_iteration.cxx +++ b/test/unit/test_result_iteration.cxx @@ -12,9 +12,7 @@ void test_result_iteration() pqxx::connection cx; pqxx::work tx{cx}; pqxx::result r{tx.exec("SELECT generate_series(1, 3)")}; -#if defined(PQXX_HAVE_CONCEPTS) static_assert(std::forward_iterator); -#endif PQXX_CHECK(std::end(r) != std::begin(r), "Broken begin/end."); PQXX_CHECK(std::rend(r) != std::rbegin(r), "Broken rbegin/rend."); diff --git a/test/unit/test_row.cxx b/test/unit/test_row.cxx index 378375efe..7beaa5f03 100644 --- a/test/unit/test_row.cxx +++ b/test/unit/test_row.cxx @@ -11,9 +11,7 @@ void test_row() pqxx::connection cx; pqxx::work tx{cx}; pqxx::row r{tx.exec("SELECT 1, 2, 3").one_row()}; -#if defined(PQXX_HAVE_CONCEPTS) static_assert(std::forward_iterator); -#endif PQXX_CHECK_EQUAL(std::size(r), 3, "Unexpected row size."); PQXX_CHECK_EQUAL(r.at(0).as(), 1, "Wrong value at index 0."); PQXX_CHECK(std::begin(r) != std::end(r), "Broken row iteration."); From 50c8d03f6a1afce5d5e72f1e1caf9555193947ab Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 13 Jan 2025 01:14:29 +0100 Subject: [PATCH 54/64] Format. --- src/connection.cxx | 8 ++++---- src/encodings.cxx | 4 ++-- src/strconv.cxx | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/connection.cxx b/src/connection.cxx index bddd7a0e0..18db0a470 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -641,10 +641,10 @@ int pqxx::connection::get_notifs() auto const handler{m_notification_handlers.find(N->relname)}; if (handler != std::end(m_notification_handlers)) (handler->second)(notification{ - .conn=*this, - .channel=channel, - .payload=N->extra, - .backend_pid=N->be_pid, + .conn = *this, + .channel = channel, + .payload = N->extra, + .backend_pid = N->be_pid, }); N.reset(); diff --git a/src/encodings.cxx b/src/encodings.cxx index fee1e585a..2e8d80b4c 100644 --- a/src/encodings.cxx +++ b/src/encodings.cxx @@ -136,8 +136,8 @@ pqxx::internal::encoding_group enc_group(std::string_view encoding_name) case 'U': if (encoding_name == "UHC"sv) return pqxx::internal::encoding_group::UHC; - else if (encoding_name == "UTF8"sv) - [[likely]] return pqxx::internal::encoding_group::UTF8; + else if (encoding_name == "UTF8"sv) [[likely]] + return pqxx::internal::encoding_group::UTF8; [[unlikely]] break; case 'W': if (encoding_name.substr(0, 3) == "WIN"sv) diff --git a/src/strconv.cxx b/src/strconv.cxx index e08d0365a..e19509311 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -541,7 +541,8 @@ template std::string to_string_float(T value) namespace pqxx::internal { -template T integral_traits::from_string(std::string_view text) +template +T integral_traits::from_string(std::string_view text) { return from_string_arithmetic(text); } From 5449a009127f301d3530c0748a3853f0e4ee2e24 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sun, 19 Jan 2025 13:52:58 +0100 Subject: [PATCH 55/64] String conversion to `string_view`. More binary types. (#926) * Retire `binarystring`. This type has been deprecated since 7.2.0, more than 4 years ago. * String conversion to `string_view`. More binary types. Fixes: https://github.com/jtv/libpqxx/issues/694 Fixes: https://github.com/jtv/libpqxx/issues/827 Making much broader use of concepts. String conversions now accept any contiguous range of `std::byte` as binary data. Traits specialisations for integer and floating-point types are simpler now. And you can now just convert from string to `std::string_view` (or `char const *`), so long as you don't access it after the original string's lifetime ends. * Work around Visual Studio 2022 concepts problem. This compiler was having trouble with the syntax I used to specialise the generic `string_traits` template to a _concept_ `T` (as opposed to run-of-the-mill specialisation to a _type_ `T`). So just for the floating-point string traits, I went back to the old setup where I had a separate implementation type template (`string_float_traits`) and derived the `string_traits` implementations for those types from instantiations of that template. * Forbid string conversion from `char const *`. It was stupid of me to allow this. I hope nobody ever used it. --- NEWS | 4 + config/Makefile.in | 2 +- include/pqxx/doc/datatypes.md | 27 ++- include/pqxx/field.hxx | 45 +--- include/pqxx/internal/conversions.hxx | 321 ++++++++------------------ include/pqxx/params.hxx | 2 + include/pqxx/strconv.hxx | 18 +- include/pqxx/types.hxx | 42 +++- include/pqxx/util.hxx | 16 +- src/strconv.cxx | 214 ++++++++--------- test/unit/test_string_conversion.cxx | 18 ++ 11 files changed, 291 insertions(+), 418 deletions(-) diff --git a/NEWS b/NEWS index b5b9055b4..4c3e0a601 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,9 @@ 8.0.0 - C++20 is now the oldest C++ version that libpqxx supports. + - "String conversion" to `std::string_view` is now supported. (#694) + - **Beware lifetime** when "converting" a string to `std::string_view`! + - Conversion from string to `char const *` is no longer allowed. + - Binary data can be any `std::contiguous_range` of `std::byte`. (#925) - Retired `binarystring` and its headers. Use `blob` instead. - Retired `connection_base` type alias. Use `connection`. - Retired `pqxx::encrypt_password()`. Use the ones in `pqxx::connection`. diff --git a/config/Makefile.in b/config/Makefile.in index 9b1454986..51586bda2 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -124,7 +124,7 @@ am__can_run_installinfo = \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \ - config.sub depcomp install-sh ltmain.sh missing mkinstalldirs + config.sub install-sh ltmain.sh missing mkinstalldirs DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ diff --git a/include/pqxx/doc/datatypes.md b/include/pqxx/doc/datatypes.md index 4584f24b8..b5bc463ee 100644 --- a/include/pqxx/doc/datatypes.md +++ b/include/pqxx/doc/datatypes.md @@ -12,8 +12,8 @@ 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.** +specialise several templates. And, **the API for doing this can change with +any major libpqxx release.** If that happens, your code may fail to compile with the newer libpqxx version, and you'll have to go through the `NEWS` file to find the API changes. Usually @@ -107,13 +107,16 @@ namespace near the top of your translation unit, and pass the type as an argument. The library also provides specialisations for `std::optional`, -`std::shared_ptr`, and `std::unique_ptr`. If you have conversions for -`T`, you'll also automatically have conversions for those. +`std::shared_ptr`, and `std::unique_ptr` (for any given `T`). If you +have conversions for `T`, you'll also automatically have conversions for those. Specialise `type_name` ---------------------- +(This is a feature that should disappear once we have introspection in the C++ +language.) + 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. @@ -142,12 +145,16 @@ 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. +"null value" built in. For example, a `std::optional` instantiation has a +value that neatly maps to an SQL null: the un-initialised state. + +If your type has a value like that, its `pqxx::nullness` specialisation 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. There is no "null `int`" in C++. In that kind of case, just -derive your nullness traits from `pqxx::no_null` as a shorthand: +derive your nullness traits from `pqxx::no_null` as a shorthand: This tells +libpqxx that your type has no null value of its own. ```cxx // T is your type. @@ -196,9 +203,9 @@ where `NULL <> NULL`). Or `T` may have multiple different null values. Or `T` may override the comparison operator to behave in some unusual way. 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::always_null` to `true` (as well as `has_null` of course), -and you won't need to define any actual conversions. +This is the case for `std::nullptr_t` and `std::nullopt_t`. In a case like +that, you set `nullness::always_null` to `true` (as well as `has_null` +of course), and you won't need to define any actual conversions. Specialise `string_traits` diff --git a/include/pqxx/field.hxx b/include/pqxx/field.hxx index e1fe709c3..16fe5a93c 100644 --- a/include/pqxx/field.hxx +++ b/include/pqxx/field.hxx @@ -344,44 +344,13 @@ template<> inline bool field::to(char const *&obj) const } -template<> inline bool field::to(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 &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() const -{ - if (is_null()) - internal::throw_null_conversion(type_name); - return view(); -} - - -template<> -inline std::string_view -field::as(std::string_view const &default_value) const -{ - return is_null() ? default_value : view(); -} - - +/// Specialization: `to(zview &)`. +/** This conversion is not generally available, since the general conversion + * would not know whether there was indeed a terminating zero at the end of + * the string. (It could check, but it would have no way of knowing that a + * zero occurring after the string in memory was actually part of the same + * allocation.) + */ template<> inline bool field::to(zview &obj) const { bool const null{is_null()}; diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index 7694d70d5..f03c32ef8 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -70,7 +70,8 @@ throw_null_conversion(std::string_view type); * ensure that the compiler disallows their use. The compiler error message * will at least contain a hint of the root of the problem. */ -template struct disallowed_ambiguous_char_conversion +template +struct disallowed_ambiguous_char_conversion { static constexpr bool converts_to_string{false}; static constexpr bool converts_from_string{false}; @@ -104,34 +105,24 @@ inline char *generic_into_buf(char *begin, char *end, T const &value) } -// TODO: Define type_traits directly? -/// String traits for builtin integral types (though not bool). -template struct integral_traits +/// String traits for builtin floating-point types. +/** It _would_ make sense to define this directly as the definition for + * `pqxx::string_traits` where `T` is a `std::floating_point`. However + * Viual Studio 2022 does not seem to accept that syntax. + * + * So instead, we create a separate base class for `std::floating_point` types + * and then derive specialisatinos of `pqxx::string_traits` from that. + */ +template struct float_string_traits { static constexpr bool converts_to_string{true}; static constexpr bool converts_from_string{true}; - static PQXX_LIBEXPORT T from_string(std::string_view text); - static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value); - static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value); - static constexpr std::size_t size_buffer(T const &) noexcept - { - /** Includes a sign if needed; the number of base-10 digits which the type - * can reliably represent; the one extra base-10 digit which the type can - * only partially represent; and the terminating zero. - */ - return std::is_signed_v + std::numeric_limits::digits10 + 1 + 1; - } -}; + static PQXX_LIBEXPORT T from_string(std::string_view text); + static PQXX_LIBEXPORT pqxx::zview + to_buf(char *begin, char *end, T const &value); -/// String traits for builtin floating-point types. -template struct float_traits -{ - static constexpr bool converts_to_string{true}; - static constexpr bool converts_from_string{true}; - static PQXX_LIBEXPORT T from_string(std::string_view text); - static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value); static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value); // Return a nonnegative integral value's number of decimal digits. @@ -199,46 +190,44 @@ struct nullness>> : no_null {}; -template<> struct string_traits : internal::integral_traits -{}; -template<> inline constexpr bool is_unquoted_safe{true}; -template<> -struct string_traits - : internal::integral_traits -{}; -template<> inline constexpr bool is_unquoted_safe{true}; -template<> struct string_traits : internal::integral_traits -{}; -template<> inline constexpr bool is_unquoted_safe{true}; -template<> struct string_traits : internal::integral_traits -{}; -template<> inline constexpr bool is_unquoted_safe{true}; -template<> struct string_traits : internal::integral_traits -{}; -template<> inline constexpr bool is_unquoted_safe{true}; -template<> -struct string_traits : internal::integral_traits -{}; -template<> inline constexpr bool is_unquoted_safe{true}; +/// String traits for builtin integer types. +/** This does not cover `bool` or (unlike `std::integral`) the `char` types. + */ +template struct string_traits +{ + static constexpr bool converts_to_string{true}; + static constexpr bool converts_from_string{true}; + static PQXX_LIBEXPORT T from_string(std::string_view text); + static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value); + static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value); + + static constexpr std::size_t size_buffer(T const &) noexcept + { + /** Includes a sign if needed; the number of base-10 digits which the type + * can reliably represent; the one extra base-10 digit which the type can + * only partially represent; and the terminating zero. + */ + return std::is_signed_v + std::numeric_limits::digits10 + 1 + 1; + } +}; + + +template +inline constexpr bool is_unquoted_safe{true}; +template +inline constexpr bool is_unquoted_safe{true}; + + template<> -struct string_traits : internal::integral_traits +struct string_traits : pqxx::internal::float_string_traits {}; -template<> inline constexpr bool is_unquoted_safe{true}; template<> -struct string_traits - : internal::integral_traits -{}; -template<> inline constexpr bool is_unquoted_safe{true}; -template<> struct string_traits : internal::float_traits +struct string_traits : pqxx::internal::float_string_traits {}; -template<> inline constexpr bool is_unquoted_safe{true}; -template<> struct string_traits : internal::float_traits -{}; -template<> inline constexpr bool is_unquoted_safe{true}; template<> -struct string_traits : internal::float_traits +struct string_traits + : pqxx::internal::float_string_traits {}; -template<> inline constexpr bool is_unquoted_safe{true}; template<> struct string_traits @@ -489,12 +478,21 @@ template<> struct nullness /// String traits for C-style string ("pointer to char const"). +/** This conversion is not bidirectional. You can convert a C-style string to + * an SQL string, but not the other way around. + * + * The reason for this is the terminating zero. The incoming SQL string is a + * `std::string_view`, which may or may not have a zero at the end. (And + * there's no reliable way of checking, since the next memory position may not + * be a valid address. Even if there happens to be a zero there, it isn't + * necessarily part of the same block of mmory.) + */ template<> struct string_traits { static constexpr bool converts_to_string{true}; - static constexpr bool converts_from_string{true}; + static constexpr bool converts_from_string{false}; - static char const *from_string(std::string_view text) { return text.data(); } + static char const *from_string(std::string_view text) =delete; static zview to_buf(char *begin, char *end, char const *const &value) { @@ -537,6 +535,15 @@ template<> struct nullness /// String traits for non-const C-style string ("pointer to char"). +/** This conversion is not bidirectional. You can convert a `char *` to an + * SQL string, but not vice versa. + * + * There are two reasons. One is the fact that an SQL string arrives in the + * form of a `std::string_view`; there is no guarantee of a trailing zero. + * + * The other reason is constness. We can't give you a non-const pointer into + * a string that was handed into the conversion as `const`. + */ template<> struct string_traits { static constexpr bool converts_to_string{true}; @@ -558,7 +565,6 @@ template<> struct string_traits return string_traits::size_buffer(value); } - /// Don't allow conversion to this type since it breaks const-safety. static char *from_string(std::string_view) = delete; }; @@ -590,6 +596,7 @@ template struct string_traits std::memcpy(begin, value, N); return begin + N; } + static constexpr std::size_t size_buffer(char const (&)[N]) noexcept { return N; @@ -646,10 +653,15 @@ template<> struct nullness : no_null /// String traits for `string_view`. +/** @warning This conversion does not store the string's contents anywhere. + * When you convert a string to a `std::string_view`, _do not access the + * resulting view after the original string has been destroyed. The contents + * will no longer be valid, even though tests may not make this obvious. + */ template<> struct string_traits { static constexpr bool converts_to_string{true}; - static constexpr bool converts_from_string{false}; + static constexpr bool converts_from_string{true}; static constexpr std::size_t size_buffer(std::string_view const &value) noexcept @@ -674,8 +686,7 @@ template<> struct string_traits return generic_to_buf(begin, end, value); } - /// Don't convert to this type; it has nowhere to store its contents. - static std::string_view from_string(std::string_view) = delete; + static std::string_view from_string(std::string_view value) { return value; } }; @@ -711,7 +722,11 @@ template<> struct string_traits return {begin, static_cast(stop - begin - 1)}; } - /// Don't convert to this type; it has nowhere to store its contents. + /// Don't convert to this type. There may not be a terminating zero. + /** There is no valid way to figure out here whether there is a terminating + * zero. Even if there is one, that may just be the first byte of an + * entirely separately allocated piece of memory. + */ static zview from_string(std::string_view) = delete; }; @@ -893,20 +908,10 @@ inline constexpr bool is_unquoted_safe>{ is_unquoted_safe}; -template<> struct nullness : no_null -{}; - - template struct nullness : no_null {}; -template inline constexpr format param_format(DATA const &) -{ - return format::binary; -} - - template struct string_traits { static constexpr bool converts_to_string{true}; @@ -937,90 +942,11 @@ template struct string_traits auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; bytes buf; buf.resize(size); + // XXX: Use std::as_writable_bytes. pqxx::internal::unesc_bin(text, reinterpret_cast(buf.data())); return buf; } }; - - -template<> struct string_traits -{ - static constexpr bool converts_to_string{true}; - static constexpr bool converts_from_string{true}; - - static std::size_t size_buffer(bytes const &value) noexcept - { - return internal::size_esc_bin(std::size(value)); - } - - static zview to_buf(char *begin, char *end, bytes const &value) - { - return generic_to_buf(begin, end, value); - } - - static char *into_buf(char *begin, char *end, bytes const &value) - { - auto const budget{size_buffer(value)}; - if (std::cmp_less(end - begin, budget)) - throw conversion_overrun{ - "Not enough buffer space to escape binary data."}; - internal::esc_bin(value, begin); - return begin + budget; - } - - static bytes from_string(std::string_view text) - { - auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; - bytes buf; - buf.resize(size); - pqxx::internal::unesc_bin(text, reinterpret_cast(buf.data())); - return buf; - } -}; - - -template<> inline constexpr format param_format(bytes const &) -{ - return format::binary; -} - - -template<> struct nullness : no_null -{}; - - -template<> struct string_traits -{ - static constexpr bool converts_to_string{true}; - static constexpr bool converts_from_string{false}; - - static std::size_t size_buffer(bytes_view const &value) noexcept - { - return internal::size_esc_bin(std::size(value)); - } - - static zview to_buf(char *begin, char *end, bytes_view const &value) - { - return generic_to_buf(begin, end, value); - } - - static char *into_buf(char *begin, char *end, bytes_view const &value) - { - auto const budget{size_buffer(value)}; - if (std::cmp_less(end - begin, budget)) - throw conversion_overrun{ - "Not enough buffer space to escape binary data."}; - internal::esc_bin(value, begin); - return begin + budget; - } - - // There's no from_string, because there's nobody to hold the data. -}; - -template<> inline constexpr format param_format(bytes_view const &) -{ - return format::binary; -} } // namespace pqxx @@ -1147,99 +1073,32 @@ public: namespace pqxx { -template -struct nullness> : no_null> -{}; - - -template -struct string_traits> - : internal::array_string_traits> -{}; - - -/// We don't know how to pass array params in binary format, so pass as text. -template -inline constexpr format param_format(std::vector const &) -{ - return format::text; -} - - -/// A `std::vector` is a binary string. Other vectors are not. -template -inline constexpr format param_format(std::vector const &) -{ - return format::binary; -} - - -template inline constexpr bool is_sql_array>{true}; - - -template -struct nullness> : no_null> -{}; - - -template -struct string_traits> - : internal::array_string_traits> -{}; - - -template -inline constexpr format param_format(std::span const &) -{ - return format::text; -} - - -template -inline constexpr format param_format(std::span const &) -{ - return format::binary; -} - - -template -inline constexpr bool is_sql_array>{true}; - - -template -struct nullness> : no_null> +template struct nullness : no_null {}; -template -struct string_traits> - : internal::array_string_traits> +template +struct string_traits : internal::array_string_traits {}; /// We don't know how to pass array params in binary format, so pass as text. -template -inline constexpr format param_format(std::array const &) +template inline constexpr format param_format(T const &) { return format::text; } -/// An array of `std::byte` is a binary string. -template -inline constexpr format param_format(std::array const &) +/// A contiguous range of `std::byte` is a binary string; other ranges are not. +template inline constexpr format param_format(T const &) { return format::binary; } -template -inline constexpr bool is_sql_array>{true}; -} // namespace pqxx +template inline constexpr bool is_sql_array{true}; -namespace pqxx -{ template inline std::string to_string(T const &value) { if (is_null(value)) @@ -1261,15 +1120,15 @@ template inline std::string to_string(T const &value) template<> inline std::string to_string(float const &value) { - return internal::to_string_float(value); + return pqxx::internal::to_string_float(value); } template<> inline std::string to_string(double const &value) { - return internal::to_string_float(value); + return pqxx::internal::to_string_float(value); } template<> inline std::string to_string(long double const &value) { - return internal::to_string_float(value); + return pqxx::internal::to_string_float(value); } template<> inline std::string to_string(std::stringstream const &value) { diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index 9b684be0c..f945ff4dd 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -106,8 +106,10 @@ public: /// Append a non-null binary parameter. void append(bytes &&) &; + /// Append all parameters in `value`. void append(params const &value) &; + /// Append all parameters in `value`. void append(params &&value) &; /// Append a non-null parameter, converting it to its string diff --git a/include/pqxx/strconv.hxx b/include/pqxx/strconv.hxx index 590b58c59..cab0387bc 100644 --- a/include/pqxx/strconv.hxx +++ b/include/pqxx/strconv.hxx @@ -198,6 +198,11 @@ template struct string_traits * for a value of this type. * * @warning A null value has no string representation. Do not parse a null. + * + * @warning If you convert a string to `std::string_view`, you're basically + * just getting a pointer into the original buffer. So, the `string_view` + * will become invalid when the original string's lifetime ends, or gets + * overwritten. Do not access the `string_view` you got after that! */ [[nodiscard]] static inline TYPE from_string(std::string_view text); @@ -590,19 +595,6 @@ inline zview generic_to_buf(char *begin, char *end, TYPE const &value) else return {begin, traits::into_buf(begin, end, value) - begin - 1}; } - - -/// 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 -concept binary = - std::ranges::contiguous_range and - std::is_same_v>, std::byte>; //@} } // namespace pqxx diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index 28185fb36..eba1909e3 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -102,6 +102,30 @@ concept potential_binary = std::ranges::contiguous_range and (sizeof(value_type) == 1); +/// 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 +concept binary = + std::ranges::contiguous_range and + std::same_as< + std::remove_cvref_t>, std::byte>; + + +/// A series of something that's not bytes. +template +concept nonbinary_range = + std::ranges::range and + not std::same_as< + std::remove_cvref_t>, std::byte> and + not std::same_as< + std::remove_cvref_t>, char>; + + /// Marker for @ref stream_from constructors: "stream from table." /** @deprecated Use @ref stream_from::table() instead. */ @@ -113,6 +137,22 @@ struct from_table_t */ struct from_query_t {}; - } // namespace pqxx + + +namespace pqxx::internal +{ +/// Concept: one of the "char" types. +template +concept char_type = std::same_as, char> or + std::same_as, signed char> or + std::same_as, unsigned char>; + + +/// Concept: an integral number type. +/** Unlike `std::integral`, this does not include the `char` types. + */ +template +concept integer = std::integral and not char_type; +} // namespace pqxx::internal #endif diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index 7cb17e62e..669b47104 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -334,7 +334,7 @@ using bytes_view = std::conditional< * @warning You must keep the storage holding the actual data alive for as * long as you might use this function's return value. */ -template bytes_view binary_cast(TYPE const &data) +template inline bytes_view binary_cast(TYPE const &data) { static_assert(sizeof(value_type) == 1); // C++20: Use std::as_bytes. @@ -453,7 +453,7 @@ inline constexpr std::size_t size_unesc_bin(std::size_t escaped_bytes) noexcept } -// TODO: Use actual binary type for "data". +// XXX: Maybe pass a span so we can check length? /// Hex-escape binary data into a buffer. /** The buffer must be able to accommodate * `size_esc_bin(std::size(binary_data))` bytes, and the function will write @@ -463,6 +463,18 @@ inline constexpr std::size_t size_unesc_bin(std::size_t escaped_bytes) noexcept void PQXX_LIBEXPORT esc_bin(bytes_view binary_data, char buffer[]) noexcept; +/// Hex-escape binary data into a buffer. +/** The buffer must be able to accommodate + * `size_esc_bin(std::size(binary_data))` bytes, and the function will write + * exactly that number of bytes into the buffer. This includes a trailing + * zero. + */ +template inline void esc_bin(T &&binary_data, char buffer[]) noexcept +{ + esc_bin(binary_cast(binary_data), buffer); +} + + /// Hex-escape binary data into a std::string. std::string PQXX_LIBEXPORT esc_bin(bytes_view binary_data); diff --git a/src/strconv.cxx b/src/strconv.cxx index e19509311..c3293b6bf 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -144,11 +144,13 @@ inline char *wrap_to_chars(char *begin, char *end, T const &value) } // namespace -namespace pqxx::internal +namespace pqxx { -template -// NOLINTNEXTLINE(readability-non-const-parameter) -zview integral_traits::to_buf(char *begin, char *end, T const &value) +template +inline + // NOLINTNEXTLINE(readability-non-const-parameter) + zview + string_traits::to_buf(char *begin, char *end, T const &value) { static_assert(std::is_integral_v); auto const space{end - begin}, @@ -174,23 +176,8 @@ zview integral_traits::to_buf(char *begin, char *end, T const &value) } -template zview integral_traits::to_buf(char *, char *, short const &); -template zview integral_traits::to_buf( - char *, char *, unsigned short const &); -template zview integral_traits::to_buf(char *, char *, int const &); -template zview -integral_traits::to_buf(char *, char *, unsigned const &); -template zview integral_traits::to_buf(char *, char *, long const &); -template zview -integral_traits::to_buf(char *, char *, unsigned long const &); -template zview -integral_traits::to_buf(char *, char *, long long const &); -template zview integral_traits::to_buf( - char *, char *, unsigned long long const &); - - -template -char *integral_traits::into_buf(char *begin, char *end, T const &value) +template +inline char *string_traits::into_buf(char *begin, char *end, T const &value) { // This is exactly what to_chars is good at. Trust standard library // implementers to optimise better than we can. @@ -198,20 +185,15 @@ char *integral_traits::into_buf(char *begin, char *end, T const &value) } -template char *integral_traits::into_buf(char *, char *, short const &); -template char *integral_traits::into_buf( - char *, char *, unsigned short const &); -template char *integral_traits::into_buf(char *, char *, int const &); -template char * -integral_traits::into_buf(char *, char *, unsigned const &); -template char *integral_traits::into_buf(char *, char *, long const &); -template char *integral_traits::into_buf( - char *, char *, unsigned long const &); -template char * -integral_traits::into_buf(char *, char *, long long const &); -template char *integral_traits::into_buf( - char *, char *, unsigned long long const &); -} // namespace pqxx::internal +template struct string_traits; +template struct string_traits; +template struct string_traits; +template struct string_traits; +template struct string_traits; +template struct string_traits; +template struct string_traits; +template struct string_traits; +} // namespace pqxx namespace pqxx::internal @@ -321,12 +303,9 @@ template inline TYPE from_string_arithmetic(std::string_view in) else throw pqxx::conversion_error{base + ": " + msg}; } -} // namespace #if !defined(PQXX_HAVE_CHARCONV_FLOAT) -namespace -{ constexpr bool valid_infinity_string(std::string_view text) noexcept { return text == "inf" or text == "infinity" or text == "INFINITY" or @@ -430,69 +409,9 @@ inline T PQXX_COLD from_string_awful_float(std::string_view text) return result; } -} // namespace #endif // !PQXX_HAVE_CHARCONV_FLOAT -namespace pqxx::internal -{ -/// Floating-point to_buf implemented in terms of to_string. -template -zview float_traits::to_buf(char *begin, char *end, T const &value) -{ -#if defined(PQXX_HAVE_CHARCONV_FLOAT) - { - // Definitely prefer to let the standard library handle this! - auto const ptr{wrap_to_chars(begin, end, value)}; - return zview{begin, std::size_t(ptr - begin - 1)}; - } -#else - { - // Implement it ourselves. Weird detail: since this workaround is based on - // std::stringstream, which produces a std::string, it's actually easier to - // build the to_buf() on top of the to_string() than the other way around. - if (std::isnan(value)) - return "nan"_zv; - if (std::isinf(value)) - return (value > 0) ? "infinity"_zv : "-infinity"_zv; - auto text{to_string_float(value)}; - auto have{end - begin}; - auto need{std::size(text) + 1}; - if (need > std::size_t(have)) - throw conversion_error{ - "Could not convert floating-point number to string: " - "buffer too small. " + - state_buffer_overrun(have, need)}; - text.copy(begin, need); - return zview{begin, std::size(text)}; - } -#endif -} - - -template zview float_traits::to_buf(char *, char *, float const &); -template zview float_traits::to_buf(char *, char *, double const &); -template zview -float_traits::to_buf(char *, char *, long double const &); - - -template -char *float_traits::into_buf(char *begin, char *end, T const &value) -{ -#if defined(PQXX_HAVE_CHARCONV_FLOAT) - return wrap_to_chars(begin, end, value); -#else - return generic_into_buf(begin, end, value); -#endif -} - - -template char *float_traits::into_buf(char *, char *, float const &); -template char *float_traits::into_buf(char *, char *, double const &); -template char * -float_traits::into_buf(char *, char *, long double const &); - - #if !defined(PQXX_HAVE_CHARCONV_FLOAT) template inline std::string PQXX_COLD @@ -503,18 +422,21 @@ to_dumb_stringstream(dumb_stringstream &s, F value) return s.str(); } #endif +} // namespace +namespace pqxx::internal +{ /// Floating-point implementations for @c pqxx::to_string(). template std::string to_string_float(T value) { #if defined(PQXX_HAVE_CHARCONV_FLOAT) { - static constexpr auto space{float_traits::size_buffer(value)}; + static constexpr auto space{string_traits::size_buffer(value)}; std::string buf; buf.resize(space); std::string_view const view{ - float_traits::to_buf(std::data(buf), std::data(buf) + space, value)}; + string_traits::to_buf(std::data(buf), std::data(buf) + space, value)}; buf.resize(static_cast(std::end(view) - std::begin(view))); return buf; } @@ -536,46 +458,94 @@ template std::string to_string_float(T value) } #endif } -} // namespace pqxx::internal -namespace pqxx::internal -{ -template -T integral_traits::from_string(std::string_view text) +template +T float_string_traits::from_string(std::string_view text) { +#if defined(PQXX_HAVE_CHARCONV_FLOAT) return from_string_arithmetic(text); +#else + return from_string_awful_float(text); +#endif } -template short integral_traits::from_string(std::string_view); -template unsigned short - integral_traits::from_string(std::string_view); -template int integral_traits::from_string(std::string_view); -template unsigned integral_traits::from_string(std::string_view); -template long integral_traits::from_string(std::string_view); -template unsigned long - integral_traits::from_string(std::string_view); -template long long integral_traits::from_string(std::string_view); -template unsigned long long - integral_traits::from_string(std::string_view); + +template +zview float_string_traits::to_buf(char *begin, char *end, T const &value) +{ +#if defined(PQXX_HAVE_CHARCONV_FLOAT) + { + // Definitely prefer to let the standard library handle this! + auto const ptr{wrap_to_chars(begin, end, value)}; + return zview{begin, std::size_t(ptr - begin - 1)}; + } +#else + { + // Implement it ourselves. Weird detail: since this workaround is based + // on std::stringstream, which produces a std::string, it's actually + // easier to build the to_buf() on top of the to_string() than the other + // way around. + if (std::isnan(value)) + return "nan"_zv; + if (std::isinf(value)) + return (value > 0) ? "infinity"_zv : "-infinity"_zv; + auto text{to_string_float(value)}; + auto have{end - begin}; + auto need{std::size(text) + 1}; + if (need > std::size_t(have)) + throw conversion_error{ + "Could not convert floating-point number to string: " + "buffer too small. " + + state_buffer_overrun(have, need)}; + text.copy(begin, need); + return zview{begin, std::size(text)}; + } +#endif +} template -T float_traits::from_string(std::string_view text) +char *float_string_traits::into_buf(char *begin, char *end, T const &value) { #if defined(PQXX_HAVE_CHARCONV_FLOAT) - return from_string_arithmetic(text); + return wrap_to_chars(begin, end, value); #else - return from_string_awful_float(text); + return generic_into_buf(begin, end, value); #endif } -template float float_traits::from_string(std::string_view); -template double float_traits::from_string(std::string_view); -template long double float_traits::from_string(std::string_view); +template struct float_string_traits; +template struct float_string_traits; +template struct float_string_traits; +} // namespace pqxx::internal + + +namespace pqxx +{ +template +T string_traits::from_string(std::string_view text) +{ + return from_string_arithmetic(text); +} + +template short string_traits::from_string(std::string_view); +template unsigned short + string_traits::from_string(std::string_view); +template int string_traits::from_string(std::string_view); +template unsigned string_traits::from_string(std::string_view); +template long string_traits::from_string(std::string_view); +template unsigned long + string_traits::from_string(std::string_view); +template long long string_traits::from_string(std::string_view); +template unsigned long long + string_traits::from_string(std::string_view); +} // namespace pqxx +namespace pqxx::internal +{ template std::string to_string_float(float); template std::string to_string_float(double); template std::string to_string_float(long double); diff --git a/test/unit/test_string_conversion.cxx b/test/unit/test_string_conversion.cxx index 55486c666..44e4761c8 100644 --- a/test/unit/test_string_conversion.cxx +++ b/test/unit/test_string_conversion.cxx @@ -206,9 +206,27 @@ void test_string_view_conversion() } +void test_binary_converts_to_string() +{ + PQXX_CHECK_EQUAL( + pqxx::to_string(std::array{ + std::byte{0x41}, std::byte{0x42}, std::byte{0x43}}), + "\\x414243", "Bad conversino from std::array to string."); + + std::array x{std::byte{0x78}}; + PQXX_CHECK_EQUAL(std::size(x), 1u, "This vector is not what I thought."); + std::span span{x}; + PQXX_CHECK_EQUAL(std::size(span), 1u, "Strangely different span."); + PQXX_CHECK_EQUAL( + pqxx::to_string(span), "\\x78", + "Bad conversion from std::span to string."); +} + + PQXX_REGISTER_TEST(test_string_conversion); PQXX_REGISTER_TEST(test_convert_variant_to_string); PQXX_REGISTER_TEST(test_integer_conversion); PQXX_REGISTER_TEST(test_convert_null); PQXX_REGISTER_TEST(test_string_view_conversion); +PQXX_REGISTER_TEST(test_binary_converts_to_string); } // namespace From 34a5faa2bbf8547260ad0a1f2d44008aef9b07c0 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sun, 19 Jan 2025 15:33:59 +0100 Subject: [PATCH 56/64] Make `is_null()` check `always_null` itself. Not sure how useful this is, but it's definitely more intuitive. Theoretically we might be able to leave `nullness::is_null()` on such types unimplemented altogether, though that might make things a little too irregular. --- NEWS | 1 + config/Makefile.in | 2 +- include/pqxx/connection.hxx | 5 ++--- include/pqxx/internal/conversions.hxx | 29 ++++++++++++++++++--------- include/pqxx/strconv.hxx | 5 ++++- test/unit/test_errorhandler.cxx | 4 ++-- 6 files changed, 30 insertions(+), 16 deletions(-) diff --git a/NEWS b/NEWS index 4c3e0a601..ce6e780f5 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ - **Beware lifetime** when "converting" a string to `std::string_view`! - Conversion from string to `char const *` is no longer allowed. - Binary data can be any `std::contiguous_range` of `std::byte`. (#925) + - Generic `quote()` takes `always_null` into account. - Retired `binarystring` and its headers. Use `blob` instead. - Retired `connection_base` type alias. Use `connection`. - Retired `pqxx::encrypt_password()`. Use the ones in `pqxx::connection`. diff --git a/config/Makefile.in b/config/Makefile.in index 51586bda2..9b1454986 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -124,7 +124,7 @@ am__can_run_installinfo = \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \ - config.sub install-sh ltmain.sh missing mkinstalldirs + config.sub depcomp install-sh ltmain.sh missing mkinstalldirs DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index 27a77a673..ff5805cb0 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -1383,14 +1383,13 @@ private: template inline std::string connection::quote(T const &t) const { - if constexpr (nullness::always_null) + // TODO: Can we leave the quotes out if unquoted_safe? + if (is_null(t)) { return "NULL"; } else { - if (is_null(t)) - return "NULL"; auto const text{to_string(t)}; // Okay, there's an easy way to do this and there's a hard way. The easy diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index f03c32ef8..3e80f9d95 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -1106,15 +1106,26 @@ template inline std::string to_string(T const &value) "Attempt to convert null " + std::string{type_name} + " to a string."}; - std::string buf; - // We can't just reserve() space; modifying the terminating zero leads to - // undefined behaviour. - buf.resize(size_buffer(value)); - auto const data{buf.data()}; - auto const end{ - string_traits::into_buf(data, data + std::size(buf), value)}; - buf.resize(static_cast(end - data - 1)); - return buf; + if constexpr (nullness>::always_null) + { + // Have to separate out this case: some functions in the "regular" code + // may not exist in the "always null" case. + PQXX_UNREACHABLE; + // C++23: The return may not be needed when std::unreachable is available. + return {}; + } + else + { + std::string buf; + // We can't just reserve() space; modifying the terminating zero leads to + // undefined behaviour. + buf.resize(size_buffer(value)); + auto const data{buf.data()}; + auto const end{ + string_traits::into_buf(data, data + std::size(buf), value)}; + buf.resize(static_cast(end - data - 1)); + return buf; + } } diff --git a/include/pqxx/strconv.hxx b/include/pqxx/strconv.hxx index cab0387bc..e0226bfb7 100644 --- a/include/pqxx/strconv.hxx +++ b/include/pqxx/strconv.hxx @@ -515,7 +515,10 @@ inline void into_string(TYPE const &value, std::string &out); template [[nodiscard]] inline constexpr bool is_null(TYPE const &value) noexcept { - return nullness>::is_null(value); + using base_type = std::remove_cvref_t; + using null_traits = nullness; + if constexpr (null_traits::always_null) return true; + else return null_traits::is_null(value); } diff --git a/test/unit/test_errorhandler.cxx b/test/unit/test_errorhandler.cxx index b6cd5d70a..edfa4f19f 100644 --- a/test/unit/test_errorhandler.cxx +++ b/test/unit/test_errorhandler.cxx @@ -42,8 +42,8 @@ template<> struct nullness // clang warns about these being unused. And clang 6 won't accept a // [[maybe_unused]] attribute on them either! - // static inline constexpr bool has_null{true}; - // static inline constexpr bool always_null{false}; + [[maybe_unused]] static inline constexpr bool has_null{true}; + static inline constexpr bool always_null{false}; static constexpr bool is_null(TestErrorHandler *e) noexcept { From f18a864ce4c7e92ef6faf8fe304fd0899e1cfc61 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Tue, 21 Jan 2025 21:28:30 +0100 Subject: [PATCH 57/64] Move clang-tidy directive. --- src/strconv.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strconv.cxx b/src/strconv.cxx index c3293b6bf..7efbbcfd7 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -148,8 +148,8 @@ namespace pqxx { template inline - // NOLINTNEXTLINE(readability-non-const-parameter) zview + // NOLINTNEXTLINE(readability-non-const-parameter) string_traits::to_buf(char *begin, char *end, T const &value) { static_assert(std::is_integral_v); From 2307ee32e9d6241b28f88b17077369a3487bbf5c Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sat, 25 Jan 2025 23:01:46 +0100 Subject: [PATCH 58/64] Accept `pqxx::binary` concept in more places. (#937) Fixes: https://github.com/jtv/libpqxx/issues/925 Extends a bunch more functions that accept `pqxx::bytes_view` with variants which accept any type that satisfies the `pqxx::binary` concept. Also, change the `pqxx::bytes_view` type alias from being a `std::basic_string_view` (which doesn't actually have to work according to the standard!) to being a `std::span`. This seems to be broadly compatible with existing code. For completeness I'm adding a `pqxx::writable_bytes_view` as well. --- NEWS | 3 +- cmake/pqxx_cxx_feature_checks.cmake | 4 -- config-tests/PQXX_HAVE_PATH.cxx | 9 --- config-tests/no_need_fslib.cxx | 14 +---- config/Makefile.in | 2 +- configitems | 1 - configure | 80 ++------------------------- include/pqxx/blob.hxx | 71 +++++++++--------------- include/pqxx/config.h.in | 3 - include/pqxx/connection.hxx | 15 +++-- include/pqxx/doc/binary-data.md | 46 +++++---------- include/pqxx/internal/conversions.hxx | 2 +- include/pqxx/params.hxx | 2 + include/pqxx/strconv.hxx | 6 +- include/pqxx/stream_to.hxx | 3 +- include/pqxx/types.hxx | 32 +++++++---- include/pqxx/util.hxx | 46 ++++----------- include/pqxx/zview.hxx | 9 +++ pqxx_cxx_feature_checks.ac | 10 ---- requirements.json | 2 +- src/blob.cxx | 24 ++++---- src/connection.cxx | 6 -- test/Makefile.am | 1 + test/Makefile.in | 12 +++- test/runner.cxx | 6 +- test/unit/test_blob.cxx | 54 +++++++++++++++--- test/unit/test_escape.cxx | 8 +++ test/unit/test_util.cxx | 41 ++++++++++++++ test/unit/test_zview.cxx | 11 ++++ 29 files changed, 245 insertions(+), 278 deletions(-) delete mode 100644 config-tests/PQXX_HAVE_PATH.cxx create mode 100644 test/unit/test_util.cxx diff --git a/NEWS b/NEWS index ce6e780f5..aeb56dce7 100644 --- a/NEWS +++ b/NEWS @@ -17,7 +17,8 @@ - Assume compiler supports concepts. - Assume compiler supports integral conversions in `charconv`. - Assume compiler supports spans, ranges, and `cmp_less` etc. - - Assume compiler supports `std::remove_cvref_t`. + - Assume compiler supports `std::remove_cvref_t` etc. + - Assume compiler supports `std::filesystem::path`. - Assume compiler supports `[[likely]]` & `[[unlikely]]`. - Assume compiler supports `ssize()`. - Assume compiler supports ISO-646 without needing `` header. diff --git a/cmake/pqxx_cxx_feature_checks.cmake b/cmake/pqxx_cxx_feature_checks.cmake index b6fbe7e36..bb2f6253a 100644 --- a/cmake/pqxx_cxx_feature_checks.cmake +++ b/cmake/pqxx_cxx_feature_checks.cmake @@ -23,10 +23,6 @@ try_compile( PQXX_HAVE_MULTIDIM ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_MULTIDIM.cxx ) -try_compile( - PQXX_HAVE_PATH ${PROJECT_BINARY_DIR} - SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_PATH.cxx -) try_compile( PQXX_HAVE_POLL ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_POLL.cxx diff --git a/config-tests/PQXX_HAVE_PATH.cxx b/config-tests/PQXX_HAVE_PATH.cxx deleted file mode 100644 index d93d37f5d..000000000 --- a/config-tests/PQXX_HAVE_PATH.cxx +++ /dev/null @@ -1,9 +0,0 @@ -// Check for working std::filesystem support. -#include - - -int main() -{ - // Apparently some versions of MinGW lack this comparison operator. - return std::filesystem::path{} != std::filesystem::path{}; -} diff --git a/config-tests/no_need_fslib.cxx b/config-tests/no_need_fslib.cxx index 041c46b43..8f5401a90 100644 --- a/config-tests/no_need_fslib.cxx +++ b/config-tests/no_need_fslib.cxx @@ -1,21 +1,13 @@ // Check whether we need to link to the stdc++fs library. // -// We assume that the presence of the header means that we have -// support for the basics of std::filesystem. This check will succeed if -// either there is no header, or there is one and it works without -// any special options. If the link fails, we assume that -lstdc++fs will fix -// it for us. +// This check will succeed if std::filesystem::path works without any special +// options. If the link fails, we assume that -lstdc++fs will fix it for us. +#include #include -#if __has_include() -# include -#endif - int main() { -#if __has_include() std::cout << std::filesystem::path{"foo.bar"}.c_str() << '\n'; -#endif } diff --git a/config/Makefile.in b/config/Makefile.in index 9b1454986..51586bda2 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -124,7 +124,7 @@ am__can_run_installinfo = \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \ - config.sub depcomp install-sh ltmain.sh missing mkinstalldirs + config.sub install-sh ltmain.sh missing mkinstalldirs DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ diff --git a/configitems b/configitems index 8208cd679..58af65a35 100644 --- a/configitems +++ b/configitems @@ -10,7 +10,6 @@ PQXX_HAVE_CXA_DEMANGLE internal compiler PQXX_HAVE_GCC_PURE public compiler PQXX_HAVE_GCC_VISIBILITY public compiler PQXX_HAVE_MULTIDIM public compiler -PQXX_HAVE_PATH public compiler PQXX_HAVE_POLL internal compiler PQXX_HAVE_SLEEP_FOR internal compiler PQXX_HAVE_SOURCE_LOCATION public compiler diff --git a/configure b/configure index 2e4d1dd41..73cb691b5 100755 --- a/configure +++ b/configure @@ -17500,42 +17500,6 @@ fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_MULTIDIM" >&5 printf "%s\n" "$PQXX_HAVE_MULTIDIM" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_PATH" >&5 -printf %s "checking PQXX_HAVE_PATH... " >&6; } -PQXX_HAVE_PATH=yes -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -// Check for working std::filesystem support. - -#include - - - - - -int main() - -{ - - // Apparently some versions of MinGW lack this comparison operator. - - return std::filesystem::path{} != std::filesystem::path{}; - -} - - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO" -then : - -printf "%s\n" "#define PQXX_HAVE_PATH 1" >>confdefs.h - -else $as_nop - PQXX_HAVE_PATH=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_PATH" >&5 -printf "%s\n" "$PQXX_HAVE_PATH" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_POLL" >&5 printf %s "checking PQXX_HAVE_POLL... " >&6; } PQXX_HAVE_POLL=yes @@ -17875,42 +17839,26 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext // -// We assume that the presence of the header means that we have - -// support for the basics of std::filesystem. This check will succeed if +// This check will succeed if std::filesystem::path works without any special -// either there is no header, or there is one and it works without +// options. If the link fails, we assume that -lstdc++fs will fix it for us. -// any special options. If the link fails, we assume that -lstdc++fs will fix - -// it for us. +#include #include -#if __has_include() - -# include - -#endif - - - int main() { -#if __has_include() - std::cout << std::filesystem::path{"foo.bar"}.c_str() << '\n'; -#endif - } @@ -18397,42 +18345,26 @@ do // -// We assume that the presence of the header means that we have +// This check will succeed if std::filesystem::path works without any special -// support for the basics of std::filesystem. This check will succeed if +// options. If the link fails, we assume that -lstdc++fs will fix it for us. -// either there is no header, or there is one and it works without - -// any special options. If the link fails, we assume that -lstdc++fs will fix - -// it for us. +#include #include -#if __has_include() - -# include - -#endif - - - int main() { -#if __has_include() - std::cout << std::filesystem::path{"foo.bar"}.c_str() << '\n'; -#endif - } diff --git a/include/pqxx/blob.hxx b/include/pqxx/blob.hxx index 9621fd44e..dcd504934 100644 --- a/include/pqxx/blob.hxx +++ b/include/pqxx/blob.hxx @@ -19,10 +19,6 @@ #include -#if defined(PQXX_HAVE_PATH) -# include -#endif - #include #include @@ -88,6 +84,7 @@ public: */ static constexpr std::size_t chunk_limit = 0x7fffffff; + // XXX: Can we build a generic version of this? /// 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. @@ -106,7 +103,7 @@ public: * Returns the filled portion of `buf`. This may be empty. */ template - std::span read(std::span buf) + writable_bytes_view read(std::span buf) { return buf.subspan(0, raw_read(std::data(buf), std::size(buf))); } @@ -117,7 +114,7 @@ public: * * Returns the filled portion of `buf`. This may be empty. */ - template std::span read(DATA &buf) + template writable_bytes_view read(DATA &buf) { return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; } @@ -143,7 +140,7 @@ public: */ template void write(DATA const &data) { - raw_write(std::data(data), std::size(data)); + return raw_write(binary_cast(data)); } /// Resize large object to `size` bytes. @@ -180,53 +177,47 @@ public: */ static oid from_buf(dbtransaction &tx, bytes_view data, oid id = 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. + */ + template + static oid from_buf(dbtransaction &tx, DATA data, oid id = 0) + { + return from_buf(tx, binary_cast(data), id); + } + /// Append `data` to binary large object. /** The underlying protocol only supports appending blocks up to 2 GB. */ static void append_from_buf(dbtransaction &tx, bytes_view 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. + /// Append `data` to binary large object. + /** The underlying protocol only supports appending blocks up to 2 GB. */ - [[nodiscard]] static oid - from_file(dbtransaction &tx, std::filesystem::path const &path) + template + static void append_from_buf(dbtransaction &tx, DATA data, oid id) { - return from_file(tx, path.c_str()); + append_from_buf(tx, binary_cast(data), id); } -#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); + [[nodiscard]] static oid from_file(dbtransaction &, zview path); -#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 + static oid from_file(dbtransaction &, zview path, oid); + // XXX: Can we build a generic version of this? /// 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, bytes &, std::size_t max_size); + // XXX: Can we build a generic version of this? /// 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. @@ -239,19 +230,7 @@ public: 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 + static void to_file(dbtransaction &, oid, zview path); /// Close this blob. /** This does not delete the blob from the database; it only terminates your @@ -282,7 +261,7 @@ private: 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); + void raw_write(bytes_view); connection *m_conn = nullptr; int m_fd = -1; diff --git a/include/pqxx/config.h.in b/include/pqxx/config.h.in index b79862a6b..0e9834a5b 100644 --- a/include/pqxx/config.h.in +++ b/include/pqxx/config.h.in @@ -75,9 +75,6 @@ /* Define if this feature is available. */ #undef PQXX_HAVE_MULTIDIM -/* Define if this feature is available. */ -#undef PQXX_HAVE_PATH - /* Define if this feature is available. */ #undef PQXX_HAVE_POLL diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index ff5805cb0..bfd8a454b 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -939,6 +939,7 @@ public: } /// Escape and quote a string of binary data. + /** You can also just use @ref quote with binary data. */ std::string quote_raw(bytes_view) const; /// Escape and quote a string of binary data. @@ -992,10 +993,6 @@ public: template [[nodiscard]] inline std::string quote(T const &t) const; - // TODO: Make "into buffer" variant to eliminate a string allocation. - /// Escape and quote binary data for use as a BYTEA value in SQL statement. - [[nodiscard]] std::string quote(bytes_view bytes) const; - // TODO: Make "into buffer" variant to eliminate a string allocation. /// Escape string for literal LIKE match. /** Use this when part of an SQL "LIKE" pattern should match only as a @@ -1383,13 +1380,21 @@ private: template inline std::string connection::quote(T const &t) const { - // TODO: Can we leave the quotes out if unquoted_safe? if (is_null(t)) { + // It's easy to forget, but we can't support nulls in string conversion + // itself, because the "NULL" may end up inside quotes or something. + // We can only handle nulls at this slightly higher level in the call tree, + // where there is awareness of the quoting. return "NULL"; } + else if constexpr (binary) + { + return quote_raw(t); + } else { + // TODO: Can we leave the quotes out if unquoted_safe? auto const text{to_string(t)}; // Okay, there's an easy way to do this and there's a hard way. The easy diff --git a/include/pqxx/doc/binary-data.md b/include/pqxx/doc/binary-data.md index 57d37e5a1..e6d911dc4 100644 --- a/include/pqxx/doc/binary-data.md +++ b/include/pqxx/doc/binary-data.md @@ -9,51 +9,33 @@ 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 `pqxx::bytes` or `pqxx::bytes_view`, or anything else that's a block of -contiguous `std::byte` in memory. +some block of contiguous `std::byte` values in memory. That could be a +`std::vector`, or `std::span`, and so on. However the +_preferred_ types for binary data in libpqxx are... +* `pqxx::bytes` for storing the data, similar to `std::string` for text. +* `pqxx::bytes_view` for reading data stored elsewhere, similar to how you'd + use `std::string_view` for text. +* `pqxx::writable_bytes_view` for writing to data stored elsewhere. 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 -`pqxx::bytes_view`. - -Your particular binary data may look different though. You may have it in a -`std::string`, or a `std::vector`, 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. +`pqxx::blob` object. You might use that to write data which you pass in the +form of a `pqxx::bytes_view`. You might then read that data back by letting +`pqxx::blob` write the data into a `pqxx::bytes &` or a +`pqxx::writable_bytes_view` that you give it. So long as it's _basically_ still a block of bytes though, you can use -`pqxx::binary_cast` to construct a `pqxx::bytes_view` from it. - -There are two forms of `binary_cast`. One takes a single argument that must -support `std::data()` and `std::size()`: +`pqxx::binary_cast` to construct a `pqxx::bytes_view` from it: ```cxx std::string hi{"Hello binary world"}; my_blob.write(pqxx::binary_cast(hi); ``` -The other takes a pointer and a size: +For convenience there's also a form of `binary_cast` that takes a pointer and +a length. ```cxx 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. diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index 3e80f9d95..2a4ea8106 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -492,7 +492,7 @@ template<> struct string_traits static constexpr bool converts_to_string{true}; static constexpr bool converts_from_string{false}; - static char const *from_string(std::string_view text) =delete; + static char const *from_string(std::string_view text) = delete; static zview to_buf(char *begin, char *end, char const *const &value) { diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index f945ff4dd..52d3f8340 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -82,6 +82,8 @@ public: /// Append a non-null string parameter. void append(std::string &&) &; + // XXX: Rethink the view/copy situation. + // XXX: Generic pqxx::binary support. /// Append a non-null binary parameter. /** The underlying data must stay valid for as long as the `params` * remains active. diff --git a/include/pqxx/strconv.hxx b/include/pqxx/strconv.hxx index e0226bfb7..c6586430a 100644 --- a/include/pqxx/strconv.hxx +++ b/include/pqxx/strconv.hxx @@ -517,8 +517,10 @@ template { using base_type = std::remove_cvref_t; using null_traits = nullness; - if constexpr (null_traits::always_null) return true; - else return null_traits::is_null(value); + if constexpr (null_traits::always_null) + return true; + else + return null_traits::is_null(value); } diff --git a/include/pqxx/stream_to.hxx b/include/pqxx/stream_to.hxx index 37e86c27f..ff9df1043 100644 --- a/include/pqxx/stream_to.hxx +++ b/include/pqxx/stream_to.hxx @@ -390,7 +390,8 @@ private: /// Write raw COPY line into @c m_buffer, based on a container of fields. template - std::enable_if_t> + std::enable_if_t< + not std::is_same_v, char>> fill_buffer(Container const &c) { // To avoid unnecessary allocations and deallocations, we run through c diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index eba1909e3..523693db9 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -81,25 +81,29 @@ template using strip_t = std::remove_cvref_t; * which we may or may not end up using for this. */ template -using value_type = - std::remove_cvref_t()))>; +using value_type = std::remove_cvref_t>; + + +/// A type one byte in size. +template +concept char_sized = (sizeof(CHAR) == 1); /// Concept: Any type that we can read as a string of `char`. template -concept char_string = - std::ranges::contiguous_range and - std::same_as>, char>; +concept char_string = std::ranges::contiguous_range and + std::same_as>, char>; /// Concept: Anything we can iterate to get things we can read as strings. template concept char_strings = std::ranges::range and - char_string>>; + char_string>>; /// Concept: Anything we might want to treat as binary data. template concept potential_binary = - std::ranges::contiguous_range and (sizeof(value_type) == 1); + std::ranges::contiguous_range and char_sized> and + not std::is_reference_v>; /// Concept: Binary string, akin to @c std::string for binary data. @@ -110,10 +114,8 @@ concept potential_binary = * we can reference them by a pointer. */ template -concept binary = - std::ranges::contiguous_range and - std::same_as< - std::remove_cvref_t>, std::byte>; +concept binary = std::ranges::contiguous_range and + std::same_as>, std::byte>; /// A series of something that's not bytes. @@ -126,6 +128,14 @@ concept nonbinary_range = std::remove_cvref_t>, char>; +/// Type alias for a view of bytes. +using bytes_view = std::span; + + +/// Type alias for a view of writable bytes. +using writable_bytes_view = std::span; + + /// Marker for @ref stream_from constructors: "stream from table." /** @deprecated Use @ref stream_from::table() instead. */ diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index 669b47104..64aedfa13 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -296,7 +296,7 @@ inline constexpr bool has_generic_bytes_char_traits = // Necessary for libc++ 18. #include "pqxx/internal/ignore-deprecated-pre.hxx" -// C++20: Change this type. +// XXX: Replace this type! /// Type alias for a container containing bytes. /* Required to support standard libraries without a generic implementation for * `std::char_traits`. @@ -306,27 +306,14 @@ using bytes = std::conditional< has_generic_bytes_char_traits, std::basic_string, std::basic_string>::type; -// C++20: Change this type. -/// Type alias for a view of bytes. -/* Required to support standard libraries without a generic implementation for - * `std::char_traits`. - * @warn Will change to `std::span` in the next major release. - */ -using bytes_view = std::conditional< - has_generic_bytes_char_traits, std::basic_string_view, - std::basic_string_view>::type; - #include "pqxx/internal/ignore-deprecated-post.hxx" -/// Cast binary data to a type that libpqxx will recognise as binary. -/** There are many different formats for storing binary data in memory. You - * may have yours as a `std::string`, or a `std::vector`, or one of - * many other types. In libpqxx we commend a container of `std::byte`. - * - * For libpqxx to recognise your data as binary, we recommend using a - * `pqxx::bytes`, or a `pqxx::bytes_view`; but any contiguous block of - * `std::byte` should do. +/// Cast binary data to libpqxx's standard "view on binary data." +/** There are many different formats for passing a reference to binary data in + * memory. There's `std::string_view`, or a `std::vector`, or one of + * many other types. In libpqxx we recommend `std::span`, but + * thanks to this conversion functions, most of these types should work. * * Use `binary_cast` as a convenience helper to cast your data as a * `pqxx::bytes_view`. @@ -336,21 +323,13 @@ using bytes_view = std::conditional< */ template inline bytes_view binary_cast(TYPE const &data) { - static_assert(sizeof(value_type) == 1); - // C++20: Use std::as_bytes. - return { - reinterpret_cast( - const_cast const *>( - std::data(data))), - std::size(data)}; + using item_t = value_type; + return std::as_bytes( + std::span{std::data(data), std::size(data)}); } -/// A type one byte in size. -template -concept char_sized = (sizeof(CHAR) == 1); - - +// XXX: Write separate tests for binary_cast. /// Construct a type that libpqxx will recognise as binary. /** Takes a data pointer and a size, without being too strict about their * types, and constructs a `pqxx::bytes_view` pointing to the same data. @@ -361,10 +340,7 @@ concept char_sized = (sizeof(CHAR) == 1); template bytes_view binary_cast(CHAR const *data, SIZE size) { - static_assert(sizeof(CHAR) == 1); - return { - reinterpret_cast(data), - check_cast(size, "binary data size")}; + return binary_cast(std::span{data, check_cast(size)}); } diff --git a/include/pqxx/zview.hxx b/include/pqxx/zview.hxx index 94083f686..fdda8ac39 100644 --- a/include/pqxx/zview.hxx +++ b/include/pqxx/zview.hxx @@ -11,6 +11,7 @@ #ifndef PQXX_H_ZVIEW #define PQXX_H_ZVIEW +#include #include #include #include @@ -52,6 +53,9 @@ public: {} /// Explicitly promote a `string_view` to a `zview`. + /** @warning This is not just a type conversion. It's the caller making a + * promise that the string is zero-terminated. + */ explicit constexpr zview(std::string_view other) noexcept : std::string_view{other} {} @@ -91,6 +95,11 @@ public: constexpr zview(char const (&literal)[size]) : zview(literal, size - 1) {} +#if !defined(WIN32) + /// Construct a `zview` from a `std::filesystem::path`. + zview(std::filesystem::path p) : zview(p.c_str()) {} +#endif // WIN32 + /// Either a null pointer, or a zero-terminated text buffer. [[nodiscard]] constexpr char const *c_str() const & noexcept { diff --git a/pqxx_cxx_feature_checks.ac b/pqxx_cxx_feature_checks.ac index 4ffe172a3..9f9f74d8e 100644 --- a/pqxx_cxx_feature_checks.ac +++ b/pqxx_cxx_feature_checks.ac @@ -59,16 +59,6 @@ AC_COMPILE_IFELSE( [Define if this feature is available.]), PQXX_HAVE_MULTIDIM=no) AC_MSG_RESULT($PQXX_HAVE_MULTIDIM) -AC_MSG_CHECKING([PQXX_HAVE_PATH]) -PQXX_HAVE_PATH=yes -AC_COMPILE_IFELSE( - [read_test(PQXX_HAVE_PATH.cxx)], - AC_DEFINE( - [PQXX_HAVE_PATH], - 1, - [Define if this feature is available.]), - PQXX_HAVE_PATH=no) -AC_MSG_RESULT($PQXX_HAVE_PATH) AC_MSG_CHECKING([PQXX_HAVE_POLL]) PQXX_HAVE_POLL=yes AC_COMPILE_IFELSE( diff --git a/requirements.json b/requirements.json index 0e2d0531c..0062d6044 100644 --- a/requirements.json +++ b/requirements.json @@ -5,5 +5,5 @@ "postgresql": "12.0", "gcc": "9", "clang": "12", - "msvc": "2019" + "msvc": "2022" } diff --git a/src/blob.cxx b/src/blob.cxx index a7d0f5b3e..2f6edaea2 100644 --- a/src/blob.cxx +++ b/src/blob.cxx @@ -162,15 +162,15 @@ std::size_t pqxx::blob::read(bytes &buf, std::size_t size) } -void pqxx::blob::raw_write(std::byte const buf[], std::size_t size) +void pqxx::blob::raw_write(bytes_view data) { if (m_conn == nullptr) throw usage_error{"Attempt to write to a closed binary large object."}; - if (size > chunk_limit) - throw range_error{ - "Writes to a binary large object must be less than 2 GB at once."}; - auto ptr{reinterpret_cast(buf)}; - int const written{lo_write(raw_conn(m_conn), m_fd, ptr, size)}; + auto const sz{std::size(data)}; + if (sz > chunk_limit) + throw range_error{"Write to binary large object exceeds 2GB limit."}; + auto ptr{reinterpret_cast(std::data(data))}; + int const written{lo_write(raw_conn(m_conn), m_fd, ptr, sz)}; if (written < 0) throw failure{ internal::concat("Write to binary large object failed: ", errmsg())}; @@ -305,9 +305,9 @@ std::size_t pqxx::blob::append_to_buf( } -pqxx::oid pqxx::blob::from_file(dbtransaction &tx, char const path[]) +pqxx::oid pqxx::blob::from_file(dbtransaction &tx, zview path) { - auto id{lo_import(raw_conn(tx), path)}; + auto id{lo_import(raw_conn(tx), path.c_str())}; if (id == 0) throw failure{internal::concat( "Could not import '", path, "' as a binary large object: ", errmsg(tx))}; @@ -315,9 +315,9 @@ pqxx::oid pqxx::blob::from_file(dbtransaction &tx, char const path[]) } -pqxx::oid pqxx::blob::from_file(dbtransaction &tx, char const path[], oid id) +pqxx::oid pqxx::blob::from_file(dbtransaction &tx, zview path, oid id) { - auto actual_id{lo_import_with_oid(raw_conn(tx), path, id)}; + auto actual_id{lo_import_with_oid(raw_conn(tx), path.c_str(), id)}; if (actual_id == 0) throw failure{internal::concat( "Could not import '", path, "' as binary large object ", id, ": ", @@ -326,9 +326,9 @@ pqxx::oid pqxx::blob::from_file(dbtransaction &tx, char const path[], oid id) } -void pqxx::blob::to_file(dbtransaction &tx, oid id, char const path[]) +void pqxx::blob::to_file(dbtransaction &tx, oid id, zview path) { - if (lo_export(raw_conn(tx), id, path) < 0) + if (lo_export(raw_conn(tx), id, path.c_str()) < 0) throw failure{internal::concat( "Could not export binary large object ", id, " to file '", path, "': ", errmsg(tx))}; diff --git a/src/connection.cxx b/src/connection.cxx index 18db0a470..8a7c5cbd9 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -979,12 +979,6 @@ std::string pqxx::connection::quote_raw(bytes_view bytes) const } -std::string pqxx::connection::quote(bytes_view b) const -{ - return internal::concat("'", esc_raw(b), "'::bytea"); -} - - std::string pqxx::connection::quote_name(std::string_view identifier) const { std::unique_ptr const buf{ diff --git a/test/Makefile.am b/test/Makefile.am index 67280a457..15e85b282 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -116,6 +116,7 @@ runner_SOURCES = \ unit/test_transaction_focus.cxx \ unit/test_transactor.cxx \ unit/test_type_name.cxx \ + unit/test_util.cxx \ unit/test_zview.cxx \ runner.cxx diff --git a/test/Makefile.in b/test/Makefile.in index 9a3c5521d..ce7be9f4c 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -166,7 +166,8 @@ am_runner_OBJECTS = test00.$(OBJEXT) test01.$(OBJEXT) test02.$(OBJEXT) \ unit/test_transaction_base.$(OBJEXT) \ unit/test_transaction_focus.$(OBJEXT) \ unit/test_transactor.$(OBJEXT) unit/test_type_name.$(OBJEXT) \ - unit/test_zview.$(OBJEXT) runner.$(OBJEXT) + unit/test_util.$(OBJEXT) unit/test_zview.$(OBJEXT) \ + runner.$(OBJEXT) runner_OBJECTS = $(am_runner_OBJECTS) runner_DEPENDENCIES = $(top_builddir)/src/libpqxx.la AM_V_lt = $(am__v_lt_@AM_V@) @@ -242,7 +243,8 @@ am__depfiles_remade = ./$(DEPDIR)/runner.Po ./$(DEPDIR)/test00.Po \ unit/$(DEPDIR)/test_transaction_base.Po \ unit/$(DEPDIR)/test_transaction_focus.Po \ unit/$(DEPDIR)/test_transactor.Po \ - unit/$(DEPDIR)/test_type_name.Po unit/$(DEPDIR)/test_zview.Po + unit/$(DEPDIR)/test_type_name.Po unit/$(DEPDIR)/test_util.Po \ + unit/$(DEPDIR)/test_zview.Po am__mv = mv -f CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) @@ -541,6 +543,7 @@ runner_SOURCES = \ unit/test_transaction_focus.cxx \ unit/test_transactor.cxx \ unit/test_type_name.cxx \ + unit/test_util.cxx \ unit/test_zview.cxx \ runner.cxx @@ -677,6 +680,8 @@ unit/test_transactor.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test_type_name.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) +unit/test_util.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) unit/test_zview.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) @@ -771,6 +776,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_transaction_focus.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_transactor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_type_name.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_zview.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @@ -1112,6 +1118,7 @@ distclean: distclean-am -rm -f unit/$(DEPDIR)/test_transaction_focus.Po -rm -f unit/$(DEPDIR)/test_transactor.Po -rm -f unit/$(DEPDIR)/test_type_name.Po + -rm -f unit/$(DEPDIR)/test_util.Po -rm -f unit/$(DEPDIR)/test_zview.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ @@ -1238,6 +1245,7 @@ maintainer-clean: maintainer-clean-am -rm -f unit/$(DEPDIR)/test_transaction_focus.Po -rm -f unit/$(DEPDIR)/test_transactor.Po -rm -f unit/$(DEPDIR)/test_type_name.Po + -rm -f unit/$(DEPDIR)/test_util.Po -rm -f unit/$(DEPDIR)/test_zview.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic diff --git a/test/runner.cxx b/test/runner.cxx index 14145f9d2..377271038 100644 --- a/test/runner.cxx +++ b/test/runner.cxx @@ -172,7 +172,8 @@ int main(int argc, char const *argv[]) { // TODO: Accept multiple names. std::string_view test_name; - if (argc > 1) test_name = argv[1]; + if (argc > 1) + test_name = argv[1]; auto const num_tests{std::size(*all_test_names)}; std::map all_tests; @@ -200,8 +201,7 @@ int main(int argc, char const *argv[]) catch (pqxx::test::test_failure const &e) { std::cerr << "Test failure in " << e.file() << " line " - << pqxx::to_string(e.line()) << ": " << e.what() - << '\n'; + << pqxx::to_string(e.line()) << ": " << e.what() << '\n'; } catch (std::bad_alloc const &) { diff --git a/test/unit/test_blob.cxx b/test/unit/test_blob.cxx index fb95767e4..b7c540be1 100644 --- a/test/unit/test_blob.cxx +++ b/test/unit/test_blob.cxx @@ -1,4 +1,5 @@ #include +#include #include #include @@ -174,6 +175,32 @@ void test_blob_read_reads_data() } +void test_blob_read_reads_generic_data() +{ + std::array const data{ + std::byte{'a'}, std::byte{'b'}, std::byte{'c'}}; + + pqxx::connection cx; + pqxx::work tx{cx}; + pqxx::oid id{pqxx::blob::from_buf(tx, data)}; + + pqxx::bytes buf; + auto b{pqxx::blob::open_rw(tx, id)}; + PQXX_CHECK_EQUAL( + b.read(buf, 2), 2u, "Full read() returned an unexpected value."); + PQXX_CHECK_EQUAL( + buf, (pqxx::bytes{std::byte{'a'}, std::byte{'b'}}), + "Read back the wrong data."); + PQXX_CHECK_EQUAL( + b.read(buf, 2), 1u, "Partial read() returned an unexpected value."); + PQXX_CHECK_EQUAL( + buf, (pqxx::bytes{std::byte{'c'}}), "Continued read produced wrong data."); + PQXX_CHECK_EQUAL( + b.read(buf, 2), 0u, "read at end returned an unexpected value."); + PQXX_CHECK_EQUAL(buf, (pqxx::bytes{}), "Read past end produced data."); +} + + /// Cast a `char` or `std::byte` to `unsigned int`. template inline unsigned byte_val(BYTE val) { @@ -412,6 +439,22 @@ void test_blob_append_from_buf_appends() } +void test_blob_generic_append_from_buf_appends() +{ + std::array const data{std::byte{'h'}, std::byte{'o'}}; + pqxx::connection cx; + pqxx::work tx{cx}; + auto id{pqxx::blob::create(tx)}; + pqxx::blob::append_from_buf(tx, data, id); + pqxx::blob::append_from_buf(tx, data, id); + pqxx::bytes buf; + pqxx::blob::to_buf(tx, id, buf, 10); + PQXX_CHECK_EQUAL( + std::size(buf), 2 * std::size(data), + "Generic append_from_buf() created unexpected length."); +} + + namespace { /// Wrap `std::fopen`. @@ -587,10 +630,7 @@ void test_blob_close_leaves_blob_unusable() void test_blob_accepts_std_filesystem_path() { -#if defined(PQXX_HAVE_PATH) && !defined(_WIN32) - // A bug in gcc 8's ~std::filesystem::path() causes a run-time crash. -# if !defined(__GNUC__) || (__GNUC__ > 8) - +#if !defined(_WIN32) char const temp_file[] = "blob-test-filesystem-path.tmp"; pqxx::bytes const data{std::byte{'4'}, std::byte{'2'}}; @@ -603,9 +643,7 @@ void test_blob_accepts_std_filesystem_path() auto id{pqxx::blob::from_file(tx, path)}; pqxx::blob::to_buf(tx, id, buf, 10); PQXX_CHECK_EQUAL(buf, data, "Wrong data from blob::from_file()."); - -# endif -#endif +#endif // WIN32 } @@ -619,6 +657,7 @@ PQXX_REGISTER_TEST(test_blob_remove_is_not_idempotent); PQXX_REGISTER_TEST(test_blob_checks_open_mode); PQXX_REGISTER_TEST(test_blob_supports_move); PQXX_REGISTER_TEST(test_blob_read_reads_data); +PQXX_REGISTER_TEST(test_blob_read_reads_generic_data); PQXX_REGISTER_TEST(test_blob_reads_vector); PQXX_REGISTER_TEST(test_blob_read_span); PQXX_REGISTER_TEST(test_blob_write_appends_at_insertion_point); @@ -629,6 +668,7 @@ PQXX_REGISTER_TEST(test_blob_tell_tracks_position); PQXX_REGISTER_TEST(test_blob_seek_sets_positions); PQXX_REGISTER_TEST(test_blob_from_buf_interoperates_with_to_buf); PQXX_REGISTER_TEST(test_blob_append_from_buf_appends); +PQXX_REGISTER_TEST(test_blob_generic_append_from_buf_appends); PQXX_REGISTER_TEST(test_blob_from_file_creates_blob_from_file_contents); PQXX_REGISTER_TEST(test_blob_from_file_with_oid_writes_blob); PQXX_REGISTER_TEST(test_blob_append_to_buf_appends); diff --git a/test/unit/test_escape.cxx b/test/unit/test_escape.cxx index 1966f48f3..51546eb5f 100644 --- a/test/unit/test_escape.cxx +++ b/test/unit/test_escape.cxx @@ -61,6 +61,7 @@ void test_quote(pqxx::connection &cx, pqxx::transaction_base &t) PQXX_CHECK_EQUAL(t.quote(0), "'0'", "Quoting zero is a problem."); char const *const null_ptr{nullptr}; PQXX_CHECK_EQUAL(t.quote(null_ptr), "NULL", "Not quoting NULL correctly."); + PQXX_CHECK_EQUAL(t.quote(nullptr), "NULL", "Not quoting nullptr correctly."); PQXX_CHECK_EQUAL( t.quote(std::string{"'"}), "''''", "Escaping quotes goes wrong."); @@ -77,6 +78,13 @@ void test_quote(pqxx::connection &cx, pqxx::transaction_base &t) PQXX_CHECK_EQUAL( r, test_strings[i], "Selecting quoted string does not come back equal."); } + + std::vector const bin{std::byte{0x33}, std::byte{0x4a}}; + PQXX_CHECK_EQUAL( + t.quote(bin), "'\\x334a'::bytea", "Unexpected binary quoting output."); + PQXX_CHECK_EQUAL( + t.quote(std::span{bin}), "'\\x334a'::bytea", + "Binary span quotes differently from vector."); } diff --git a/test/unit/test_util.cxx b/test/unit/test_util.cxx new file mode 100644 index 000000000..01c447635 --- /dev/null +++ b/test/unit/test_util.cxx @@ -0,0 +1,41 @@ +#include + +#include +#include + +#include "../test_helpers.hxx" + +namespace +{ +template void test_for(T const &val) +{ + auto const name{pqxx::type_name}; + auto const sz{std::size(val)}; + + std::span out{pqxx::binary_cast(val)}; + + PQXX_CHECK_EQUAL( + std::size(out), sz, "Got bad size on binary_cast<" + name + "()."); + + for (std::size_t i{0}; i < sz; ++i) + PQXX_CHECK_EQUAL( + static_cast(out[i]), static_cast(val[i]), + "Mismatch in " + name + " byte " + pqxx::to_string(i) + "."); +} + + +void test_binary_cast() +{ + std::byte const bytes_c_array[]{ + std::byte{0x22}, std::byte{0x23}, std::byte{0x24}}; + test_for(bytes_c_array); + test_for("Hello world"); + + test_for(std::vector{'n', 'o', 'p', 'q'}); + test_for(std::vector{'n', 'o', 'p', 'q'}); + test_for(std::vector{'n', 'o', 'p', 'q'}); +} + + +PQXX_REGISTER_TEST(test_binary_cast); +} // namespace diff --git a/test/unit/test_zview.cxx b/test/unit/test_zview.cxx index 913b29e91..a853da6aa 100644 --- a/test/unit/test_zview.cxx +++ b/test/unit/test_zview.cxx @@ -1,3 +1,5 @@ +#include + #include #include "../test_helpers.hxx" @@ -5,6 +7,14 @@ namespace { +void test_zview_is_a_range() +{ + static_assert(std::ranges::range); + static_assert(std::ranges::borrowed_range); + static_assert(std::ranges::contiguous_range); +} + + void test_zview_literal() { using pqxx::operator"" _zv; @@ -40,6 +50,7 @@ void test_zview_converts_to_string() } +PQXX_REGISTER_TEST(test_zview_is_a_range); PQXX_REGISTER_TEST(test_zview_literal); PQXX_REGISTER_TEST(test_zview_converts_to_string); } // namespace From b1f69da795603c43bcafaeb4c4324a59449f85c8 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sat, 25 Jan 2025 23:55:57 +0100 Subject: [PATCH 59/64] #934: Unify test directories (#939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There really wasn't much point to having "unit" tests and story tests ("legacy" tests, really) in separate directories. Sweep them all together into a single directory — it's simpler, and possibly faster on some build systems. --- src/strconv.cxx | 7 +- test/CMakeLists.txt | 2 +- test/Makefile.am | 88 +-- test/Makefile.am.template | 3 - test/Makefile.in | 572 +++++++----------- test/{unit => }/test_array.cxx | 2 +- test/{unit => }/test_blob.cxx | 4 +- test/{unit => }/test_cancel_query.cxx | 2 +- test/{unit => }/test_column.cxx | 2 +- test/{unit => }/test_composite.cxx | 2 +- test/{unit => }/test_connection.cxx | 2 +- test/{unit => }/test_cursor.cxx | 2 +- test/{unit => }/test_encodings.cxx | 2 +- test/{unit => }/test_error_verbosity.cxx | 2 +- test/{unit => }/test_errorhandler.cxx | 2 +- test/{unit => }/test_escape.cxx | 2 +- test/{unit => }/test_exceptions.cxx | 2 +- test/{unit => }/test_field.cxx | 2 +- test/{unit => }/test_float.cxx | 2 +- test/{unit => }/test_largeobject.cxx | 2 +- test/{unit => }/test_nonblocking_connect.cxx | 2 +- test/{unit => }/test_notice_handler.cxx | 2 +- test/{unit => }/test_notification.cxx | 2 +- test/{unit => }/test_pipeline.cxx | 2 +- test/{unit => }/test_prepared_statement.cxx | 2 +- test/{unit => }/test_range.cxx | 2 +- test/{unit => }/test_read_transaction.cxx | 2 +- test/{unit => }/test_result_iteration.cxx | 2 +- test/{unit => }/test_row.cxx | 2 +- test/{unit => }/test_separated_list.cxx | 2 +- .../test_simultaneous_transactions.cxx | 2 +- test/{unit => }/test_sql_cursor.cxx | 2 +- test/{unit => }/test_stateless_cursor.cxx | 2 +- test/{unit => }/test_strconv.cxx | 2 +- test/{unit => }/test_stream_from.cxx | 4 +- test/{unit => }/test_stream_query.cxx | 4 +- test/{unit => }/test_stream_to.cxx | 4 +- test/{unit => }/test_string_conversion.cxx | 2 +- test/{unit => }/test_subtransaction.cxx | 2 +- test/{unit => }/test_test_helpers.cxx | 2 +- test/{unit => }/test_thread_safety_model.cxx | 2 +- test/{unit => }/test_time.cxx | 2 +- test/{unit => }/test_transaction.cxx | 2 +- test/{unit => }/test_transaction_base.cxx | 2 +- test/{unit => }/test_transaction_focus.cxx | 2 +- test/{unit => }/test_transactor.cxx | 2 +- test/{unit => }/test_type_name.cxx | 2 +- test/{unit => }/test_util.cxx | 2 +- test/{unit => }/test_zview.cxx | 2 +- test/unit/CMakeLists.txt | 14 - 50 files changed, 325 insertions(+), 457 deletions(-) rename test/{unit => }/test_array.cxx (99%) rename test/{unit => }/test_blob.cxx (99%) rename test/{unit => }/test_cancel_query.cxx (94%) rename test/{unit => }/test_column.cxx (98%) rename test/{unit => }/test_composite.cxx (98%) rename test/{unit => }/test_connection.cxx (99%) rename test/{unit => }/test_cursor.cxx (97%) rename test/{unit => }/test_encodings.cxx (99%) rename test/{unit => }/test_error_verbosity.cxx (96%) rename test/{unit => }/test_errorhandler.cxx (99%) rename test/{unit => }/test_escape.cxx (99%) rename test/{unit => }/test_exceptions.cxx (96%) rename test/{unit => }/test_field.cxx (98%) rename test/{unit => }/test_float.cxx (99%) rename test/{unit => }/test_largeobject.cxx (98%) rename test/{unit => }/test_nonblocking_connect.cxx (94%) rename test/{unit => }/test_notice_handler.cxx (99%) rename test/{unit => }/test_notification.cxx (99%) rename test/{unit => }/test_pipeline.cxx (98%) rename test/{unit => }/test_prepared_statement.cxx (99%) rename test/{unit => }/test_range.cxx (99%) rename test/{unit => }/test_read_transaction.cxx (93%) rename test/{unit => }/test_result_iteration.cxx (99%) rename test/{unit => }/test_row.cxx (99%) rename test/{unit => }/test_separated_list.cxx (96%) rename test/{unit => }/test_simultaneous_transactions.cxx (92%) rename test/{unit => }/test_sql_cursor.cxx (99%) rename test/{unit => }/test_stateless_cursor.cxx (99%) rename test/{unit => }/test_strconv.cxx (99%) rename test/{unit => }/test_stream_from.cxx (99%) rename test/{unit => }/test_stream_query.cxx (99%) rename test/{unit => }/test_stream_to.cxx (99%) rename test/{unit => }/test_string_conversion.cxx (99%) rename test/{unit => }/test_subtransaction.cxx (98%) rename test/{unit => }/test_test_helpers.cxx (99%) rename test/{unit => }/test_thread_safety_model.cxx (93%) rename test/{unit => }/test_time.cxx (98%) rename test/{unit => }/test_transaction.cxx (98%) rename test/{unit => }/test_transaction_base.cxx (99%) rename test/{unit => }/test_transaction_focus.cxx (97%) rename test/{unit => }/test_transactor.cxx (99%) rename test/{unit => }/test_type_name.cxx (94%) rename test/{unit => }/test_util.cxx (96%) rename test/{unit => }/test_zview.cxx (97%) delete mode 100644 test/unit/CMakeLists.txt diff --git a/src/strconv.cxx b/src/strconv.cxx index 7efbbcfd7..d12eb3a32 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -147,10 +147,9 @@ inline char *wrap_to_chars(char *begin, char *end, T const &value) namespace pqxx { template -inline - zview - // NOLINTNEXTLINE(readability-non-const-parameter) - string_traits::to_buf(char *begin, char *end, T const &value) +inline zview +// NOLINTNEXTLINE(readability-non-const-parameter) +string_traits::to_buf(char *begin, char *end, T const &value) { static_assert(std::is_integral_v); auto const space{end - begin}, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 331631145..562ea3e84 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,7 +4,7 @@ if(NOT PostgreSQL_FOUND) find_package(PostgreSQL REQUIRED) endif() -file(GLOB TEST_SOURCES test*.cxx unit/test_*.cxx runner.cxx) +file(GLOB TEST_SOURCES *.cxx) add_executable(runner ${TEST_SOURCES}) target_link_libraries(runner PUBLIC pqxx) diff --git a/test/Makefile.am b/test/Makefile.am index 15e85b282..ce30386b0 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -74,50 +74,50 @@ runner_SOURCES = \ test88.cxx \ test89.cxx \ test90.cxx \ - unit/test_array.cxx \ - unit/test_blob.cxx \ - unit/test_cancel_query.cxx \ - unit/test_column.cxx \ - unit/test_composite.cxx \ - unit/test_connection.cxx \ - unit/test_cursor.cxx \ - unit/test_encodings.cxx \ - unit/test_error_verbosity.cxx \ - unit/test_errorhandler.cxx \ - unit/test_escape.cxx \ - unit/test_exceptions.cxx \ - unit/test_field.cxx \ - unit/test_float.cxx \ - unit/test_largeobject.cxx \ - unit/test_nonblocking_connect.cxx \ - unit/test_notice_handler.cxx \ - unit/test_notification.cxx \ - unit/test_pipeline.cxx \ - unit/test_prepared_statement.cxx \ - unit/test_range.cxx \ - unit/test_read_transaction.cxx \ - unit/test_result_iteration.cxx \ - unit/test_row.cxx \ - unit/test_separated_list.cxx \ - unit/test_simultaneous_transactions.cxx \ - unit/test_sql_cursor.cxx \ - unit/test_stateless_cursor.cxx \ - unit/test_strconv.cxx \ - unit/test_stream_from.cxx \ - unit/test_stream_query.cxx \ - unit/test_stream_to.cxx \ - unit/test_string_conversion.cxx \ - unit/test_subtransaction.cxx \ - unit/test_test_helpers.cxx \ - unit/test_thread_safety_model.cxx \ - unit/test_time.cxx \ - unit/test_transaction.cxx \ - unit/test_transaction_base.cxx \ - unit/test_transaction_focus.cxx \ - unit/test_transactor.cxx \ - unit/test_type_name.cxx \ - unit/test_util.cxx \ - unit/test_zview.cxx \ + test_array.cxx \ + test_blob.cxx \ + test_cancel_query.cxx \ + test_column.cxx \ + test_composite.cxx \ + test_connection.cxx \ + test_cursor.cxx \ + test_encodings.cxx \ + test_error_verbosity.cxx \ + test_errorhandler.cxx \ + test_escape.cxx \ + test_exceptions.cxx \ + test_field.cxx \ + test_float.cxx \ + test_largeobject.cxx \ + test_nonblocking_connect.cxx \ + test_notice_handler.cxx \ + test_notification.cxx \ + test_pipeline.cxx \ + test_prepared_statement.cxx \ + test_range.cxx \ + test_read_transaction.cxx \ + test_result_iteration.cxx \ + test_row.cxx \ + test_separated_list.cxx \ + test_simultaneous_transactions.cxx \ + test_sql_cursor.cxx \ + test_stateless_cursor.cxx \ + test_strconv.cxx \ + test_stream_from.cxx \ + test_stream_query.cxx \ + test_stream_to.cxx \ + test_string_conversion.cxx \ + test_subtransaction.cxx \ + test_test_helpers.cxx \ + test_thread_safety_model.cxx \ + test_time.cxx \ + test_transaction.cxx \ + test_transaction_base.cxx \ + test_transaction_focus.cxx \ + test_transactor.cxx \ + test_type_name.cxx \ + test_util.cxx \ + test_zview.cxx \ runner.cxx runner_LDADD = $(top_builddir)/src/libpqxx.la ${POSTGRES_LIB} diff --git a/test/Makefile.am.template b/test/Makefile.am.template index 2a21f6ef7..18028c6bd 100644 --- a/test/Makefile.am.template +++ b/test/Makefile.am.template @@ -26,9 +26,6 @@ MAINTAINERCLEANFILES=Makefile.in runner_SOURCES = \ ###MAKTEMPLATE:FOREACH test/test*.cxx ###BASENAME###.cxx \ -###MAKTEMPLATE:ENDFOREACH -###MAKTEMPLATE:FOREACH test/unit/test_*.cxx - unit/###BASENAME###.cxx \ ###MAKTEMPLATE:ENDFOREACH runner.cxx diff --git a/test/Makefile.in b/test/Makefile.in index ce7be9f4c..43355ebde 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -124,7 +124,6 @@ CONFIG_HEADER = $(top_builddir)/include/pqxx/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = runner$(EXEEXT) -am__dirstamp = $(am__leading_dot)dirstamp am_runner_OBJECTS = test00.$(OBJEXT) test01.$(OBJEXT) test02.$(OBJEXT) \ test07.$(OBJEXT) test10.$(OBJEXT) test11.$(OBJEXT) \ test13.$(OBJEXT) test14.$(OBJEXT) test16.$(OBJEXT) \ @@ -137,37 +136,30 @@ am_runner_OBJECTS = test00.$(OBJEXT) test01.$(OBJEXT) test02.$(OBJEXT) \ test72.$(OBJEXT) test74.$(OBJEXT) test75.$(OBJEXT) \ test76.$(OBJEXT) test77.$(OBJEXT) test82.$(OBJEXT) \ test84.$(OBJEXT) test88.$(OBJEXT) test89.$(OBJEXT) \ - test90.$(OBJEXT) unit/test_array.$(OBJEXT) \ - unit/test_blob.$(OBJEXT) unit/test_cancel_query.$(OBJEXT) \ - unit/test_column.$(OBJEXT) unit/test_composite.$(OBJEXT) \ - unit/test_connection.$(OBJEXT) unit/test_cursor.$(OBJEXT) \ - unit/test_encodings.$(OBJEXT) \ - unit/test_error_verbosity.$(OBJEXT) \ - unit/test_errorhandler.$(OBJEXT) unit/test_escape.$(OBJEXT) \ - unit/test_exceptions.$(OBJEXT) unit/test_field.$(OBJEXT) \ - unit/test_float.$(OBJEXT) unit/test_largeobject.$(OBJEXT) \ - unit/test_nonblocking_connect.$(OBJEXT) \ - unit/test_notice_handler.$(OBJEXT) \ - unit/test_notification.$(OBJEXT) unit/test_pipeline.$(OBJEXT) \ - unit/test_prepared_statement.$(OBJEXT) \ - unit/test_range.$(OBJEXT) unit/test_read_transaction.$(OBJEXT) \ - unit/test_result_iteration.$(OBJEXT) unit/test_row.$(OBJEXT) \ - unit/test_separated_list.$(OBJEXT) \ - unit/test_simultaneous_transactions.$(OBJEXT) \ - unit/test_sql_cursor.$(OBJEXT) \ - unit/test_stateless_cursor.$(OBJEXT) \ - unit/test_strconv.$(OBJEXT) unit/test_stream_from.$(OBJEXT) \ - unit/test_stream_query.$(OBJEXT) unit/test_stream_to.$(OBJEXT) \ - unit/test_string_conversion.$(OBJEXT) \ - unit/test_subtransaction.$(OBJEXT) \ - unit/test_test_helpers.$(OBJEXT) \ - unit/test_thread_safety_model.$(OBJEXT) \ - unit/test_time.$(OBJEXT) unit/test_transaction.$(OBJEXT) \ - unit/test_transaction_base.$(OBJEXT) \ - unit/test_transaction_focus.$(OBJEXT) \ - unit/test_transactor.$(OBJEXT) unit/test_type_name.$(OBJEXT) \ - unit/test_util.$(OBJEXT) unit/test_zview.$(OBJEXT) \ - runner.$(OBJEXT) + test90.$(OBJEXT) test_array.$(OBJEXT) test_blob.$(OBJEXT) \ + test_cancel_query.$(OBJEXT) test_column.$(OBJEXT) \ + test_composite.$(OBJEXT) test_connection.$(OBJEXT) \ + test_cursor.$(OBJEXT) test_encodings.$(OBJEXT) \ + test_error_verbosity.$(OBJEXT) test_errorhandler.$(OBJEXT) \ + test_escape.$(OBJEXT) test_exceptions.$(OBJEXT) \ + test_field.$(OBJEXT) test_float.$(OBJEXT) \ + test_largeobject.$(OBJEXT) test_nonblocking_connect.$(OBJEXT) \ + test_notice_handler.$(OBJEXT) test_notification.$(OBJEXT) \ + test_pipeline.$(OBJEXT) test_prepared_statement.$(OBJEXT) \ + test_range.$(OBJEXT) test_read_transaction.$(OBJEXT) \ + test_result_iteration.$(OBJEXT) test_row.$(OBJEXT) \ + test_separated_list.$(OBJEXT) \ + test_simultaneous_transactions.$(OBJEXT) \ + test_sql_cursor.$(OBJEXT) test_stateless_cursor.$(OBJEXT) \ + test_strconv.$(OBJEXT) test_stream_from.$(OBJEXT) \ + test_stream_query.$(OBJEXT) test_stream_to.$(OBJEXT) \ + test_string_conversion.$(OBJEXT) test_subtransaction.$(OBJEXT) \ + test_test_helpers.$(OBJEXT) test_thread_safety_model.$(OBJEXT) \ + test_time.$(OBJEXT) test_transaction.$(OBJEXT) \ + test_transaction_base.$(OBJEXT) \ + test_transaction_focus.$(OBJEXT) test_transactor.$(OBJEXT) \ + test_type_name.$(OBJEXT) test_util.$(OBJEXT) \ + test_zview.$(OBJEXT) runner.$(OBJEXT) runner_OBJECTS = $(am_runner_OBJECTS) runner_DEPENDENCIES = $(top_builddir)/src/libpqxx.la AM_V_lt = $(am__v_lt_@AM_V@) @@ -207,44 +199,35 @@ am__depfiles_remade = ./$(DEPDIR)/runner.Po ./$(DEPDIR)/test00.Po \ ./$(DEPDIR)/test77.Po ./$(DEPDIR)/test82.Po \ ./$(DEPDIR)/test84.Po ./$(DEPDIR)/test88.Po \ ./$(DEPDIR)/test89.Po ./$(DEPDIR)/test90.Po \ - unit/$(DEPDIR)/test_array.Po unit/$(DEPDIR)/test_blob.Po \ - unit/$(DEPDIR)/test_cancel_query.Po \ - unit/$(DEPDIR)/test_column.Po unit/$(DEPDIR)/test_composite.Po \ - unit/$(DEPDIR)/test_connection.Po \ - unit/$(DEPDIR)/test_cursor.Po unit/$(DEPDIR)/test_encodings.Po \ - unit/$(DEPDIR)/test_error_verbosity.Po \ - unit/$(DEPDIR)/test_errorhandler.Po \ - unit/$(DEPDIR)/test_escape.Po \ - unit/$(DEPDIR)/test_exceptions.Po unit/$(DEPDIR)/test_field.Po \ - unit/$(DEPDIR)/test_float.Po \ - unit/$(DEPDIR)/test_largeobject.Po \ - unit/$(DEPDIR)/test_nonblocking_connect.Po \ - unit/$(DEPDIR)/test_notice_handler.Po \ - unit/$(DEPDIR)/test_notification.Po \ - unit/$(DEPDIR)/test_pipeline.Po \ - unit/$(DEPDIR)/test_prepared_statement.Po \ - unit/$(DEPDIR)/test_range.Po \ - unit/$(DEPDIR)/test_read_transaction.Po \ - unit/$(DEPDIR)/test_result_iteration.Po \ - unit/$(DEPDIR)/test_row.Po \ - unit/$(DEPDIR)/test_separated_list.Po \ - unit/$(DEPDIR)/test_simultaneous_transactions.Po \ - unit/$(DEPDIR)/test_sql_cursor.Po \ - unit/$(DEPDIR)/test_stateless_cursor.Po \ - unit/$(DEPDIR)/test_strconv.Po \ - unit/$(DEPDIR)/test_stream_from.Po \ - unit/$(DEPDIR)/test_stream_query.Po \ - unit/$(DEPDIR)/test_stream_to.Po \ - unit/$(DEPDIR)/test_string_conversion.Po \ - unit/$(DEPDIR)/test_subtransaction.Po \ - unit/$(DEPDIR)/test_test_helpers.Po \ - unit/$(DEPDIR)/test_thread_safety_model.Po \ - unit/$(DEPDIR)/test_time.Po unit/$(DEPDIR)/test_transaction.Po \ - unit/$(DEPDIR)/test_transaction_base.Po \ - unit/$(DEPDIR)/test_transaction_focus.Po \ - unit/$(DEPDIR)/test_transactor.Po \ - unit/$(DEPDIR)/test_type_name.Po unit/$(DEPDIR)/test_util.Po \ - unit/$(DEPDIR)/test_zview.Po + ./$(DEPDIR)/test_array.Po ./$(DEPDIR)/test_blob.Po \ + ./$(DEPDIR)/test_cancel_query.Po ./$(DEPDIR)/test_column.Po \ + ./$(DEPDIR)/test_composite.Po ./$(DEPDIR)/test_connection.Po \ + ./$(DEPDIR)/test_cursor.Po ./$(DEPDIR)/test_encodings.Po \ + ./$(DEPDIR)/test_error_verbosity.Po \ + ./$(DEPDIR)/test_errorhandler.Po ./$(DEPDIR)/test_escape.Po \ + ./$(DEPDIR)/test_exceptions.Po ./$(DEPDIR)/test_field.Po \ + ./$(DEPDIR)/test_float.Po ./$(DEPDIR)/test_largeobject.Po \ + ./$(DEPDIR)/test_nonblocking_connect.Po \ + ./$(DEPDIR)/test_notice_handler.Po \ + ./$(DEPDIR)/test_notification.Po ./$(DEPDIR)/test_pipeline.Po \ + ./$(DEPDIR)/test_prepared_statement.Po \ + ./$(DEPDIR)/test_range.Po ./$(DEPDIR)/test_read_transaction.Po \ + ./$(DEPDIR)/test_result_iteration.Po ./$(DEPDIR)/test_row.Po \ + ./$(DEPDIR)/test_separated_list.Po \ + ./$(DEPDIR)/test_simultaneous_transactions.Po \ + ./$(DEPDIR)/test_sql_cursor.Po \ + ./$(DEPDIR)/test_stateless_cursor.Po \ + ./$(DEPDIR)/test_strconv.Po ./$(DEPDIR)/test_stream_from.Po \ + ./$(DEPDIR)/test_stream_query.Po ./$(DEPDIR)/test_stream_to.Po \ + ./$(DEPDIR)/test_string_conversion.Po \ + ./$(DEPDIR)/test_subtransaction.Po \ + ./$(DEPDIR)/test_test_helpers.Po \ + ./$(DEPDIR)/test_thread_safety_model.Po \ + ./$(DEPDIR)/test_time.Po ./$(DEPDIR)/test_transaction.Po \ + ./$(DEPDIR)/test_transaction_base.Po \ + ./$(DEPDIR)/test_transaction_focus.Po \ + ./$(DEPDIR)/test_transactor.Po ./$(DEPDIR)/test_type_name.Po \ + ./$(DEPDIR)/test_util.Po ./$(DEPDIR)/test_zview.Po am__mv = mv -f CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) @@ -501,50 +484,50 @@ runner_SOURCES = \ test88.cxx \ test89.cxx \ test90.cxx \ - unit/test_array.cxx \ - unit/test_blob.cxx \ - unit/test_cancel_query.cxx \ - unit/test_column.cxx \ - unit/test_composite.cxx \ - unit/test_connection.cxx \ - unit/test_cursor.cxx \ - unit/test_encodings.cxx \ - unit/test_error_verbosity.cxx \ - unit/test_errorhandler.cxx \ - unit/test_escape.cxx \ - unit/test_exceptions.cxx \ - unit/test_field.cxx \ - unit/test_float.cxx \ - unit/test_largeobject.cxx \ - unit/test_nonblocking_connect.cxx \ - unit/test_notice_handler.cxx \ - unit/test_notification.cxx \ - unit/test_pipeline.cxx \ - unit/test_prepared_statement.cxx \ - unit/test_range.cxx \ - unit/test_read_transaction.cxx \ - unit/test_result_iteration.cxx \ - unit/test_row.cxx \ - unit/test_separated_list.cxx \ - unit/test_simultaneous_transactions.cxx \ - unit/test_sql_cursor.cxx \ - unit/test_stateless_cursor.cxx \ - unit/test_strconv.cxx \ - unit/test_stream_from.cxx \ - unit/test_stream_query.cxx \ - unit/test_stream_to.cxx \ - unit/test_string_conversion.cxx \ - unit/test_subtransaction.cxx \ - unit/test_test_helpers.cxx \ - unit/test_thread_safety_model.cxx \ - unit/test_time.cxx \ - unit/test_transaction.cxx \ - unit/test_transaction_base.cxx \ - unit/test_transaction_focus.cxx \ - unit/test_transactor.cxx \ - unit/test_type_name.cxx \ - unit/test_util.cxx \ - unit/test_zview.cxx \ + test_array.cxx \ + test_blob.cxx \ + test_cancel_query.cxx \ + test_column.cxx \ + test_composite.cxx \ + test_connection.cxx \ + test_cursor.cxx \ + test_encodings.cxx \ + test_error_verbosity.cxx \ + test_errorhandler.cxx \ + test_escape.cxx \ + test_exceptions.cxx \ + test_field.cxx \ + test_float.cxx \ + test_largeobject.cxx \ + test_nonblocking_connect.cxx \ + test_notice_handler.cxx \ + test_notification.cxx \ + test_pipeline.cxx \ + test_prepared_statement.cxx \ + test_range.cxx \ + test_read_transaction.cxx \ + test_result_iteration.cxx \ + test_row.cxx \ + test_separated_list.cxx \ + test_simultaneous_transactions.cxx \ + test_sql_cursor.cxx \ + test_stateless_cursor.cxx \ + test_strconv.cxx \ + test_stream_from.cxx \ + test_stream_query.cxx \ + test_stream_to.cxx \ + test_string_conversion.cxx \ + test_subtransaction.cxx \ + test_test_helpers.cxx \ + test_thread_safety_model.cxx \ + test_time.cxx \ + test_transaction.cxx \ + test_transaction_base.cxx \ + test_transaction_focus.cxx \ + test_transactor.cxx \ + test_type_name.cxx \ + test_util.cxx \ + test_zview.cxx \ runner.cxx runner_LDADD = $(top_builddir)/src/libpqxx.la ${POSTGRES_LIB} @@ -590,100 +573,6 @@ clean-checkPROGRAMS: list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list -unit/$(am__dirstamp): - @$(MKDIR_P) unit - @: > unit/$(am__dirstamp) -unit/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) unit/$(DEPDIR) - @: > unit/$(DEPDIR)/$(am__dirstamp) -unit/test_array.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_blob.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_cancel_query.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_column.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_composite.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_connection.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_cursor.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_encodings.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_error_verbosity.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_errorhandler.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_escape.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_exceptions.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_field.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_float.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_largeobject.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_nonblocking_connect.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_notice_handler.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_notification.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_pipeline.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_prepared_statement.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_range.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_read_transaction.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_result_iteration.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_row.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_separated_list.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_simultaneous_transactions.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_sql_cursor.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_stateless_cursor.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_strconv.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_stream_from.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_stream_query.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_stream_to.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_string_conversion.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_subtransaction.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_test_helpers.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_thread_safety_model.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_time.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_transaction.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_transaction_base.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_transaction_focus.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_transactor.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_type_name.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_util.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) -unit/test_zview.$(OBJEXT): unit/$(am__dirstamp) \ - unit/$(DEPDIR)/$(am__dirstamp) runner$(EXEEXT): $(runner_OBJECTS) $(runner_DEPENDENCIES) $(EXTRA_runner_DEPENDENCIES) @rm -f runner$(EXEEXT) @@ -691,7 +580,6 @@ runner$(EXEEXT): $(runner_OBJECTS) $(runner_DEPENDENCIES) $(EXTRA_runner_DEPENDE mostlyclean-compile: -rm -f *.$(OBJEXT) - -rm -f unit/*.$(OBJEXT) distclean-compile: -rm -f *.tab.c @@ -734,50 +622,50 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test88.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test89.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test90.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_array.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_blob.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_cancel_query.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_column.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_composite.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_connection.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_cursor.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_encodings.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_error_verbosity.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_errorhandler.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_escape.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_exceptions.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_field.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_float.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_largeobject.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_nonblocking_connect.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_notice_handler.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_notification.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_pipeline.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_prepared_statement.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_range.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_read_transaction.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_result_iteration.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_row.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_separated_list.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_simultaneous_transactions.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_sql_cursor.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_stateless_cursor.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_strconv.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_stream_from.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_stream_query.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_stream_to.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_string_conversion.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_subtransaction.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_test_helpers.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_thread_safety_model.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_time.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_transaction.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_transaction_base.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_transaction_focus.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_transactor.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_type_name.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_util.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_zview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_array.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_blob.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_cancel_query.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_column.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_composite.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_connection.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_cursor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_encodings.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_error_verbosity.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_errorhandler.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_escape.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_exceptions.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_field.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_float.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_largeobject.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_nonblocking_connect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_notice_handler.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_notification.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_pipeline.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_prepared_statement.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_range.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_read_transaction.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_result_iteration.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_row.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_separated_list.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_simultaneous_transactions.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_sql_cursor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stateless_cursor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_strconv.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stream_from.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stream_query.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stream_to.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_string_conversion.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_subtransaction.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_test_helpers.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_thread_safety_model.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_time.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_transaction.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_transaction_base.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_transaction_focus.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_transactor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_type_name.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_zview.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @@ -1025,8 +913,6 @@ 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) - -rm -f unit/$(DEPDIR)/$(am__dirstamp) - -rm -f unit/$(am__dirstamp) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @@ -1076,50 +962,50 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/test88.Po -rm -f ./$(DEPDIR)/test89.Po -rm -f ./$(DEPDIR)/test90.Po - -rm -f unit/$(DEPDIR)/test_array.Po - -rm -f unit/$(DEPDIR)/test_blob.Po - -rm -f unit/$(DEPDIR)/test_cancel_query.Po - -rm -f unit/$(DEPDIR)/test_column.Po - -rm -f unit/$(DEPDIR)/test_composite.Po - -rm -f unit/$(DEPDIR)/test_connection.Po - -rm -f unit/$(DEPDIR)/test_cursor.Po - -rm -f unit/$(DEPDIR)/test_encodings.Po - -rm -f unit/$(DEPDIR)/test_error_verbosity.Po - -rm -f unit/$(DEPDIR)/test_errorhandler.Po - -rm -f unit/$(DEPDIR)/test_escape.Po - -rm -f unit/$(DEPDIR)/test_exceptions.Po - -rm -f unit/$(DEPDIR)/test_field.Po - -rm -f unit/$(DEPDIR)/test_float.Po - -rm -f unit/$(DEPDIR)/test_largeobject.Po - -rm -f unit/$(DEPDIR)/test_nonblocking_connect.Po - -rm -f unit/$(DEPDIR)/test_notice_handler.Po - -rm -f unit/$(DEPDIR)/test_notification.Po - -rm -f unit/$(DEPDIR)/test_pipeline.Po - -rm -f unit/$(DEPDIR)/test_prepared_statement.Po - -rm -f unit/$(DEPDIR)/test_range.Po - -rm -f unit/$(DEPDIR)/test_read_transaction.Po - -rm -f unit/$(DEPDIR)/test_result_iteration.Po - -rm -f unit/$(DEPDIR)/test_row.Po - -rm -f unit/$(DEPDIR)/test_separated_list.Po - -rm -f unit/$(DEPDIR)/test_simultaneous_transactions.Po - -rm -f unit/$(DEPDIR)/test_sql_cursor.Po - -rm -f unit/$(DEPDIR)/test_stateless_cursor.Po - -rm -f unit/$(DEPDIR)/test_strconv.Po - -rm -f unit/$(DEPDIR)/test_stream_from.Po - -rm -f unit/$(DEPDIR)/test_stream_query.Po - -rm -f unit/$(DEPDIR)/test_stream_to.Po - -rm -f unit/$(DEPDIR)/test_string_conversion.Po - -rm -f unit/$(DEPDIR)/test_subtransaction.Po - -rm -f unit/$(DEPDIR)/test_test_helpers.Po - -rm -f unit/$(DEPDIR)/test_thread_safety_model.Po - -rm -f unit/$(DEPDIR)/test_time.Po - -rm -f unit/$(DEPDIR)/test_transaction.Po - -rm -f unit/$(DEPDIR)/test_transaction_base.Po - -rm -f unit/$(DEPDIR)/test_transaction_focus.Po - -rm -f unit/$(DEPDIR)/test_transactor.Po - -rm -f unit/$(DEPDIR)/test_type_name.Po - -rm -f unit/$(DEPDIR)/test_util.Po - -rm -f unit/$(DEPDIR)/test_zview.Po + -rm -f ./$(DEPDIR)/test_array.Po + -rm -f ./$(DEPDIR)/test_blob.Po + -rm -f ./$(DEPDIR)/test_cancel_query.Po + -rm -f ./$(DEPDIR)/test_column.Po + -rm -f ./$(DEPDIR)/test_composite.Po + -rm -f ./$(DEPDIR)/test_connection.Po + -rm -f ./$(DEPDIR)/test_cursor.Po + -rm -f ./$(DEPDIR)/test_encodings.Po + -rm -f ./$(DEPDIR)/test_error_verbosity.Po + -rm -f ./$(DEPDIR)/test_errorhandler.Po + -rm -f ./$(DEPDIR)/test_escape.Po + -rm -f ./$(DEPDIR)/test_exceptions.Po + -rm -f ./$(DEPDIR)/test_field.Po + -rm -f ./$(DEPDIR)/test_float.Po + -rm -f ./$(DEPDIR)/test_largeobject.Po + -rm -f ./$(DEPDIR)/test_nonblocking_connect.Po + -rm -f ./$(DEPDIR)/test_notice_handler.Po + -rm -f ./$(DEPDIR)/test_notification.Po + -rm -f ./$(DEPDIR)/test_pipeline.Po + -rm -f ./$(DEPDIR)/test_prepared_statement.Po + -rm -f ./$(DEPDIR)/test_range.Po + -rm -f ./$(DEPDIR)/test_read_transaction.Po + -rm -f ./$(DEPDIR)/test_result_iteration.Po + -rm -f ./$(DEPDIR)/test_row.Po + -rm -f ./$(DEPDIR)/test_separated_list.Po + -rm -f ./$(DEPDIR)/test_simultaneous_transactions.Po + -rm -f ./$(DEPDIR)/test_sql_cursor.Po + -rm -f ./$(DEPDIR)/test_stateless_cursor.Po + -rm -f ./$(DEPDIR)/test_strconv.Po + -rm -f ./$(DEPDIR)/test_stream_from.Po + -rm -f ./$(DEPDIR)/test_stream_query.Po + -rm -f ./$(DEPDIR)/test_stream_to.Po + -rm -f ./$(DEPDIR)/test_string_conversion.Po + -rm -f ./$(DEPDIR)/test_subtransaction.Po + -rm -f ./$(DEPDIR)/test_test_helpers.Po + -rm -f ./$(DEPDIR)/test_thread_safety_model.Po + -rm -f ./$(DEPDIR)/test_time.Po + -rm -f ./$(DEPDIR)/test_transaction.Po + -rm -f ./$(DEPDIR)/test_transaction_base.Po + -rm -f ./$(DEPDIR)/test_transaction_focus.Po + -rm -f ./$(DEPDIR)/test_transactor.Po + -rm -f ./$(DEPDIR)/test_type_name.Po + -rm -f ./$(DEPDIR)/test_util.Po + -rm -f ./$(DEPDIR)/test_zview.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags @@ -1203,50 +1089,50 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/test88.Po -rm -f ./$(DEPDIR)/test89.Po -rm -f ./$(DEPDIR)/test90.Po - -rm -f unit/$(DEPDIR)/test_array.Po - -rm -f unit/$(DEPDIR)/test_blob.Po - -rm -f unit/$(DEPDIR)/test_cancel_query.Po - -rm -f unit/$(DEPDIR)/test_column.Po - -rm -f unit/$(DEPDIR)/test_composite.Po - -rm -f unit/$(DEPDIR)/test_connection.Po - -rm -f unit/$(DEPDIR)/test_cursor.Po - -rm -f unit/$(DEPDIR)/test_encodings.Po - -rm -f unit/$(DEPDIR)/test_error_verbosity.Po - -rm -f unit/$(DEPDIR)/test_errorhandler.Po - -rm -f unit/$(DEPDIR)/test_escape.Po - -rm -f unit/$(DEPDIR)/test_exceptions.Po - -rm -f unit/$(DEPDIR)/test_field.Po - -rm -f unit/$(DEPDIR)/test_float.Po - -rm -f unit/$(DEPDIR)/test_largeobject.Po - -rm -f unit/$(DEPDIR)/test_nonblocking_connect.Po - -rm -f unit/$(DEPDIR)/test_notice_handler.Po - -rm -f unit/$(DEPDIR)/test_notification.Po - -rm -f unit/$(DEPDIR)/test_pipeline.Po - -rm -f unit/$(DEPDIR)/test_prepared_statement.Po - -rm -f unit/$(DEPDIR)/test_range.Po - -rm -f unit/$(DEPDIR)/test_read_transaction.Po - -rm -f unit/$(DEPDIR)/test_result_iteration.Po - -rm -f unit/$(DEPDIR)/test_row.Po - -rm -f unit/$(DEPDIR)/test_separated_list.Po - -rm -f unit/$(DEPDIR)/test_simultaneous_transactions.Po - -rm -f unit/$(DEPDIR)/test_sql_cursor.Po - -rm -f unit/$(DEPDIR)/test_stateless_cursor.Po - -rm -f unit/$(DEPDIR)/test_strconv.Po - -rm -f unit/$(DEPDIR)/test_stream_from.Po - -rm -f unit/$(DEPDIR)/test_stream_query.Po - -rm -f unit/$(DEPDIR)/test_stream_to.Po - -rm -f unit/$(DEPDIR)/test_string_conversion.Po - -rm -f unit/$(DEPDIR)/test_subtransaction.Po - -rm -f unit/$(DEPDIR)/test_test_helpers.Po - -rm -f unit/$(DEPDIR)/test_thread_safety_model.Po - -rm -f unit/$(DEPDIR)/test_time.Po - -rm -f unit/$(DEPDIR)/test_transaction.Po - -rm -f unit/$(DEPDIR)/test_transaction_base.Po - -rm -f unit/$(DEPDIR)/test_transaction_focus.Po - -rm -f unit/$(DEPDIR)/test_transactor.Po - -rm -f unit/$(DEPDIR)/test_type_name.Po - -rm -f unit/$(DEPDIR)/test_util.Po - -rm -f unit/$(DEPDIR)/test_zview.Po + -rm -f ./$(DEPDIR)/test_array.Po + -rm -f ./$(DEPDIR)/test_blob.Po + -rm -f ./$(DEPDIR)/test_cancel_query.Po + -rm -f ./$(DEPDIR)/test_column.Po + -rm -f ./$(DEPDIR)/test_composite.Po + -rm -f ./$(DEPDIR)/test_connection.Po + -rm -f ./$(DEPDIR)/test_cursor.Po + -rm -f ./$(DEPDIR)/test_encodings.Po + -rm -f ./$(DEPDIR)/test_error_verbosity.Po + -rm -f ./$(DEPDIR)/test_errorhandler.Po + -rm -f ./$(DEPDIR)/test_escape.Po + -rm -f ./$(DEPDIR)/test_exceptions.Po + -rm -f ./$(DEPDIR)/test_field.Po + -rm -f ./$(DEPDIR)/test_float.Po + -rm -f ./$(DEPDIR)/test_largeobject.Po + -rm -f ./$(DEPDIR)/test_nonblocking_connect.Po + -rm -f ./$(DEPDIR)/test_notice_handler.Po + -rm -f ./$(DEPDIR)/test_notification.Po + -rm -f ./$(DEPDIR)/test_pipeline.Po + -rm -f ./$(DEPDIR)/test_prepared_statement.Po + -rm -f ./$(DEPDIR)/test_range.Po + -rm -f ./$(DEPDIR)/test_read_transaction.Po + -rm -f ./$(DEPDIR)/test_result_iteration.Po + -rm -f ./$(DEPDIR)/test_row.Po + -rm -f ./$(DEPDIR)/test_separated_list.Po + -rm -f ./$(DEPDIR)/test_simultaneous_transactions.Po + -rm -f ./$(DEPDIR)/test_sql_cursor.Po + -rm -f ./$(DEPDIR)/test_stateless_cursor.Po + -rm -f ./$(DEPDIR)/test_strconv.Po + -rm -f ./$(DEPDIR)/test_stream_from.Po + -rm -f ./$(DEPDIR)/test_stream_query.Po + -rm -f ./$(DEPDIR)/test_stream_to.Po + -rm -f ./$(DEPDIR)/test_string_conversion.Po + -rm -f ./$(DEPDIR)/test_subtransaction.Po + -rm -f ./$(DEPDIR)/test_test_helpers.Po + -rm -f ./$(DEPDIR)/test_thread_safety_model.Po + -rm -f ./$(DEPDIR)/test_time.Po + -rm -f ./$(DEPDIR)/test_transaction.Po + -rm -f ./$(DEPDIR)/test_transaction_base.Po + -rm -f ./$(DEPDIR)/test_transaction_focus.Po + -rm -f ./$(DEPDIR)/test_transactor.Po + -rm -f ./$(DEPDIR)/test_type_name.Po + -rm -f ./$(DEPDIR)/test_util.Po + -rm -f ./$(DEPDIR)/test_zview.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic diff --git a/test/unit/test_array.cxx b/test/test_array.cxx similarity index 99% rename from test/unit/test_array.cxx rename to test/test_array.cxx index c8861f2ac..e7da2ef6c 100644 --- a/test/unit/test_array.cxx +++ b/test/test_array.cxx @@ -1,6 +1,6 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" // Test program for libpqxx array parsing. diff --git a/test/unit/test_blob.cxx b/test/test_blob.cxx similarity index 99% rename from test/unit/test_blob.cxx rename to test/test_blob.cxx index b7c540be1..eb603170b 100644 --- a/test/unit/test_blob.cxx +++ b/test/test_blob.cxx @@ -4,8 +4,8 @@ #include #include -#include "../test_helpers.hxx" -#include "../test_types.hxx" +#include "test_helpers.hxx" +#include "test_types.hxx" namespace diff --git a/test/unit/test_cancel_query.cxx b/test/test_cancel_query.cxx similarity index 94% rename from test/unit/test_cancel_query.cxx rename to test/test_cancel_query.cxx index b38f0206a..c9b1344f5 100644 --- a/test/unit/test_cancel_query.cxx +++ b/test/test_cancel_query.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_column.cxx b/test/test_column.cxx similarity index 98% rename from test/unit/test_column.cxx rename to test/test_column.cxx index eb84acd63..69a318bd1 100644 --- a/test/unit/test_column.cxx +++ b/test/test_column.cxx @@ -1,6 +1,6 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_composite.cxx b/test/test_composite.cxx similarity index 98% rename from test/unit/test_composite.cxx rename to test/test_composite.cxx index 9e5745070..ba3d73982 100644 --- a/test/unit/test_composite.cxx +++ b/test/test_composite.cxx @@ -1,4 +1,4 @@ -#include "../test_helpers.hxx" +#include "test_helpers.hxx" #include "pqxx/composite" #include "pqxx/transaction" diff --git a/test/unit/test_connection.cxx b/test/test_connection.cxx similarity index 99% rename from test/unit/test_connection.cxx rename to test/test_connection.cxx index e9a52809d..38d7cd7a3 100644 --- a/test/unit/test_connection.cxx +++ b/test/test_connection.cxx @@ -3,7 +3,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_cursor.cxx b/test/test_cursor.cxx similarity index 97% rename from test/unit/test_cursor.cxx rename to test/test_cursor.cxx index 2a2b2b243..b5676cdef 100644 --- a/test/unit/test_cursor.cxx +++ b/test/test_cursor.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_encodings.cxx b/test/test_encodings.cxx similarity index 99% rename from test/unit/test_encodings.cxx rename to test/test_encodings.cxx index 4374671c6..7965357a5 100644 --- a/test/unit/test_encodings.cxx +++ b/test/test_encodings.cxx @@ -1,4 +1,4 @@ -#include "../test_helpers.hxx" +#include "test_helpers.hxx" #include "pqxx/internal/encodings.hxx" diff --git a/test/unit/test_error_verbosity.cxx b/test/test_error_verbosity.cxx similarity index 96% rename from test/unit/test_error_verbosity.cxx rename to test/test_error_verbosity.cxx index c77df17c7..c47782c05 100644 --- a/test/unit/test_error_verbosity.cxx +++ b/test/test_error_verbosity.cxx @@ -1,6 +1,6 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" extern "C" { diff --git a/test/unit/test_errorhandler.cxx b/test/test_errorhandler.cxx similarity index 99% rename from test/unit/test_errorhandler.cxx rename to test/test_errorhandler.cxx index edfa4f19f..b40275694 100644 --- a/test/unit/test_errorhandler.cxx +++ b/test/test_errorhandler.cxx @@ -3,7 +3,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_escape.cxx b/test/test_escape.cxx similarity index 99% rename from test/unit/test_escape.cxx rename to test/test_escape.cxx index 51546eb5f..408e6fe88 100644 --- a/test/unit/test_escape.cxx +++ b/test/test_escape.cxx @@ -2,7 +2,7 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_exceptions.cxx b/test/test_exceptions.cxx similarity index 96% rename from test/unit/test_exceptions.cxx rename to test/test_exceptions.cxx index 1fee49c84..831c7c5ac 100644 --- a/test/unit/test_exceptions.cxx +++ b/test/test_exceptions.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace diff --git a/test/unit/test_field.cxx b/test/test_field.cxx similarity index 98% rename from test/unit/test_field.cxx rename to test/test_field.cxx index 86a156568..1995d85a4 100644 --- a/test/unit/test_field.cxx +++ b/test/test_field.cxx @@ -1,6 +1,6 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_float.cxx b/test/test_float.cxx similarity index 99% rename from test/unit/test_float.cxx rename to test/test_float.cxx index 312409a3d..308bd7bbc 100644 --- a/test/unit/test_float.cxx +++ b/test/test_float.cxx @@ -2,7 +2,7 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_largeobject.cxx b/test/test_largeobject.cxx similarity index 98% rename from test/unit/test_largeobject.cxx rename to test/test_largeobject.cxx index 4fa1c10b2..e6113ae77 100644 --- a/test/unit/test_largeobject.cxx +++ b/test/test_largeobject.cxx @@ -4,7 +4,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_nonblocking_connect.cxx b/test/test_nonblocking_connect.cxx similarity index 94% rename from test/unit/test_nonblocking_connect.cxx rename to test/test_nonblocking_connect.cxx index 1b24b814c..eb690f022 100644 --- a/test/unit/test_nonblocking_connect.cxx +++ b/test/test_nonblocking_connect.cxx @@ -2,7 +2,7 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace diff --git a/test/unit/test_notice_handler.cxx b/test/test_notice_handler.cxx similarity index 99% rename from test/unit/test_notice_handler.cxx rename to test/test_notice_handler.cxx index 45b7957b9..2ab060504 100644 --- a/test/unit/test_notice_handler.cxx +++ b/test/test_notice_handler.cxx @@ -1,6 +1,6 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_notification.cxx b/test/test_notification.cxx similarity index 99% rename from test/unit/test_notification.cxx rename to test/test_notification.cxx index 2c38fbb88..1864585c2 100644 --- a/test/unit/test_notification.cxx +++ b/test/test_notification.cxx @@ -10,7 +10,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_pipeline.cxx b/test/test_pipeline.cxx similarity index 98% rename from test/unit/test_pipeline.cxx rename to test/test_pipeline.cxx index 8011c57ee..ff78ba3d2 100644 --- a/test/unit/test_pipeline.cxx +++ b/test/test_pipeline.cxx @@ -3,7 +3,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_prepared_statement.cxx b/test/test_prepared_statement.cxx similarity index 99% rename from test/unit/test_prepared_statement.cxx rename to test/test_prepared_statement.cxx index 54596a54a..bf26d6cce 100644 --- a/test/unit/test_prepared_statement.cxx +++ b/test/test_prepared_statement.cxx @@ -5,7 +5,7 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" // Test program for libpqxx. Define and use prepared statements. diff --git a/test/unit/test_range.cxx b/test/test_range.cxx similarity index 99% rename from test/unit/test_range.cxx rename to test/test_range.cxx index 0272a889b..52ff98399 100644 --- a/test/unit/test_range.cxx +++ b/test/test_range.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace diff --git a/test/unit/test_read_transaction.cxx b/test/test_read_transaction.cxx similarity index 93% rename from test/unit/test_read_transaction.cxx rename to test/test_read_transaction.cxx index 28c3a2041..9723b7e8a 100644 --- a/test/unit/test_read_transaction.cxx +++ b/test/test_read_transaction.cxx @@ -1,6 +1,6 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_result_iteration.cxx b/test/test_result_iteration.cxx similarity index 99% rename from test/unit/test_result_iteration.cxx rename to test/test_result_iteration.cxx index ff444e28b..aee58994a 100644 --- a/test/unit/test_result_iteration.cxx +++ b/test/test_result_iteration.cxx @@ -3,7 +3,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_row.cxx b/test/test_row.cxx similarity index 99% rename from test/unit/test_row.cxx rename to test/test_row.cxx index 7beaa5f03..ed49abc9b 100644 --- a/test/unit/test_row.cxx +++ b/test/test_row.cxx @@ -2,7 +2,7 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_separated_list.cxx b/test/test_separated_list.cxx similarity index 96% rename from test/unit/test_separated_list.cxx rename to test/test_separated_list.cxx index 748379b47..310cdd2c1 100644 --- a/test/unit/test_separated_list.cxx +++ b/test/test_separated_list.cxx @@ -1,6 +1,6 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" // Test program for separated_list. diff --git a/test/unit/test_simultaneous_transactions.cxx b/test/test_simultaneous_transactions.cxx similarity index 92% rename from test/unit/test_simultaneous_transactions.cxx rename to test/test_simultaneous_transactions.cxx index 1c536a95e..180fa6206 100644 --- a/test/unit/test_simultaneous_transactions.cxx +++ b/test/test_simultaneous_transactions.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_sql_cursor.cxx b/test/test_sql_cursor.cxx similarity index 99% rename from test/unit/test_sql_cursor.cxx rename to test/test_sql_cursor.cxx index 2be893fb9..bc37b4030 100644 --- a/test/unit/test_sql_cursor.cxx +++ b/test/test_sql_cursor.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_stateless_cursor.cxx b/test/test_stateless_cursor.cxx similarity index 99% rename from test/unit/test_stateless_cursor.cxx rename to test/test_stateless_cursor.cxx index 1ee538440..39f0b4b57 100644 --- a/test/unit/test_stateless_cursor.cxx +++ b/test/test_stateless_cursor.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_strconv.cxx b/test/test_strconv.cxx similarity index 99% rename from test/unit/test_strconv.cxx rename to test/test_strconv.cxx index be6ce0ee7..baaf26c72 100644 --- a/test/unit/test_strconv.cxx +++ b/test/test_strconv.cxx @@ -3,7 +3,7 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_stream_from.cxx b/test/test_stream_from.cxx similarity index 99% rename from test/unit/test_stream_from.cxx rename to test/test_stream_from.cxx index 03606d918..534c20137 100644 --- a/test/unit/test_stream_from.cxx +++ b/test/test_stream_from.cxx @@ -1,8 +1,8 @@ #include #include -#include "../test_helpers.hxx" -#include "../test_types.hxx" +#include "test_helpers.hxx" +#include "test_types.hxx" #include #include diff --git a/test/unit/test_stream_query.cxx b/test/test_stream_query.cxx similarity index 99% rename from test/unit/test_stream_query.cxx rename to test/test_stream_query.cxx index a89bea43d..2f12e0179 100644 --- a/test/unit/test_stream_query.cxx +++ b/test/test_stream_query.cxx @@ -1,7 +1,7 @@ #include -#include "../test_helpers.hxx" -#include "../test_types.hxx" +#include "test_helpers.hxx" +#include "test_types.hxx" #include #include diff --git a/test/unit/test_stream_to.cxx b/test/test_stream_to.cxx similarity index 99% rename from test/unit/test_stream_to.cxx rename to test/test_stream_to.cxx index 20efef55f..634179039 100644 --- a/test/unit/test_stream_to.cxx +++ b/test/test_stream_to.cxx @@ -4,8 +4,8 @@ #include #include -#include "../test_helpers.hxx" -#include "../test_types.hxx" +#include "test_helpers.hxx" +#include "test_types.hxx" namespace { diff --git a/test/unit/test_string_conversion.cxx b/test/test_string_conversion.cxx similarity index 99% rename from test/unit/test_string_conversion.cxx rename to test/test_string_conversion.cxx index 44e4761c8..dbbdd6e63 100644 --- a/test/unit/test_string_conversion.cxx +++ b/test/test_string_conversion.cxx @@ -4,7 +4,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" using namespace std::literals; diff --git a/test/unit/test_subtransaction.cxx b/test/test_subtransaction.cxx similarity index 98% rename from test/unit/test_subtransaction.cxx rename to test/test_subtransaction.cxx index 941fc5519..7c17b8cad 100644 --- a/test/unit/test_subtransaction.cxx +++ b/test/test_subtransaction.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_test_helpers.cxx b/test/test_test_helpers.cxx similarity index 99% rename from test/unit/test_test_helpers.cxx rename to test/test_test_helpers.cxx index f6ccf94b7..cc84b536a 100644 --- a/test/unit/test_test_helpers.cxx +++ b/test/test_test_helpers.cxx @@ -1,4 +1,4 @@ -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_thread_safety_model.cxx b/test/test_thread_safety_model.cxx similarity index 93% rename from test/unit/test_thread_safety_model.cxx rename to test/test_thread_safety_model.cxx index cf7627cb3..97b99869f 100644 --- a/test/unit/test_thread_safety_model.cxx +++ b/test/test_thread_safety_model.cxx @@ -1,4 +1,4 @@ -#include "../test_helpers.hxx" +#include "test_helpers.hxx" #include diff --git a/test/unit/test_time.cxx b/test/test_time.cxx similarity index 98% rename from test/unit/test_time.cxx rename to test/test_time.cxx index 115af2d9c..621d5f5b5 100644 --- a/test/unit/test_time.cxx +++ b/test/test_time.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_transaction.cxx b/test/test_transaction.cxx similarity index 98% rename from test/unit/test_transaction.cxx rename to test/test_transaction.cxx index 0195d196a..cd7762a68 100644 --- a/test/unit/test_transaction.cxx +++ b/test/test_transaction.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace diff --git a/test/unit/test_transaction_base.cxx b/test/test_transaction_base.cxx similarity index 99% rename from test/unit/test_transaction_base.cxx rename to test/test_transaction_base.cxx index 4f3b26980..bed2d0192 100644 --- a/test/unit/test_transaction_base.cxx +++ b/test/test_transaction_base.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_transaction_focus.cxx b/test/test_transaction_focus.cxx similarity index 97% rename from test/unit/test_transaction_focus.cxx rename to test/test_transaction_focus.cxx index 31b4a6f4c..e056c0be2 100644 --- a/test/unit/test_transaction_focus.cxx +++ b/test/test_transaction_focus.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_transactor.cxx b/test/test_transactor.cxx similarity index 99% rename from test/unit/test_transactor.cxx rename to test/test_transactor.cxx index f278e0e93..6fa12c520 100644 --- a/test/unit/test_transactor.cxx +++ b/test/test_transactor.cxx @@ -1,7 +1,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_type_name.cxx b/test/test_type_name.cxx similarity index 94% rename from test/unit/test_type_name.cxx rename to test/test_type_name.cxx index af1c93eb5..5cdc12fe6 100644 --- a/test/unit/test_type_name.cxx +++ b/test/test_type_name.cxx @@ -1,4 +1,4 @@ -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_util.cxx b/test/test_util.cxx similarity index 96% rename from test/unit/test_util.cxx rename to test/test_util.cxx index 01c447635..293a937e6 100644 --- a/test/unit/test_util.cxx +++ b/test/test_util.cxx @@ -3,7 +3,7 @@ #include #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace { diff --git a/test/unit/test_zview.cxx b/test/test_zview.cxx similarity index 97% rename from test/unit/test_zview.cxx rename to test/test_zview.cxx index a853da6aa..d7920a7bc 100644 --- a/test/unit/test_zview.cxx +++ b/test/test_zview.cxx @@ -2,7 +2,7 @@ #include -#include "../test_helpers.hxx" +#include "test_helpers.hxx" namespace diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt deleted file mode 100644 index 34be39dab..000000000 --- a/test/unit/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -if(NOT PostgreSQL_FOUND) - find_package(PostgreSQL REQUIRED) -endif() - -file(GLOB UNIT_TEST_SOURCES *.cxx) - -add_executable(unit_runner ${UNIT_TEST_SOURCES}) -target_link_libraries(unit_runner PUBLIC pqxx) -target_include_directories(unit_runner PRIVATE ${PostgreSQL_INCLUDE_DIRS}) -add_test( - NAME unit_runner - WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} - COMMAND unit_runner -) From 42f6b172ad404494058341282fcffec53adbcea4 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sun, 26 Jan 2025 00:44:21 +0100 Subject: [PATCH 60/64] A few more tweaks to generic binary support. (#940) --- include/pqxx/doc/prepared-statement.md | 3 ++- include/pqxx/params.hxx | 10 +--------- src/params.cxx | 6 ------ test/test_util.cxx | 2 ++ 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/include/pqxx/doc/prepared-statement.md b/include/pqxx/doc/prepared-statement.md index ba00947f0..5d81b3e79 100644 --- a/include/pqxx/doc/prepared-statement.md +++ b/include/pqxx/doc/prepared-statement.md @@ -136,4 +136,5 @@ 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 `pqxx::bytes`, or `pqxx::bytes_view`, or -`std::vector`. +`std::vector`, or `std::array`, or +`std::span`, etc. diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index 52d3f8340..a697492de 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -90,20 +90,12 @@ public: */ void append(bytes_view) &; - /// Append a non-null binary parameter. - /** Copies the underlying data into internal storage. For best efficiency, - * use the `pqxx::bytes_view` variant if you can, or `std::move()`. - */ - void append(bytes const &) &; - /// Append a non-null binary parameter. /** The `data` object must stay in place and unchanged, for as long as the * `params` remains active. */ template void append(DATA const &data) & - { - append(bytes_view{std::data(data), std::size(data)}); - } + { append(binary_cast(data)); } /// Append a non-null binary parameter. void append(bytes &&) &; diff --git a/src/params.cxx b/src/params.cxx index aa8350967..603c405d5 100644 --- a/src/params.cxx +++ b/src/params.cxx @@ -66,12 +66,6 @@ void pqxx::params::append(bytes_view value) & } -void pqxx::params::append(bytes const &value) & -{ - m_params.emplace_back(value); -} - - void pqxx::params::append(bytes &&value) & { m_params.emplace_back(std::move(value)); diff --git a/test/test_util.cxx b/test/test_util.cxx index 293a937e6..61142e0f6 100644 --- a/test/test_util.cxx +++ b/test/test_util.cxx @@ -30,6 +30,8 @@ void test_binary_cast() std::byte{0x22}, std::byte{0x23}, std::byte{0x24}}; test_for(bytes_c_array); test_for("Hello world"); + test_for(std::string{"I'm a string"}); + test_for(std::string_view{"I'm a string_view"}); test_for(std::vector{'n', 'o', 'p', 'q'}); test_for(std::vector{'n', 'o', 'p', 'q'}); From 1593123b9b41d1ff8c42e3815e49dde134f996fe Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sun, 26 Jan 2025 02:18:19 +0100 Subject: [PATCH 61/64] Assume support for `std::source_location`. (#941) * Assume support for `std::source_location`. This is a C++20 feature, and it seems to be widely implemented. Next step of course is to pass the source location in more places, so that exceptions will be properly helpful in pinpointing where their errors happened. Ideally an error will tell you the location of the original call into libpqxx that led to the problem. * Use `std::map::contains()`. --- .gitignore | 2 + cmake/pqxx_cxx_feature_checks.cmake | 4 - config-tests/PQXX_HAVE_SOURCE_LOCATION.cxx | 11 - configitems | 1 - configure | 40 --- cxx_features.txt | 1 - include/pqxx/config.h.in | 3 - include/pqxx/except.hxx | 331 ++------------------- include/pqxx/params.hxx | 4 +- pqxx_cxx_feature_checks.ac | 10 - src/except.cxx | 228 +++----------- src/pipeline.cxx | 2 +- src/transaction.cxx | 8 +- test/runner.cxx | 58 +--- test/test_helpers.hxx | 224 +++----------- test/test_stream_from.cxx | 4 +- test/test_test_helpers.cxx | 19 +- 17 files changed, 117 insertions(+), 833 deletions(-) delete mode 100644 config-tests/PQXX_HAVE_SOURCE_LOCATION.cxx diff --git a/.gitignore b/.gitignore index 3dc64cd81..4e9e62bee 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ CMakeFiles/CMakeTmp confdefs.h config.log config.status +configure~ conftest conftest.cpp conftest.err +include/pqxx/config.h.in~ libpqxx.pc libpqxx-*.tar.gz libtool diff --git a/cmake/pqxx_cxx_feature_checks.cmake b/cmake/pqxx_cxx_feature_checks.cmake index bb2f6253a..7732ff6ec 100644 --- a/cmake/pqxx_cxx_feature_checks.cmake +++ b/cmake/pqxx_cxx_feature_checks.cmake @@ -31,10 +31,6 @@ try_compile( PQXX_HAVE_SLEEP_FOR ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_SLEEP_FOR.cxx ) -try_compile( - PQXX_HAVE_SOURCE_LOCATION ${PROJECT_BINARY_DIR} - SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_SOURCE_LOCATION.cxx -) try_compile( PQXX_HAVE_STRERROR_R ${PROJECT_BINARY_DIR} SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_STRERROR_R.cxx diff --git a/config-tests/PQXX_HAVE_SOURCE_LOCATION.cxx b/config-tests/PQXX_HAVE_SOURCE_LOCATION.cxx deleted file mode 100644 index 234a593c3..000000000 --- a/config-tests/PQXX_HAVE_SOURCE_LOCATION.cxx +++ /dev/null @@ -1,11 +0,0 @@ -// Feature check for 'PQXX_HAVE_SOURCE_LOCATION'. -// Generated by generate_cxx_checks.py. -#include -#if !defined(__cpp_lib_source_location) -# error "No PQXX_HAVE_SOURCE_LOCATION: __cpp_lib_source_location is not set." -#endif -#if !__cpp_lib_source_location -# error "No PQXX_HAVE_SOURCE_LOCATION: __cpp_lib_source_location is false." -#endif - -int main() {} diff --git a/configitems b/configitems index 58af65a35..7e77494fb 100644 --- a/configitems +++ b/configitems @@ -12,7 +12,6 @@ PQXX_HAVE_GCC_VISIBILITY public compiler PQXX_HAVE_MULTIDIM public compiler PQXX_HAVE_POLL internal compiler PQXX_HAVE_SLEEP_FOR internal compiler -PQXX_HAVE_SOURCE_LOCATION public compiler PQXX_HAVE_STRERROR_R public compiler PQXX_HAVE_STRERROR_S public compiler PQXX_HAVE_THREAD_LOCAL internal compiler diff --git a/configure b/configure index 73cb691b5..50169ea8a 100755 --- a/configure +++ b/configure @@ -17622,46 +17622,6 @@ fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_SLEEP_FOR" >&5 printf "%s\n" "$PQXX_HAVE_SLEEP_FOR" >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_SOURCE_LOCATION" >&5 -printf %s "checking PQXX_HAVE_SOURCE_LOCATION... " >&6; } -PQXX_HAVE_SOURCE_LOCATION=yes -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -// Feature check for 'PQXX_HAVE_SOURCE_LOCATION'. - -// Generated by generate_cxx_checks.py. - -#include - -#if !defined(__cpp_lib_source_location) - -# error "No PQXX_HAVE_SOURCE_LOCATION: __cpp_lib_source_location is not set." - -#endif - -#if !__cpp_lib_source_location - -# error "No PQXX_HAVE_SOURCE_LOCATION: __cpp_lib_source_location is false." - -#endif - - - -int main() {} - - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO" -then : - -printf "%s\n" "#define PQXX_HAVE_SOURCE_LOCATION 1" >>confdefs.h - -else $as_nop - PQXX_HAVE_SOURCE_LOCATION=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_SOURCE_LOCATION" >&5 -printf "%s\n" "$PQXX_HAVE_SOURCE_LOCATION" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_STRERROR_R" >&5 printf %s "checking PQXX_HAVE_STRERROR_R... " >&6; } PQXX_HAVE_STRERROR_R=yes diff --git a/cxx_features.txt b/cxx_features.txt index 3b722f97e..41b3c07f8 100644 --- a/cxx_features.txt +++ b/cxx_features.txt @@ -13,4 +13,3 @@ # end up in the actual configuration headers. PQXX_HAVE_MULTIDIM __cpp_multidimensional_subscript -PQXX_HAVE_SOURCE_LOCATION __cpp_lib_source_location diff --git a/include/pqxx/config.h.in b/include/pqxx/config.h.in index 0e9834a5b..53ae326a0 100644 --- a/include/pqxx/config.h.in +++ b/include/pqxx/config.h.in @@ -81,9 +81,6 @@ /* Define if this feature is available. */ #undef PQXX_HAVE_SLEEP_FOR -/* Define if this feature is available. */ -#undef PQXX_HAVE_SOURCE_LOCATION - /* Define if this feature is available. */ #undef PQXX_HAVE_STRERROR_R diff --git a/include/pqxx/except.hxx b/include/pqxx/except.hxx index 26d454413..63cad9af9 100644 --- a/include/pqxx/except.hxx +++ b/include/pqxx/except.hxx @@ -17,10 +17,7 @@ # error "Include libpqxx headers as , not ." #endif -#if defined(PQXX_HAVE_SOURCE_LOCATION) -# include -#endif - +#include #include #include @@ -46,14 +43,10 @@ namespace pqxx /// Run-time failure encountered by libpqxx, similar to std::runtime_error. struct PQXX_LIBEXPORT failure : std::runtime_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit failure( std::string const &, std::source_location = std::source_location::current()); std::source_location location; -#else - explicit failure(std::string const &); -#endif }; @@ -81,12 +74,8 @@ struct PQXX_LIBEXPORT broken_connection : failure { broken_connection(); explicit broken_connection( - std::string const & -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + std::string const &, + std::source_location = std::source_location::current()); }; @@ -102,12 +91,8 @@ struct PQXX_LIBEXPORT broken_connection : failure struct PQXX_LIBEXPORT protocol_violation : broken_connection { explicit protocol_violation( - std::string const & -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + std::string const &, + std::source_location = std::source_location::current()); }; @@ -115,12 +100,8 @@ struct PQXX_LIBEXPORT protocol_violation : broken_connection struct PQXX_LIBEXPORT variable_set_to_null : failure { explicit variable_set_to_null( - std::string const & -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + std::string const &, + std::source_location = std::source_location::current()); }; @@ -138,12 +119,8 @@ class PQXX_LIBEXPORT sql_error : public failure public: explicit sql_error( std::string const &whatarg = "", std::string Q = "", - char const *sqlstate = nullptr -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + char const *sqlstate = nullptr, + std::source_location = std::source_location::current()); virtual ~sql_error() noexcept override; /// The query whose execution triggered the exception @@ -164,12 +141,8 @@ public: struct PQXX_LIBEXPORT in_doubt_error : failure { explicit in_doubt_error( - std::string const & -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + std::string const &, + std::source_location = std::source_location::current()); }; @@ -178,12 +151,8 @@ struct PQXX_LIBEXPORT transaction_rollback : sql_error { explicit transaction_rollback( std::string const &whatarg, std::string const &q = "", - char const sqlstate[] = nullptr -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + char const sqlstate[] = nullptr, + std::source_location = std::source_location::current()); }; @@ -200,12 +169,8 @@ struct PQXX_LIBEXPORT serialization_failure : transaction_rollback { explicit serialization_failure( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + char const sqlstate[] = nullptr, + std::source_location = std::source_location::current()); }; @@ -214,12 +179,8 @@ struct PQXX_LIBEXPORT statement_completion_unknown : transaction_rollback { explicit statement_completion_unknown( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + char const sqlstate[] = nullptr, + std::source_location = std::source_location::current()); }; @@ -228,12 +189,8 @@ struct PQXX_LIBEXPORT deadlock_detected : transaction_rollback { explicit deadlock_detected( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + char const sqlstate[] = nullptr, + std::source_location = std::source_location::current()); }; @@ -248,16 +205,10 @@ struct PQXX_LIBEXPORT internal_error : std::logic_error struct PQXX_LIBEXPORT usage_error : std::logic_error { explicit usage_error( - std::string const & -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + std::string const &, + std::source_location = std::source_location::current()); -#if defined(PQXX_HAVE_SOURCE_LOCATION) std::source_location location; -#endif }; @@ -265,16 +216,10 @@ struct PQXX_LIBEXPORT usage_error : std::logic_error struct PQXX_LIBEXPORT argument_error : std::invalid_argument { explicit argument_error( - std::string const & -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + std::string const &, + std::source_location = std::source_location::current()); -#if defined(PQXX_HAVE_SOURCE_LOCATION) std::source_location location; -#endif }; @@ -282,16 +227,10 @@ struct PQXX_LIBEXPORT argument_error : std::invalid_argument struct PQXX_LIBEXPORT conversion_error : std::domain_error { explicit conversion_error( - std::string const & -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + std::string const &, + std::source_location = std::source_location::current()); -#if defined(PQXX_HAVE_SOURCE_LOCATION) std::source_location location; -#endif }; @@ -299,12 +238,8 @@ struct PQXX_LIBEXPORT conversion_error : std::domain_error struct PQXX_LIBEXPORT unexpected_null : conversion_error { explicit unexpected_null( - std::string const & -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + std::string const &, + std::source_location = std::source_location::current()); }; @@ -312,12 +247,8 @@ struct PQXX_LIBEXPORT unexpected_null : conversion_error struct PQXX_LIBEXPORT conversion_overrun : conversion_error { explicit conversion_overrun( - std::string const & -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + std::string const &, + std::source_location = std::source_location::current()); }; @@ -325,232 +256,134 @@ struct PQXX_LIBEXPORT conversion_overrun : conversion_error struct PQXX_LIBEXPORT range_error : std::out_of_range { explicit range_error( - std::string const & -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location = std::source_location::current() -#endif - ); + std::string const &, + std::source_location = std::source_location::current()); -#if defined(PQXX_HAVE_SOURCE_LOCATION) std::source_location location; -#endif }; /// Query returned an unexpected number of rows. struct PQXX_LIBEXPORT unexpected_rows : public range_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit unexpected_rows( std::string const &msg, std::source_location loc = std::source_location::current()) : range_error{msg, loc} {} -#else - explicit unexpected_rows(std::string const &msg) : range_error{msg} {} -#endif }; /// Database feature not supported in current setup. struct PQXX_LIBEXPORT feature_not_supported : sql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit feature_not_supported( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : sql_error{err, Q, sqlstate, loc} {} -#else - explicit feature_not_supported( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - sql_error{err, Q, sqlstate} - {} -#endif }; /// Error in data provided to SQL statement. struct PQXX_LIBEXPORT data_exception : sql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit data_exception( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : sql_error{err, Q, sqlstate, loc} {} -#else - explicit data_exception( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - sql_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT integrity_constraint_violation : sql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit integrity_constraint_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : sql_error{err, Q, sqlstate, loc} {} -#else - explicit integrity_constraint_violation( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - sql_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT restrict_violation : integrity_constraint_violation { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit restrict_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} -#else - explicit restrict_violation( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - integrity_constraint_violation{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT not_null_violation : integrity_constraint_violation { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit not_null_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} -#else - explicit not_null_violation( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - integrity_constraint_violation{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT foreign_key_violation : integrity_constraint_violation { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit foreign_key_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} -#else - explicit foreign_key_violation( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - integrity_constraint_violation{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT unique_violation : integrity_constraint_violation { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit unique_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} -#else - explicit unique_violation( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - integrity_constraint_violation{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT check_violation : integrity_constraint_violation { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit check_violation( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} -#else - explicit check_violation( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - integrity_constraint_violation{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT invalid_cursor_state : sql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit invalid_cursor_state( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : sql_error{err, Q, sqlstate, loc} {} -#else - explicit invalid_cursor_state( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - sql_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT invalid_sql_statement_name : sql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit invalid_sql_statement_name( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : sql_error{err, Q, sqlstate, loc} {} -#else - explicit invalid_sql_statement_name( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - sql_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT invalid_cursor_name : sql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit invalid_cursor_name( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : sql_error{err, Q, sqlstate, loc} {} -#else - explicit invalid_cursor_name( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - sql_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT syntax_error : sql_error @@ -558,25 +391,16 @@ struct PQXX_LIBEXPORT syntax_error : sql_error /// Approximate position in string where error occurred, or -1 if unknown. int const error_position; -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit syntax_error( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, int pos = -1, std::source_location loc = std::source_location::current()) : sql_error{err, Q, sqlstate, loc}, error_position{pos} {} -#else - 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} - {} -#endif }; struct PQXX_LIBEXPORT undefined_column : syntax_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit undefined_column( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, @@ -584,18 +408,10 @@ struct PQXX_LIBEXPORT undefined_column : syntax_error // TODO: Can we get the column? syntax_error{err, Q, sqlstate, -1, loc} {} -#else - explicit undefined_column( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - syntax_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT undefined_function : syntax_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit undefined_function( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, @@ -603,18 +419,10 @@ struct PQXX_LIBEXPORT undefined_function : syntax_error // TODO: Can we get the column? syntax_error{err, Q, sqlstate, -1, loc} {} -#else - explicit undefined_function( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - syntax_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT undefined_table : syntax_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit undefined_table( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, @@ -622,101 +430,56 @@ struct PQXX_LIBEXPORT undefined_table : syntax_error // TODO: Can we get the column? syntax_error{err, Q, sqlstate, -1, loc} {} -#else - explicit undefined_table( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - syntax_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT insufficient_privilege : sql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit insufficient_privilege( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : sql_error{err, Q, sqlstate, loc} {} -#else - explicit insufficient_privilege( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - sql_error{err, Q, sqlstate} - {} -#endif }; /// Resource shortage on the server struct PQXX_LIBEXPORT insufficient_resources : sql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit insufficient_resources( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : sql_error{err, Q, sqlstate, loc} {} -#else - explicit insufficient_resources( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - sql_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT disk_full : insufficient_resources { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit disk_full( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : insufficient_resources{err, Q, sqlstate, loc} {} -#else - explicit disk_full( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - insufficient_resources{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT out_of_memory : insufficient_resources { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit out_of_memory( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : insufficient_resources{err, Q, sqlstate, loc} {} -#else - explicit out_of_memory( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - insufficient_resources{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT too_many_connections : broken_connection { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit too_many_connections( std::string const &err, std::source_location loc = std::source_location::current()) : broken_connection{err, loc} {} -#else - explicit too_many_connections(std::string const &err) : - broken_connection{err} - {} -#endif }; /// PL/pgSQL error @@ -724,75 +487,43 @@ struct PQXX_LIBEXPORT too_many_connections : broken_connection */ struct PQXX_LIBEXPORT plpgsql_error : sql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit plpgsql_error( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : sql_error{err, Q, sqlstate, loc} {} -#else - explicit plpgsql_error( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - sql_error{err, Q, sqlstate} - {} -#endif }; /// Exception raised in PL/pgSQL procedure struct PQXX_LIBEXPORT plpgsql_raise : plpgsql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit plpgsql_raise( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : plpgsql_error{err, Q, sqlstate, loc} {} -#else - explicit plpgsql_raise( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - plpgsql_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT plpgsql_no_data_found : plpgsql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit plpgsql_no_data_found( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : plpgsql_error{err, Q, sqlstate, loc} {} -#else - explicit plpgsql_no_data_found( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - plpgsql_error{err, Q, sqlstate} - {} -#endif }; struct PQXX_LIBEXPORT plpgsql_too_many_rows : plpgsql_error { -#if defined(PQXX_HAVE_SOURCE_LOCATION) explicit plpgsql_too_many_rows( std::string const &err, std::string const &Q = "", char const sqlstate[] = nullptr, std::source_location loc = std::source_location::current()) : plpgsql_error{err, Q, sqlstate, loc} {} -#else - explicit plpgsql_too_many_rows( - std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr) : - plpgsql_error{err, Q, sqlstate} - {} -#endif }; /** diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index a697492de..3957d3545 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -95,7 +95,9 @@ public: * `params` remains active. */ template void append(DATA const &data) & - { append(binary_cast(data)); } + { + append(binary_cast(data)); + } /// Append a non-null binary parameter. void append(bytes &&) &; diff --git a/pqxx_cxx_feature_checks.ac b/pqxx_cxx_feature_checks.ac index 9f9f74d8e..fe43586c1 100644 --- a/pqxx_cxx_feature_checks.ac +++ b/pqxx_cxx_feature_checks.ac @@ -79,16 +79,6 @@ AC_COMPILE_IFELSE( [Define if this feature is available.]), PQXX_HAVE_SLEEP_FOR=no) AC_MSG_RESULT($PQXX_HAVE_SLEEP_FOR) -AC_MSG_CHECKING([PQXX_HAVE_SOURCE_LOCATION]) -PQXX_HAVE_SOURCE_LOCATION=yes -AC_COMPILE_IFELSE( - [read_test(PQXX_HAVE_SOURCE_LOCATION.cxx)], - AC_DEFINE( - [PQXX_HAVE_SOURCE_LOCATION], - 1, - [Define if this feature is available.]), - PQXX_HAVE_SOURCE_LOCATION=no) -AC_MSG_RESULT($PQXX_HAVE_SOURCE_LOCATION) AC_MSG_CHECKING([PQXX_HAVE_STRERROR_R]) PQXX_HAVE_STRERROR_R=yes AC_COMPILE_IFELSE( diff --git a/src/except.cxx b/src/except.cxx index 1ada84290..75088c483 100644 --- a/src/except.cxx +++ b/src/except.cxx @@ -15,15 +15,9 @@ #include "pqxx/internal/header-post.hxx" -#if defined(PQXX_HAVE_SOURCE_LOCATION) pqxx::failure::failure(std::string const &whatarg, std::source_location loc) : std::runtime_error{whatarg}, location{loc} {} -#else -pqxx::failure::failure(std::string const &whatarg) : - std::runtime_error{whatarg} -{} -#endif pqxx::broken_connection::broken_connection() : @@ -32,70 +26,27 @@ pqxx::broken_connection::broken_connection() : pqxx::broken_connection::broken_connection( - std::string const &whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - failure{ - whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - } + std::string const &whatarg, std::source_location loc) : + failure{whatarg, loc} {} pqxx::protocol_violation::protocol_violation( - std::string const &whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - broken_connection{ - whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - } + std::string const &whatarg, std::source_location loc) : + broken_connection{whatarg, loc} {} pqxx::variable_set_to_null::variable_set_to_null( - std::string const &whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - failure{ - whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - } + std::string const &whatarg, std::source_location loc) : + failure{whatarg, loc} {} pqxx::sql_error::sql_error( - std::string const &whatarg, std::string Q, char const *sqlstate -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - failure{ - whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - }, + std::string const &whatarg, std::string Q, char const *sqlstate, + std::source_location loc) : + failure{whatarg, loc}, m_query{std::move(Q)}, m_sqlstate{sqlstate ? sqlstate : ""} {} @@ -117,87 +68,36 @@ PQXX_PURE std::string const &pqxx::sql_error::sqlstate() const noexcept pqxx::in_doubt_error::in_doubt_error( - std::string const &whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - failure{ - whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - } + std::string const &whatarg, std::source_location loc) : + failure{whatarg, loc} {} pqxx::transaction_rollback::transaction_rollback( - std::string const &whatarg, std::string const &q, char const sqlstate[] -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - sql_error{ - whatarg, q, sqlstate -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - } + std::string const &whatarg, std::string const &q, char const sqlstate[], + std::source_location loc) : + sql_error{whatarg, q, sqlstate, loc} {} pqxx::serialization_failure::serialization_failure( - std::string const &whatarg, std::string const &q, char const sqlstate[] -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - transaction_rollback{ - whatarg, q, sqlstate -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - } + std::string const &whatarg, std::string const &q, char const sqlstate[], + std::source_location loc) : + transaction_rollback{whatarg, q, sqlstate, loc} {} pqxx::statement_completion_unknown::statement_completion_unknown( - std::string const &whatarg, std::string const &q, char const sqlstate[] -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - transaction_rollback{ - whatarg, q, sqlstate -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - } + std::string const &whatarg, std::string const &q, char const sqlstate[], + std::source_location loc) : + transaction_rollback{whatarg, q, sqlstate, loc} {} pqxx::deadlock_detected::deadlock_detected( - std::string const &whatarg, std::string const &q, char const sqlstate[] -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - transaction_rollback{ - whatarg, q, sqlstate -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - } + std::string const &whatarg, std::string const &q, char const sqlstate[], + std::source_location loc) : + transaction_rollback{whatarg, q, sqlstate, loc} {} @@ -207,94 +107,36 @@ pqxx::internal_error::internal_error(std::string const &whatarg) : pqxx::usage_error::usage_error( - std::string const &whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - std::logic_error{whatarg} -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - location{loc} -#endif + std::string const &whatarg, std::source_location loc) : + std::logic_error{whatarg}, location{loc} {} pqxx::argument_error::argument_error( - std::string const &whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - invalid_argument{whatarg} -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - location{loc} -#endif + std::string const &whatarg, std::source_location loc) : + invalid_argument{whatarg}, location{loc} {} pqxx::conversion_error::conversion_error( - std::string const &whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - domain_error{whatarg} -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - location{loc} -#endif + std::string const &whatarg, std::source_location loc) : + domain_error{whatarg}, location{loc} {} pqxx::unexpected_null::unexpected_null( - std::string const &whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - conversion_error{ - whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - } + std::string const &whatarg, std::source_location loc) : + conversion_error{whatarg, loc} {} pqxx::conversion_overrun::conversion_overrun( - std::string const &whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - conversion_error{ - whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - } + std::string const &whatarg, std::source_location loc) : + conversion_error{whatarg, loc} {} pqxx::range_error::range_error( - std::string const &whatarg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif - ) : - out_of_range{whatarg} -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - location{loc} -#endif + std::string const &whatarg, std::source_location loc) : + out_of_range{whatarg}, location{loc} {} diff --git a/src/pipeline.cxx b/src/pipeline.cxx index 199378dcd..7eb7c38aa 100644 --- a/src/pipeline.cxx +++ b/src/pipeline.cxx @@ -137,7 +137,7 @@ void PQXX_COLD pqxx::pipeline::cancel() bool pqxx::pipeline::is_finished(pipeline::query_id q) const { - if (m_queries.find(q) == std::end(m_queries)) + if (not m_queries.contains(q)) throw std::logic_error{ internal::concat("Requested status for unknown query '", q, "'.")}; return (QueryMap::const_iterator(m_issuedrange.first) == diff --git a/src/transaction.cxx b/src/transaction.cxx index fd06586d4..a20852afd 100644 --- a/src/transaction.cxx +++ b/src/transaction.cxx @@ -78,13 +78,7 @@ void pqxx::internal::basic_transaction::do_commit() process_notice(msg); // Strip newline. It was only needed for process_notice(). msg.pop_back(); - throw in_doubt_error{ - msg -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - e.location -#endif - }; + throw in_doubt_error{msg, e.location}; } catch (std::exception const &e) { diff --git a/test/runner.cxx b/test/runner.cxx index 377271038..e66609755 100644 --- a/test/runner.cxx +++ b/test/runner.cxx @@ -14,16 +14,9 @@ namespace pqxx::test { -#if defined(PQXX_HAVE_SOURCE_LOCATION) test_failure::test_failure(std::string const &desc, std::source_location loc) : std::logic_error{desc}, m_loc{loc} {} -#else -test_failure::test_failure( - std::string const &ffile, int fline, std::string const &desc) : - std::logic_error(desc), m_file(ffile), m_line(fline) -{} -#endif test_failure::~test_failure() noexcept = default; @@ -35,52 +28,18 @@ inline void drop_table(transaction_base &t, std::string const &table) } -[[noreturn]] void check_notreached( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - char const file[], int line, -#endif - std::string desc -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif -) +[[noreturn]] void check_notreached(std::string desc, std::source_location loc) { - throw test_failure{ -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - file, line, -#endif - desc -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - }; + throw test_failure{desc, loc}; } void check( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - char const file[], int line, -#endif - bool condition, char const text[], std::string const &desc -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc -#endif -) + bool condition, char const text[], std::string const &desc, + std::source_location loc) { if (not condition) - throw test_failure{ -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - file, line, -#endif - desc + " (failed expression: " + text + ")" -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - }; + throw test_failure{desc + " (failed expression: " + text + ")", loc}; } @@ -180,8 +139,7 @@ int main(int argc, char const *argv[]) for (std::size_t idx{0}; idx < num_tests; ++idx) { auto const name{all_test_names->at(idx)}; - // C++20: Use std::map::contains(). - assert(all_tests.find(name) == std::end(all_tests)); + assert(not all_tests.contains(name)); all_tests.emplace(name, all_test_funcs->at(idx)); } @@ -210,26 +168,22 @@ int main(int argc, char const *argv[]) catch (pqxx::feature_not_supported const &e) { std::cerr << "Not testing unsupported feature: " << e.what() << '\n'; -#if defined(PQXX_HAVE_SOURCE_LOCATION) std::cerr << "("; std::cerr << e.location.file_name() << ':' << e.location.line(); if (not name.empty()) std::cerr << " in " << name; std::cerr << ")\n"; -#endif success = true; --test_count; } catch (pqxx::sql_error const &e) { std::cerr << "SQL error: " << e.what() << '\n'; -#if defined(PQXX_HAVE_SOURCE_LOCATION) std::cerr << "("; std::cerr << e.location.file_name() << ':' << e.location.line(); if (not name.empty()) std::cerr << " in " << name; std::cerr << ")\n"; -#endif std::cerr << "Query was: " << e.query() << '\n'; } catch (std::exception const &e) diff --git a/test/test_helpers.hxx b/test/test_helpers.hxx index 190a9345d..6c1e59de3 100644 --- a/test/test_helpers.hxx +++ b/test/test_helpers.hxx @@ -11,31 +11,17 @@ namespace test class test_failure : public std::logic_error { public: -#if defined(PQXX_HAVE_SOURCE_LOCATION) test_failure( std::string const &desc, std::source_location loc = std::source_location::current()); -#else - test_failure(std::string const &ffile, int fline, std::string const &desc); -#endif ~test_failure() noexcept override; -#if defined(PQXX_HAVE_SOURCE_LOCATION) constexpr char const *file() const noexcept { return m_loc.file_name(); } constexpr auto line() const noexcept { return m_loc.line(); } -#else - std::string const &file() const noexcept { return m_file; } - int line() const noexcept { return m_line; } -#endif private: -#if defined(PQXX_HAVE_SOURCE_LOCATION) std::source_location m_loc; -#else - std::string const m_file; - int m_line; -#endif }; @@ -68,63 +54,26 @@ struct registrar // Unconditional test failure. -#if defined(PQXX_HAVE_SOURCE_LOCATION) -# define PQXX_CHECK_NOTREACHED(desc) pqxx::test::check_notreached((desc)) -#else -# define PQXX_CHECK_NOTREACHED(desc) \ - pqxx::test::check_notreached(__FILE__, __LINE__, (desc)) -#endif +#define PQXX_CHECK_NOTREACHED(desc) pqxx::test::check_notreached((desc)) [[noreturn]] void check_notreached( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - char const file[], int line, -#endif - std::string desc -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc = std::source_location::current() -#endif -); + std::string desc, + std::source_location loc = std::source_location::current()); // Verify that a condition is met, similar to assert() -#if defined(PQXX_HAVE_SOURCE_LOCATION) -# define PQXX_CHECK(condition, desc) \ - pqxx::test::check((condition), #condition, (desc)) -#else -# define PQXX_CHECK(condition, desc) \ - pqxx::test::check(__FILE__, __LINE__, (condition), #condition, (desc)) -#endif +#define PQXX_CHECK(condition, desc) \ + pqxx::test::check((condition), #condition, (desc)) void check( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - char const file[], int line, -#endif - bool condition, char const text[], std::string const &desc -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc = std::source_location::current() -#endif -); + bool condition, char const text[], std::string const &desc, + std::source_location loc = std::source_location::current()); // Verify that variable has the expected value. -#if defined(PQXX_HAVE_SOURCE_LOCATION) -# define PQXX_CHECK_EQUAL(actual, expected, desc) \ - pqxx::test::check_equal((actual), #actual, (expected), #expected, (desc)) -#else -# define PQXX_CHECK_EQUAL(actual, expected, desc) \ - pqxx::test::check_equal( \ - __FILE__, __LINE__, (actual), #actual, (expected), #expected, (desc)) -#endif +#define PQXX_CHECK_EQUAL(actual, expected, desc) \ + pqxx::test::check_equal((actual), #actual, (expected), #expected, (desc)) template inline void check_equal( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - char const file[], int line, -#endif ACTUAL actual, char const actual_text[], EXPECTED expected, - char const expected_text[], std::string const &desc -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc = std::source_location::current() -#endif -) + char const expected_text[], std::string const &desc, + std::source_location loc = std::source_location::current()) { if (expected == actual) return; @@ -136,34 +85,17 @@ inline void check_equal( ", " "expected=" + to_string(expected) + ")"; -#if defined(PQXX_HAVE_SOURCE_LOCATION) throw test_failure{fulldesc, loc}; -#else - throw test_failure(file, line, fulldesc); -#endif } // Verify that two values are not equal. -#if defined(PQXX_HAVE_SOURCE_LOCATION) -# define PQXX_CHECK_NOT_EQUAL(value1, value2, desc) \ - pqxx::test::check_not_equal((value1), #value1, (value2), #value2, (desc)) -#else -# define PQXX_CHECK_NOT_EQUAL(value1, value2, desc) \ - pqxx::test::check_not_equal( \ - __FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc)) -#endif +#define PQXX_CHECK_NOT_EQUAL(value1, value2, desc) \ + pqxx::test::check_not_equal((value1), #value1, (value2), #value2, (desc)) template inline void check_not_equal( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - char const file[], int line, -#endif VALUE1 value1, char const text1[], VALUE2 value2, char const text2[], - std::string const &desc -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc = std::source_location::current() -#endif -) + std::string const &desc, + std::source_location loc = std::source_location::current()) { if (value1 != value2) return; @@ -171,43 +103,21 @@ inline void check_not_equal( ": " "both are " + to_string(value2) + ")"; -#if defined(PQXX_HAVE_SOURCE_LOCATION) throw test_failure{fulldesc, loc}; -#else - throw test_failure{file, line, fulldesc}; -#endif } -#if defined(PQXX_HAVE_SOURCE_LOCATION) // Verify that value1 is less than value2. -# define PQXX_CHECK_LESS(value1, value2, desc) \ - pqxx::test::check_less((value1), #value1, (value2), #value2, (desc)) +#define PQXX_CHECK_LESS(value1, value2, desc) \ + pqxx::test::check_less((value1), #value1, (value2), #value2, (desc)) // Verify that value1 is greater than value2. -# define PQXX_CHECK_GREATER(value2, value1, desc) \ - pqxx::test::check_less((value1), #value1, (value2), #value2, (desc)) -#else -// Verify that value1 is less than value2. -# define PQXX_CHECK_LESS(value1, value2, desc) \ - pqxx::test::check_less( \ - __FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc)) -// Verify that value1 is greater than value2. -# define PQXX_CHECK_GREATER(value2, value1, desc) \ - pqxx::test::check_less( \ - __FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc)) -#endif +#define PQXX_CHECK_GREATER(value2, value1, desc) \ + pqxx::test::check_less((value1), #value1, (value2), #value2, (desc)) template inline void check_less( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - char const file[], int line, -#endif VALUE1 value1, char const text1[], VALUE2 value2, char const text2[], - std::string const &desc -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc = std::source_location::current() -#endif -) + std::string const &desc, + std::source_location loc = std::source_location::current()) { if (value1 < value2) return; @@ -218,43 +128,21 @@ inline void check_less( ", " "\"upper\"=" + to_string(value2) + ")"; -#if defined(PQXX_HAVE_SOURCE_LOCATION) throw test_failure{fulldesc, loc}; -#else - throw test_failure(file, line, fulldesc); -#endif } -#if defined(PQXX_HAVE_SOURCE_LOCATION) -// Verify that value1 is less than or equal to value2. -# define PQXX_CHECK_LESS_EQUAL(value1, value2, desc) \ - pqxx::test::check_less_equal((value1), #value1, (value2), #value2, (desc)) -// Verify that value1 is greater than or equal to value2. -# define PQXX_CHECK_GREATER_EQUAL(value2, value1, desc) \ - pqxx::test::check_less_equal((value1), #value1, (value2), #value2, (desc)) -#else // Verify that value1 is less than or equal to value2. -# define PQXX_CHECK_LESS_EQUAL(value1, value2, desc) \ - pqxx::test::check_less_equal( \ - __FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc)) +#define PQXX_CHECK_LESS_EQUAL(value1, value2, desc) \ + pqxx::test::check_less_equal((value1), #value1, (value2), #value2, (desc)) // Verify that value1 is greater than or equal to value2. -# define PQXX_CHECK_GREATER_EQUAL(value2, value1, desc) \ - pqxx::test::check_less_equal( \ - __FILE__, __LINE__, (value1), #value1, (value2), #value2, (desc)) -#endif +#define PQXX_CHECK_GREATER_EQUAL(value2, value1, desc) \ + pqxx::test::check_less_equal((value1), #value1, (value2), #value2, (desc)) template inline void check_less_equal( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - char const file[], int line, -#endif VALUE1 value1, char const text1[], VALUE2 value2, char const text2[], - std::string const &desc -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc = std::source_location::current() -#endif -) + std::string const &desc, + std::source_location loc = std::source_location::current()) { if (value1 <= value2) return; @@ -265,11 +153,7 @@ inline void check_less_equal( ", " "\"upper\"=" + to_string(value2) + ")"; -#if defined(PQXX_HAVE_SOURCE_LOCATION) throw test_failure{fulldesc, loc}; -#else - throw test_failure(file, line, fulldesc); -#endif } @@ -361,28 +245,14 @@ inline void end_of_statement() {} } \ pqxx::test::internal::end_of_statement() -#if defined(PQXX_HAVE_SOURCE_LOCATION) -# define PQXX_CHECK_BOUNDS(value, lower, upper, desc) \ - pqxx::test::check_bounds( \ - (value), #value, (lower), #lower, (upper), #upper, (desc)) -#else -# define PQXX_CHECK_BOUNDS(value, lower, upper, desc) \ - pqxx::test::check_bounds( \ - __FILE__, __LINE__, (value), #value, (lower), #lower, (upper), #upper, \ - (desc)) -#endif +#define PQXX_CHECK_BOUNDS(value, lower, upper, desc) \ + pqxx::test::check_bounds( \ + (value), #value, (lower), #lower, (upper), #upper, (desc)) template inline void check_bounds( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - char const file[], int line, -#endif VALUE value, char const text[], LOWER lower, char const lower_text[], - UPPER upper, char const upper_text[], std::string const &desc -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - std::source_location loc = std::source_location::current() -#endif -) + UPPER upper, char const upper_text[], std::string const &desc, + std::source_location loc = std::source_location::current()) { std::string const range_check = std::string{lower_text} + " < " + upper_text, lower_check = @@ -390,38 +260,14 @@ inline void check_bounds( upper_check = std::string{text} + " < " + upper_text; pqxx::test::check( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - file, line, -#endif lower < upper, range_check.c_str(), - desc + " (acceptable range is empty; value was " + text + ")" -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - ); + desc + " (acceptable range is empty; value was " + text + ")", loc); pqxx::test::check( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - file, line, -#endif not(value < lower), lower_check.c_str(), - desc + " (" + text + " is below lower bound " + lower_text + ")" -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - ); + desc + " (" + text + " is below lower bound " + lower_text + ")", loc); pqxx::test::check( -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - file, line, -#endif value < upper, upper_check.c_str(), - desc + " (" + text + " is not below upper bound " + upper_text + ")" -#if defined(PQXX_HAVE_SOURCE_LOCATION) - , - loc -#endif - ); + desc + " (" + text + " is not below upper bound " + upper_text + ")", loc); } diff --git a/test/test_stream_from.cxx b/test/test_stream_from.cxx index 534c20137..25c173186 100644 --- a/test/test_stream_from.cxx +++ b/test/test_stream_from.cxx @@ -287,8 +287,8 @@ void test_stream_from_does_iteration() PQXX_CHECK_EQUAL(i, 2, "Wrong number of iterations."); PQXX_CHECK_EQUAL( std::size(strings), 2u, "Wrong number of strings retrieved."); - PQXX_CHECK(strings.find("foo") != std::end(strings), "Missing key."); - PQXX_CHECK(strings.find("bar") != std::end(strings), "Missing key."); + PQXX_CHECK(strings.contains("foo"), "Missing key."); + PQXX_CHECK(strings.contains("bar"), "Missing key."); } diff --git a/test/test_test_helpers.cxx b/test/test_test_helpers.cxx index cc84b536a..7acac6138 100644 --- a/test/test_test_helpers.cxx +++ b/test/test_test_helpers.cxx @@ -19,11 +19,7 @@ void test_check_notreached() // This is what we expect. } if (not failed) - throw pqxx::test::test_failure{ -#if !defined(PQXX_HAVE_SOURCE_LOCATION) - __FILE__, __LINE__, -#endif - "PQXX_CHECK_NOTREACHED is broken."}; + throw pqxx::test::test_failure{"PQXX_CHECK_NOTREACHED is broken."}; } @@ -54,15 +50,9 @@ void test_check_throws_exception() "PQXX_CHECK_THROWS_EXCEPTION did not catch std::exception."); // ...or any exception type derived from it. -#if defined(PQXX_HAVE_SOURCE_LOCATION) PQXX_CHECK_THROWS_EXCEPTION( throw pqxx::test::test_failure{"(expected)"}, "PQXX_CHECK_THROWS_EXCEPTION() failed to catch expected exception."); -#else - PQXX_CHECK_THROWS_EXCEPTION( - throw(pqxx::test::test_failure{__FILE__, __LINE__, "(expected)"}), - "PQXX_CHECK_THROWS_EXCEPTION() failed to catch expected exception."); -#endif // Any other type is an error. bool failed{true}; @@ -107,16 +97,9 @@ void test_check_throws_exception() // Test PQXX_CHECK_THROWS. void test_check_throws() { -#if defined(PQXX_HAVE_SOURCE_LOCATION) PQXX_CHECK_THROWS( throw pqxx::test::test_failure{"(expected)"}, pqxx::test::test_failure, "PQXX_CHECK_THROWS() failed to catch expected exception."); -#else - PQXX_CHECK_THROWS( - throw pqxx::test::test_failure(__FILE__, __LINE__, "(expected)"), - pqxx::test::test_failure, - "PQXX_CHECK_THROWS() failed to catch expected exception."); -#endif // Even if it's not std::exception-derived. PQXX_CHECK_THROWS(throw 1, int, "(expected)"); From a24c88a0da2df02d01b29492988427c15bb71187 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Mon, 27 Jan 2025 23:12:40 +0100 Subject: [PATCH 62/64] Assume support for source location. (#943) * Assume support for `std::source_location`. This is a C++20 feature, and it seems to be widely implemented. Next step of course is to pass the source location in more places, so that exceptions will be properly helpful in pinpointing where their errors happened. Ideally an error will tell you the location of the original call into libpqxx that led to the problem. * Use `std::map::contains()`. --- include/pqxx/separated_list.hxx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/pqxx/separated_list.hxx b/include/pqxx/separated_list.hxx index 8e4d659eb..3a719a797 100644 --- a/include/pqxx/separated_list.hxx +++ b/include/pqxx/separated_list.hxx @@ -22,8 +22,7 @@ // C++20: Simplify using std::ranges::range. // C++20: Optimise buffer allocation using random_access_range/iterator. -// C++23: Use std::join_with(). -// TODO: Or just use std formatting? +// C++23: Use std::ranges::views::join_with()? // TODO: Can we pass separators at compile time? namespace pqxx { From f887ca58e4a6f571ac523a564dff92a3e284f982 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Thu, 30 Jan 2025 16:51:25 +0100 Subject: [PATCH 63/64] Drop `test/unit` from Doxyfile. (#946) --- doc/Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 0c0b3d460..fe8af3e58 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -103,7 +103,7 @@ RECURSIVE = YES # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = ../test ../test/unit +EXAMPLE_PATH = ../test # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and From 7e1434e3edf192b8f3c3c8c2a488231a6d400c31 Mon Sep 17 00:00:00 2001 From: Jeroen Vermeulen Date: Sat, 15 Feb 2025 13:56:13 +0100 Subject: [PATCH 64/64] Pass `std::source_location` into libpqxx functions. (#947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'm trying to ensure that every libpqxx function that may throw an exception has a `source_location` parameter, and that it passes this to any functions it calls that may throw. The end result would ideally be that any exception thrown by libpqxx would include the location of the original call into libpqxx. (Unless you pass your own choice of source location, of course). In practice, this ideal is not quite attainable... * Parameter packs make it hard — you can't just make a function accept any number of arguments of any type, and then add a `source_location` parameter _after_ that. * With overloaded operators it's completely impossible — there's no way of adding parameters to a function with a fixed, language-dictated signature. * Destructors take no arguments at all, so they're out as well. * Callbacks and string conversions are a problem as well, because their existing definitions don't include `source_location`. I do have a plan for that last item though. I can probably update the signatures and use compile-time introspection (probably using function concepts) to detect whether the given user-defined function supports the old API or the new API. That's a separate job though. Oh, and because I don't want to grow all my function signatures with an additional tedious `std::source_location location = std::source_location::current()` etc, I created a type alias `sl`. Hate how cryptic this makes the functions, but I really need these to be brief. --- NEWS | 3 + config/Makefile.in | 2 +- include/pqxx/array.hxx | 169 +++++++----- include/pqxx/blob.hxx | 73 ++--- include/pqxx/composite.hxx | 35 ++- include/pqxx/connection.hxx | 184 ++++++++----- include/pqxx/cursor.hxx | 49 ++-- include/pqxx/except.hxx | 161 ++++------- include/pqxx/field.hxx | 47 ++-- include/pqxx/internal/array-composite.hxx | 77 +++--- include/pqxx/internal/conversions.hxx | 9 +- include/pqxx/internal/encoding_group.hxx | 6 +- include/pqxx/internal/encodings.hxx | 164 +++++------ .../connection-notification_receiver.hxx | 4 +- .../internal/gates/connection-pipeline.hxx | 4 +- .../internal/gates/connection-sql_cursor.hxx | 2 +- .../internal/gates/connection-stream_to.hxx | 5 +- .../internal/gates/connection-transaction.hxx | 26 +- .../gates/icursorstream-icursor_iterator.hxx | 4 +- .../pqxx/internal/gates/result-creation.hxx | 6 +- include/pqxx/internal/result_iter.hxx | 30 +- include/pqxx/internal/result_iterator.hxx | 10 +- include/pqxx/internal/sql_cursor.hxx | 30 +- include/pqxx/internal/stream_iterator.hxx | 21 +- include/pqxx/internal/stream_query.hxx | 20 +- include/pqxx/internal/stream_query_impl.hxx | 40 +-- include/pqxx/internal/wait.hxx | 5 +- include/pqxx/nontransaction.hxx | 4 +- include/pqxx/params.hxx | 12 +- include/pqxx/pipeline.hxx | 45 +-- include/pqxx/range.hxx | 52 ++-- include/pqxx/result.hxx | 95 ++++--- include/pqxx/robusttransaction.hxx | 15 +- include/pqxx/row.hxx | 70 ++--- include/pqxx/stream_from.hxx | 22 +- include/pqxx/stream_to.hxx | 54 ++-- include/pqxx/subtransaction.hxx | 14 +- include/pqxx/time.hxx | 7 +- include/pqxx/transaction.hxx | 21 +- include/pqxx/transaction_base.hxx | 256 ++++++++++++------ include/pqxx/transactor.hxx | 43 ++- include/pqxx/types.hxx | 4 + include/pqxx/util.hxx | 20 +- src/array.cxx | 63 +++-- src/blob.cxx | 163 ++++++----- src/connection.cxx | 247 +++++++++-------- src/cursor.cxx | 55 ++-- src/encodings.cxx | 6 +- src/except.cxx | 49 ++-- src/field.cxx | 18 +- src/notification.cxx | 4 +- src/params.cxx | 7 +- src/pipeline.cxx | 116 ++++---- src/result.cxx | 218 ++++++++------- src/robusttransaction.cxx | 68 ++--- src/row.cxx | 27 +- src/sql_cursor.cxx | 40 +-- src/strconv.cxx | 10 +- src/stream_from.cxx | 38 ++- src/stream_to.cxx | 42 +-- src/subtransaction.cxx | 23 +- src/time.cxx | 40 +-- src/transaction.cxx | 19 +- src/transaction_base.cxx | 82 +++--- src/util.cxx | 18 +- src/wait.cxx | 11 +- test/runner.cxx | 12 +- test/test07.cxx | 4 +- test/test_array.cxx | 24 ++ test/test_encodings.cxx | 23 +- test/test_helpers.hxx | 200 +++++++------- test/test_sql_cursor.cxx | 53 ++-- test/test_stream_from.cxx | 8 +- test/test_stream_to.cxx | 8 +- test/test_test_helpers.cxx | 12 +- test/test_types.hxx | 4 +- 76 files changed, 2065 insertions(+), 1567 deletions(-) diff --git a/NEWS b/NEWS index aeb56dce7..710d5161b 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ - Conversion from string to `char const *` is no longer allowed. - Binary data can be any `std::contiguous_range` of `std::byte`. (#925) - Generic `quote()` takes `always_null` into account. + - The libpqxx exceptions now have a `std::source_location`. + - More getters are now `noexcept`. - Retired `binarystring` and its headers. Use `blob` instead. - Retired `connection_base` type alias. Use `connection`. - Retired `pqxx::encrypt_password()`. Use the ones in `pqxx::connection`. @@ -21,6 +23,7 @@ - Assume compiler supports `std::filesystem::path`. - Assume compiler supports `[[likely]]` & `[[unlikely]]`. - Assume compiler supports `ssize()`. + - Assume compiler supports `std::source_location`. - Assume compiler supports ISO-646 without needing `` header. 7.10.1 - Fix string conversion buffer budget for arrays containing nulls. (#921) diff --git a/config/Makefile.in b/config/Makefile.in index 51586bda2..9b1454986 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -124,7 +124,7 @@ am__can_run_installinfo = \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \ - config.sub install-sh ltmain.sh missing mkinstalldirs + config.sub depcomp install-sh ltmain.sh missing mkinstalldirs DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ diff --git a/include/pqxx/array.hxx b/include/pqxx/array.hxx index 31b31b89c..b4667784b 100644 --- a/include/pqxx/array.hxx +++ b/include/pqxx/array.hxx @@ -65,8 +65,8 @@ public: * @throws pqxx::unexpected_null if the array contains a null value, and the * `ELEMENT` type does not support null values. */ - array(std::string_view data, connection const &cx) : - array{data, pqxx::internal::enc_group(cx.encoding_id())} + array(std::string_view data, connection const &cx, sl loc = sl::current()) : + array{data, pqxx::internal::enc_group(cx.encoding_id(loc), loc), loc} {} /// How many dimensions does this array have? @@ -84,7 +84,8 @@ public: return m_extents; } - template ELEMENT const &at(INDEX... index) const + // TODO: How can we pass std::source_location here? + template ELEMENT const &at(INDEX... index) const { static_assert(sizeof...(index) == DIMENSIONS); check_bounds(index...); @@ -100,7 +101,8 @@ public: * better. In older versions of C++ it will work only with * single-dimensional arrays. */ - template ELEMENT const &operator[](INDEX... index) const + template + ELEMENT const &operator[](INDEX... index) const { static_assert(sizeof...(index) == DIMENSIONS); return m_elts[locate(index...)]; @@ -167,13 +169,15 @@ private: * walking through the entire array sequentially, and identifying all the * character boundaries. The main parsing routine detects that one. */ - void check_dims(std::string_view data) + void check_dims(std::string_view data, sl loc = sl::current()) { auto sz{std::size(data)}; if (sz < DIMENSIONS * 2) - throw conversion_error{pqxx::internal::concat( - "Trying to parse a ", DIMENSIONS, "-dimensional array out of '", data, - "'.")}; + throw conversion_error{ + pqxx::internal::concat( + "Trying to parse a ", DIMENSIONS, "-dimensional array out of '", + data, "'."), + loc}; // Making some assumptions here: // * The array holds no extraneous whitespace. @@ -185,43 +189,48 @@ private: // of DIMENSIONS bytes with the ASCII value for '}'. if (data[0] != '{') - throw conversion_error{"Malformed array: does not start with '{'."}; + throw conversion_error{"Malformed array: does not start with '{'.", loc}; for (std::size_t i{0}; i < DIMENSIONS; ++i) if (data[i] != '{') - throw conversion_error{pqxx::internal::concat( - "Expecting ", DIMENSIONS, "-dimensional array, but found ", i, ".")}; + throw conversion_error{ + pqxx::internal::concat( + "Expecting ", DIMENSIONS, "-dimensional array, but found ", i, + "."), + loc}; if (data[DIMENSIONS] == '{') - throw conversion_error{pqxx::internal::concat( - "Tried to parse ", DIMENSIONS, - "-dimensional array from array data that has more dimensions.")}; + throw conversion_error{ + pqxx::internal::concat( + "Tried to parse ", DIMENSIONS, + "-dimensional array from array data that has more dimensions."), + loc}; for (std::size_t i{0}; i < DIMENSIONS; ++i) if (data[sz - 1 - i] != '}') throw conversion_error{ - "Malformed array: does not end in the right number of '}'."}; + "Malformed array: does not end in the right number of '}'.", loc}; } // Allow fields to construct arrays passing the encoding group. // Couldn't make this work through a call gate, thanks to the templating. friend class ::pqxx::field; - array(std::string_view data, pqxx::internal::encoding_group enc) + array(std::string_view data, pqxx::internal::encoding_group enc, sl loc) { using group = pqxx::internal::encoding_group; switch (enc) { - case group::MONOBYTE: parse(data); break; - case group::BIG5: parse(data); break; - case group::EUC_CN: parse(data); break; - case group::EUC_JP: parse(data); break; - case group::EUC_KR: parse(data); break; - case group::EUC_TW: parse(data); break; - case group::GB18030: parse(data); break; - case group::GBK: parse(data); break; - case group::JOHAB: parse(data); break; - case group::MULE_INTERNAL: parse(data); break; - case group::SJIS: parse(data); break; - case group::UHC: parse(data); break; - case group::UTF8: parse(data); break; + case group::MONOBYTE: parse(data, loc); break; + case group::BIG5: parse(data, loc); break; + case group::EUC_CN: parse(data, loc); break; + case group::EUC_JP: parse(data, loc); break; + case group::EUC_KR: parse(data, loc); break; + case group::EUC_TW: parse(data, loc); break; + case group::GB18030: parse(data, loc); break; + case group::GBK: parse(data, loc); break; + case group::JOHAB: parse(data, loc); break; + case group::MULE_INTERNAL: parse(data, loc); break; + case group::SJIS: parse(data, loc); break; + case group::UHC: parse(data, loc); break; + case group::UTF8: parse(data, loc); break; default: PQXX_UNREACHABLE; break; } } @@ -230,7 +239,8 @@ private: /** Check for a trailing separator, detect any syntax errors at this somewhat * complicated point, and return the offset where parsing should continue. */ - std::size_t parse_field_end(std::string_view data, std::size_t here) const + std::size_t + parse_field_end(std::string_view data, std::size_t here, sl loc) const { auto const sz{std::size(data)}; if (here < sz) @@ -239,21 +249,24 @@ private: case SEPARATOR: ++here; if (here >= sz) - throw conversion_error{"Array looks truncated."}; + throw conversion_error{"Array looks truncated.", loc}; switch (data[here]) { case SEPARATOR: - throw conversion_error{"Array contains double separator."}; - case '}': throw conversion_error{"Array contains trailing separator."}; + throw conversion_error{"Array contains double separator.", loc}; + case '}': + throw conversion_error{"Array contains trailing separator.", loc}; default: break; } break; case '}': break; default: - throw conversion_error{pqxx::internal::concat( - "Unexpected character in array: ", - static_cast(static_cast(data[here])), - " where separator or closing brace expected.")}; + throw conversion_error{ + pqxx::internal::concat( + "Unexpected character in array: ", + static_cast(static_cast(data[here])), + " where separator or closing brace expected."), + loc}; } return here; } @@ -278,7 +291,7 @@ private: } template - void parse(std::string_view data) + void parse(std::string_view data, sl loc) { static_assert(DIMENSIONS > 0u, "Can't create a zero-dimensional array."); auto const sz{std::size(data)}; @@ -322,7 +335,8 @@ private: if (know_extents_from != DIMENSIONS) throw conversion_error{ "Array text representation closed and reopened its outside " - "brace pair."}; + "brace pair.", + loc}; assert(here == 0); PQXX_ASSUME(here == 0); } @@ -330,7 +344,7 @@ private: { if (dim >= (DIMENSIONS - 1)) throw conversion_error{ - "Array seems to have inconsistent number of dimensions."}; + "Array seems to have inconsistent number of dimensions.", loc}; ++extents[dim]; } // (Rolls over to zero if we're coming from the outer dimension.) @@ -341,7 +355,7 @@ private: else if (data[here] == '}') { if (dim == outer) - throw conversion_error{"Array has spurious '}'."}; + throw conversion_error{"Array has spurious '}'.", loc}; if (dim < know_extents_from) { // We just finished parsing our first row in this dimension. @@ -352,13 +366,14 @@ private: else { if (extents[dim] != m_extents[dim]) - throw conversion_error{"Rows in array have inconsistent sizes."}; + throw conversion_error{ + "Rows in array have inconsistent sizes.", loc}; } // Bump back down to the next-lower dimension. Which may be the outer // dimension, through underflow. --dim; ++here; - here = parse_field_end(data, here); + here = parse_field_end(data, here, loc); } else { @@ -366,14 +381,16 @@ private: // "inner" dimension. if (dim != DIMENSIONS - 1) throw conversion_error{ - "Malformed array: found element where sub-array was expected."}; + "Malformed array: found element where sub-array was expected.", + loc}; assert(dim != outer); ++extents[dim]; std::size_t end; switch (data[here]) { - case '\0': throw conversion_error{"Unexpected zero byte in array."}; - case ',': throw conversion_error{"Array contains empty field."}; + case '\0': + throw conversion_error{"Unexpected zero byte in array.", loc}; + case ',': throw conversion_error{"Array contains empty field.", loc}; case '"': { // Double-quoted string. We parse it into a buffer before parsing // the resulting string as an element. This seems wasteful: the @@ -385,11 +402,11 @@ private: // So in practice, this optimisation would only apply if the only // special characters in the string were commas. end = pqxx::internal::scan_double_quoted_string( - std::data(data), std::size(data), here); + std::data(data), std::size(data), here, loc); // TODO: scan_double_quoted_string() with reusable buffer. std::string const buf{ pqxx::internal::parse_double_quoted_string( - std::data(data), end, here)}; + std::data(data), end, here, loc)}; m_elts.emplace_back(from_string(buf)); } break; @@ -398,7 +415,7 @@ private: // escaping or encoding, so we don't need to parse it into a // buffer. We can just read it as a string_view. end = pqxx::internal::scan_unquoted_string( - std::data(data), std::size(data), here); + std::data(data), std::size(data), here, loc); std::string_view const field{ std::string_view{std::data(data) + here, end - here}}; if (field == "NULL") @@ -406,10 +423,12 @@ private: if constexpr (nullness::has_null) m_elts.emplace_back(nullness::null()); else - throw unexpected_null{pqxx::internal::concat( - "Array contains a null ", type_name, - ". Consider making it an array of std::optional<", - type_name, "> instead.")}; + throw unexpected_null{ + pqxx::internal::concat( + "Array contains a null ", type_name, + ". Consider making it an array of std::optional<", + type_name, "> instead."), + loc}; } else m_elts.emplace_back(from_string(field)); @@ -417,12 +436,12 @@ private: } here = end; PQXX_ASSUME(here <= sz); - here = parse_field_end(data, here); + here = parse_field_end(data, here, loc); } } if (dim != outer) - throw conversion_error{"Malformed array; may be truncated."}; + throw conversion_error{"Malformed array; may be truncated.", loc}; assert(know_extents_from == 0); PQXX_ASSUME(know_extents_from == 0); @@ -452,7 +471,9 @@ private: template constexpr std::size_t add_index(OUTER outer, INDEX... indexes) const noexcept { - std::size_t const first{check_cast(outer, "array index"sv)}; + sl loc{sl::current()}; + std::size_t const first{ + check_cast(outer, "array index"sv, loc)}; if constexpr (sizeof...(indexes) == 0) { return first; @@ -467,13 +488,16 @@ private: } } + // TODO: How can we pass std::source_location here? /// Check that indexes are within bounds. /** @throw pqxx::range_error if not. */ - template + template constexpr void check_bounds(OUTER outer, INDEX... indexes) const { - std::size_t const first{check_cast(outer, "array index"sv)}; + sl loc{sl::current()}; + std::size_t const first{ + check_cast(outer, "array index"sv, loc)}; static_assert(sizeof...(indexes) < DIMENSIONS); // (Offset by 1 here because the outer dimension is not in there.) constexpr auto dimension{DIMENSIONS - (sizeof...(indexes) + 1)}; @@ -550,6 +574,7 @@ public: * remains valid. Once all `result` objects referring to that data have been * destroyed, the parser will no longer refer to valid memory. */ + [[deprecated("Use pqxx::array instead.")]] explicit array_parser( std::string_view input, internal::encoding_group = internal::encoding_group::MONOBYTE); @@ -561,7 +586,10 @@ public: * Call this until the @ref array_parser::juncture it returns is * @ref juncture::done. */ - std::pair get_next() { return (this->*m_impl)(); } + std::pair get_next(sl loc = sl::current()) + { + return (this->*m_impl)(loc); + } private: std::string_view m_input; @@ -575,33 +603,36 @@ private: * compiler to inline the parsing of each text encoding, which happens in * very hot loops. */ - using implementation = std::pair (array_parser::*)(); + using implementation = + std::pair (array_parser::*)(sl); /// Pick the `implementation` for `enc`. static implementation - specialize_for_encoding(pqxx::internal::encoding_group enc); + specialize_for_encoding(pqxx::internal::encoding_group enc, sl loc); /// Our implementation of `parse_array_step`, specialised for our encoding. implementation m_impl; /// Perform one step of array parsing. template - std::pair parse_array_step(); + std::pair parse_array_step(sl loc); template - std::string::size_type scan_double_quoted_string() const; + std::string::size_type scan_double_quoted_string(sl loc) const; template - std::string parse_double_quoted_string(std::string::size_type end) const; + std::string + parse_double_quoted_string(std::string::size_type end, sl loc) const; template - std::string::size_type scan_unquoted_string() const; + std::string::size_type scan_unquoted_string(sl loc) const; template - std::string_view parse_unquoted_string(std::string::size_type end) const; + std::string_view + parse_unquoted_string(std::string::size_type end, sl loc) const; template - std::string::size_type scan_glyph(std::string::size_type pos) const; + std::string::size_type scan_glyph(std::string::size_type pos, sl loc) const; template - std::string::size_type - scan_glyph(std::string::size_type pos, std::string::size_type end) const; + std::string::size_type scan_glyph( + std::string::size_type pos, std::string::size_type end, sl loc) const; }; } // namespace pqxx #endif diff --git a/include/pqxx/blob.hxx b/include/pqxx/blob.hxx index dcd504934..7a0833230 100644 --- a/include/pqxx/blob.hxx +++ b/include/pqxx/blob.hxx @@ -48,17 +48,18 @@ public: * 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); + [[nodiscard]] static oid + create(dbtransaction &, oid = 0, sl = sl::current()); /// Delete a large object, or fail if it does not exist. - static void remove(dbtransaction &, oid); + static void remove(dbtransaction &, oid, sl = sl::current()); /// Open blob for reading. Any attempt to write to it will fail. - [[nodiscard]] static blob open_r(dbtransaction &, oid); + [[nodiscard]] static blob open_r(dbtransaction &, oid, sl = sl::current()); // Open blob for writing. Any attempt to read from it will fail. - [[nodiscard]] static blob open_w(dbtransaction &, oid); + [[nodiscard]] static blob open_w(dbtransaction &, oid, sl = sl::current()); // Open blob for reading and/or writing. - [[nodiscard]] static blob open_rw(dbtransaction &, oid); + [[nodiscard]] static blob open_rw(dbtransaction &, oid, sl = sl::current()); /// You can default-construct a blob, but it won't do anything useful. /** Most operations on a default-constructed blob will throw @ref @@ -94,7 +95,7 @@ public: * @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(bytes &buf, std::size_t size); + std::size_t read(bytes &buf, std::size_t size, sl = sl::current()); /// Read up to `std::size(buf)` bytes from the object. /** Retrieves bytes from the blob, at the current position, until `buf` is @@ -103,9 +104,10 @@ public: * Returns the filled portion of `buf`. This may be empty. */ template - writable_bytes_view read(std::span buf) + writable_bytes_view + read(std::span buf, sl loc = sl::current()) { - return buf.subspan(0, raw_read(std::data(buf), std::size(buf))); + return buf.subspan(0, raw_read(std::data(buf), std::size(buf), loc)); } /// Read up to `std::size(buf)` bytes from the object. @@ -114,9 +116,10 @@ public: * * Returns the filled portion of `buf`. This may be empty. */ - template writable_bytes_view read(DATA &buf) + template + writable_bytes_view read(DATA &buf, sl loc = sl::current()) { - return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; + return {std::data(buf), raw_read(std::data(buf), std::size(buf), loc)}; } /// Write `data` to large object, at the current position. @@ -138,9 +141,9 @@ public: * time. If you need to write more, try making repeated calls to * @ref append_from_buf. */ - template void write(DATA const &data) + template void write(DATA const &data, sl loc = sl::current()) { - return raw_write(binary_cast(data)); + return raw_write(binary_cast(data), loc); } /// Resize large object to `size` bytes. @@ -150,72 +153,78 @@ public: * 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); + void resize(std::int64_t size, sl = sl::current()); /// Return the current reading/writing position in the large object. - [[nodiscard]] std::int64_t tell() const; + [[nodiscard]] std::int64_t tell(sl = sl::current()) 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); + std::int64_t seek_abs(std::int64_t offset = 0, sl = sl::current()); /// 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); + std::int64_t seek_rel(std::int64_t offset = 0, sl = sl::current()); /// 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); + std::int64_t seek_end(std::int64_t offset = 0, sl = sl::current()); /// 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, bytes_view data, oid id = 0); + static oid + from_buf(dbtransaction &tx, bytes_view data, oid id = 0, sl = sl::current()); /// 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. */ template - static oid from_buf(dbtransaction &tx, DATA data, oid id = 0) + static oid + from_buf(dbtransaction &tx, DATA data, oid id = 0, sl loc = sl::current()) { - return from_buf(tx, binary_cast(data), id); + return from_buf(tx, binary_cast(data), id, loc); } /// Append `data` to binary large object. /** The underlying protocol only supports appending blocks up to 2 GB. */ - static void append_from_buf(dbtransaction &tx, bytes_view data, oid id); + static void append_from_buf( + dbtransaction &tx, bytes_view data, oid id, sl = sl::current()); /// Append `data` to binary large object. /** The underlying protocol only supports appending blocks up to 2 GB. */ template - static void append_from_buf(dbtransaction &tx, DATA data, oid id) + static void + append_from_buf(dbtransaction &tx, DATA data, oid id, sl loc = sl::current()) { - append_from_buf(tx, binary_cast(data), id); + append_from_buf(tx, binary_cast(data), id, loc); } /// Read client-side file and store it server-side as a binary large object. - [[nodiscard]] static oid from_file(dbtransaction &, zview path); + [[nodiscard]] static oid + from_file(dbtransaction &, zview path, sl = sl::current()); /// 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 &, zview path, oid); + static oid from_file(dbtransaction &, zview path, oid, sl = sl::current()); // XXX: Can we build a generic version of this? /// 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, bytes &, std::size_t max_size); + static void to_buf( + dbtransaction &, oid, bytes &, std::size_t max_size, sl = sl::current()); // XXX: Can we build a generic version of this? /// Read part of the binary large object with `id`, and append it to `buf`. @@ -227,10 +236,10 @@ public: */ static std::size_t append_to_buf( dbtransaction &tx, oid id, std::int64_t offset, bytes &buf, - std::size_t append_max); + std::size_t append_max, sl = sl::current()); /// Write a binary large object's contents to a client-side file. - static void to_file(dbtransaction &, oid, zview path); + static void to_file(dbtransaction &, oid, zview path, sl = sl::current()); /// Close this blob. /** This does not delete the blob from the database; it only terminates your @@ -248,7 +257,7 @@ public: private: PQXX_PRIVATE blob(connection &cx, int fd) noexcept : m_conn{&cx}, m_fd{fd} {} - static PQXX_PRIVATE blob open_internal(dbtransaction &, oid, int); + static PQXX_PRIVATE blob open_internal(dbtransaction &, oid, int, sl); static PQXX_PRIVATE pqxx::internal::pq::PGconn * raw_conn(pqxx::connection *) noexcept; static PQXX_PRIVATE pqxx::internal::pq::PGconn * @@ -259,9 +268,9 @@ private: 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(bytes_view); + PQXX_PRIVATE std::int64_t seek(std::int64_t offset, int whence, sl); + std::size_t raw_read(std::byte buf[], std::size_t size, sl); + void raw_write(bytes_view, sl); connection *m_conn = nullptr; int m_fd = -1; diff --git a/include/pqxx/composite.hxx b/include/pqxx/composite.hxx index 73702a23f..821f2e61f 100644 --- a/include/pqxx/composite.hxx +++ b/include/pqxx/composite.hxx @@ -11,6 +11,7 @@ namespace pqxx { +// TODO: How can we pass std::source_location here? /// Parse a string representation of a value of a composite type. /** @warning This code is still experimental. Use with care. * @@ -36,36 +37,44 @@ inline void parse_composite( pqxx::internal::encoding_group enc, std::string_view text, T &...fields) { static_assert(sizeof...(fields) > 0); + // TODO: Turn this into a parameter. + auto const loc{sl::current()}; - auto const scan{pqxx::internal::get_glyph_scanner(enc)}; + auto const scan{pqxx::internal::get_glyph_scanner(enc, loc)}; 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."}; + throw conversion_error{ + "Cannot parse composite value from empty string.", loc}; - std::size_t here{0}, next{scan(data, size, here)}; + std::size_t here{0}, next{scan(data, size, here, loc)}; if (next != 1 or data[here] != '(') throw conversion_error{ - internal::concat("Invalid composite value string: ", text)}; + internal::concat("Invalid composite value string: ", text), loc}; here = next; // TODO: Reuse parse_composite_field specialisation across calls. constexpr auto num_fields{sizeof...(fields)}; std::size_t index{0}; - (pqxx::internal::specialize_parse_composite_field(enc)( - index, text, here, fields, num_fields - 1), + (pqxx::internal::specialize_parse_composite_field(enc, loc)( + index, text, here, fields, num_fields - 1, loc), ...); if (here != std::size(text)) - throw conversion_error{internal::concat( - "Composite value did not end at the closing parenthesis: '", text, - "'.")}; + throw conversion_error{ + internal::concat( + "Composite value did not end at the closing parenthesis: '", text, + "'."), + loc}; if (text[here - 1] != ')') - throw conversion_error{internal::concat( - "Composive value did not end in parenthesis: '", text, "'")}; + throw conversion_error{ + internal::concat( + "Composite value did not end in parenthesis: '", text, "'"), + loc}; } +// TODO: How can we pass std::source_location here? /// Parse a string representation of a value of a composite type. /** @warning This version only works for UTF-8 and single-byte encodings. * @@ -113,6 +122,7 @@ composite_size_buffer(T const &...fields) noexcept } +// TODO: How can we pass std::source_location here? /// Render a series of values as a single composite SQL value. /** @warning This code is still experimental. Use with care. * @@ -122,9 +132,10 @@ composite_size_buffer(T const &...fields) noexcept template inline char *composite_into_buf(char *begin, char *end, T const &...fields) { + auto loc{sl::current()}; if (std::size_t(end - begin) < composite_size_buffer(fields...)) throw conversion_error{ - "Buffer space may not be enough to represent composite value."}; + "Buffer space may not be enough to represent composite value.", loc}; constexpr auto num_fields{sizeof...(fields)}; if constexpr (num_fields == 0) diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index bfd8a454b..35d15bbb1 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -273,17 +273,18 @@ enum class error_verbosity : int class PQXX_LIBEXPORT connection { public: - connection() : connection{""} {} + connection(sl loc = sl::current()) : connection{"", loc} {} /// Connect to a database, using `options` string. - explicit connection(char const options[]) + explicit connection(char const options[], sl loc = sl::current()) { check_version(); - init(options); + init(options, loc); } /// Connect to a database, using `options` string. - explicit connection(zview options) : connection{options.c_str()} + explicit connection(zview options, sl loc = sl::current()) : + connection{options.c_str(), loc} { // (Delegates to other constructor which calls check_version for us.) } @@ -294,7 +295,7 @@ public: * other objects may hold references to the old object which would become * invalid and might produce hard-to-diagnose bugs. */ - connection(connection &&rhs); + connection(connection &&rhs, sl = sl::current()); /// Connect to a database, passing options as a range of key/value pairs. /** There's no need to escape the parameter values. @@ -310,13 +311,15 @@ public: * `std::map`, and so on. */ template - inline connection(MAPPING const ¶ms); + inline connection(MAPPING const ¶ms, sl = sl::current()); ~connection() { + // TODO: How can we pass std::source_location? + sl loc{sl::current()}; try { - close(); + close(loc); } catch (std::exception const &) {} @@ -363,20 +366,20 @@ public: //@{ /// Name of the database to which we're connected, if any. /** Returns nullptr when not connected. */ - [[nodiscard]] char const *dbname() const; + [[nodiscard]] char const *dbname() const noexcept; /// Database user ID under which we are connected, if any. /** Returns nullptr when not connected. */ - [[nodiscard]] char const *username() const; + [[nodiscard]] char const *username() const noexcept; /// Database server address, if given. /** This may be an IP address, or a hostname, or (for a Unix domain socket) * a socket path. Returns nullptr when not connected. */ - [[nodiscard]] char const *hostname() const; + [[nodiscard]] char const *hostname() const noexcept; /// Server port number on which we are connected to the database. - [[nodiscard]] char const *port() const; + [[nodiscard]] char const *port() const noexcept; /// Process ID for backend process, or 0 if inactive. [[nodiscard]] int PQXX_PURE backendpid() const & noexcept; @@ -438,25 +441,25 @@ public: */ //@{ /// Get client-side character encoding, by name. - [[nodiscard]] std::string get_client_encoding() const; + [[nodiscard]] std::string get_client_encoding(sl loc = sl::current()) const; /// Set client-side character encoding, by name. /** * @param encoding Name of the character set encoding to use. */ - void set_client_encoding(zview encoding) & + void set_client_encoding(zview encoding, sl loc = sl::current()) & { - set_client_encoding(encoding.c_str()); + set_client_encoding(encoding.c_str(), loc); } /// Set client-side character encoding, by name. /** * @param encoding Name of the character set encoding to use. */ - void set_client_encoding(char const encoding[]) &; + void set_client_encoding(char const encoding[], sl = sl::current()) &; /// Get the connection's encoding, as a PostgreSQL-defined code. - [[nodiscard]] int encoding_id() const; + [[nodiscard]] int encoding_id(sl = sl::current()) const; //@} @@ -483,15 +486,18 @@ public: * allowed. */ template - void set_session_var(std::string_view var, TYPE const &value) & + void set_session_var( + std::string_view var, TYPE const &value, sl loc = sl::current()) & { if constexpr (nullness::has_null) { if (nullness::is_null(value)) throw variable_set_to_null{ - internal::concat("Attempted to set variable ", var, " to null.")}; + internal::concat("Attempted to set variable ", var, " to null."), + loc}; } - exec(internal::concat("SET ", quote_name(var), "=", quote(value))); + exec( + internal::concat("SET ", quote_name(var), "=", quote(value, loc)), loc); } /// Read currently applicable value of a variable. @@ -501,7 +507,7 @@ public: * @return a blank `std::optional` if the variable's value is null, or its * string value otherwise. */ - std::string get_var(std::string_view var); + std::string get_var(std::string_view var, sl loc = sl::current()); /// Read currently applicable value of a variable. /** This function executes an SQL statement, so it won't work while a @@ -510,9 +516,10 @@ public: * If there is any possibility that the variable is null, ensure that `TYPE` * can represent null values. */ - template TYPE get_var_as(std::string_view var) + template + TYPE get_var_as(std::string_view var, sl loc = sl::current()) { - return from_string(get_var(var)); + return from_string(get_var(var, loc)); } /** @@ -623,7 +630,7 @@ public: * * @return Number of notifications processed. */ - int get_notifs(); + int get_notifs(sl = sl::current()); /// Wait for a notification to come in. /** There are other events that will also cancel the wait, such as the @@ -640,7 +647,7 @@ public: * * @return Number of notifications processed. */ - int await_notification(); + int await_notification(sl = sl::current()); /// Wait for a notification to come in, or for given timeout to pass. /** There are other events that will also cancel the wait, such as the @@ -659,7 +666,8 @@ public: * * @return Number of notifications processed. */ - int await_notification(std::time_t seconds, long microseconds = 0); + int await_notification( + std::time_t seconds, long microseconds = 0, sl = sl::current()); /// A handler callback for incoming notifications on a given channel. /** Your callback must accept a @ref notification object. This object can @@ -693,7 +701,9 @@ public: * different handlers on the same channel, then the second overwrites the * first. */ - void listen(std::string_view channel, notification_handler handler = {}); + void listen( + std::string_view channel, notification_handler handler = {}, + sl = sl::current()); //@} @@ -786,16 +796,17 @@ public: * @param name unique name for the new prepared statement. * @param definition SQL statement to prepare. */ - void prepare(zview name, zview definition) & + void prepare(zview name, zview definition, sl loc = sl::current()) & { - prepare(name.c_str(), definition.c_str()); + prepare(name.c_str(), definition.c_str(), loc); } /** * @param name unique name for the new prepared statement. * @param definition SQL statement to prepare. */ - void prepare(char const name[], char const definition[]) &; + void prepare( + char const name[], char const definition[], sl loc = sl::current()) &; /// Define a nameless prepared statement. /** @@ -805,11 +816,14 @@ public: * the nameless statement being redefined unexpectedly by code somewhere * else. */ - void prepare(char const definition[]) &; - void prepare(zview definition) & { return prepare(definition.c_str()); } + void prepare(char const definition[], sl loc = sl::current()) &; + void prepare(zview definition, sl loc = sl::current()) & + { + return prepare(definition.c_str(), loc); + } /// Drop prepared statement. - void unprepare(std::string_view name); + void unprepare(std::string_view name, sl loc = sl::current()); //@} @@ -825,9 +839,10 @@ public: //@{ /// Escape string for use as SQL string literal on this connection. - [[nodiscard]] std::string esc(char const text[]) const + [[nodiscard]] std::string + esc(char const text[], sl loc = sl::current()) const { - return esc(std::string_view{text}); + return esc(std::string_view{text}, loc); } /// Escape string for use as SQL string literal, into `buffer`. @@ -843,23 +858,26 @@ public: * `buffer`. */ [[nodiscard]] std::string_view - esc(std::string_view text, std::span buffer) + esc(std::string_view text, std::span buffer, sl loc = sl::current()) { auto const size{std::size(text)}, space{std::size(buffer)}; auto const needed{2 * size + 1}; if (space < needed) - throw range_error{internal::concat( - "Not enough room to escape string of ", size, " byte(s): need ", - needed, " bytes of buffer space, but buffer size is ", space, ".")}; + throw range_error{ + internal::concat( + "Not enough room to escape string of ", size, " byte(s): need ", + needed, " bytes of buffer space, but buffer size is ", space, "."), + loc}; auto const data{buffer.data()}; - return {data, esc_to_buf(text, data)}; + return {data, esc_to_buf(text, data, loc)}; } /// Escape string for use as SQL string literal on this connection. /** @warning This is meant for text strings only. It cannot contain bytes * whose value is zero ("nul bytes"). */ - [[nodiscard]] std::string esc(std::string_view text) const; + [[nodiscard]] std::string + esc(std::string_view text, sl loc = sl::current()) const; /// Escape binary string for use as SQL string literal on this connection. /** This is identical to `esc_raw(data)`. */ @@ -930,11 +948,12 @@ public: * "bytea" escape format, used prior to PostgreSQL 9.0, is no longer * supported.) */ - [[nodiscard]] bytes unesc_bin(std::string_view text) const + [[nodiscard]] bytes + unesc_bin(std::string_view text, sl loc = sl::current()) const { bytes buf; buf.resize(pqxx::internal::size_unesc_bin(std::size(text))); - pqxx::internal::unesc_bin(text, buf.data()); + pqxx::internal::unesc_bin(text, buf.data(), loc); return buf; } @@ -991,7 +1010,7 @@ public: * Recognises nulls and represents them as SQL nulls. They get no quotes. */ template - [[nodiscard]] inline std::string quote(T const &t) const; + [[nodiscard]] inline std::string quote(T const &t, sl = sl::current()) const; // TODO: Make "into buffer" variant to eliminate a string allocation. /// Escape string for literal LIKE match. @@ -1020,8 +1039,9 @@ public: * The SQL "LIKE" operator also lets you choose your own escape character. * This is supported, but must be a single-byte character. */ - [[nodiscard]] std::string - esc_like(std::string_view text, char escape_char = '\\') const; + [[nodiscard]] std::string esc_like( + std::string_view text, char escape_char = '\\', + sl loc = sl::current()) const; //@} /// Attempt to cancel the ongoing query, if any. @@ -1029,7 +1049,7 @@ public: * in a pipeline, but it's up to you to ensure that you're not canceling the * wrong query. This may involve locking. */ - void cancel_query(); + void cancel_query(sl = sl::current()); #if defined(_WIN32) || __has_include() /// Set socket to blocking (true) or nonblocking (false). @@ -1037,7 +1057,7 @@ public: * @warning This function is available on most systems, but not necessarily * all. */ - void set_blocking(bool block) &; + void set_blocking(bool block, sl = sl::current()) &; #endif // defined(_WIN32) || __has_include() /// Set session verbosity. @@ -1100,7 +1120,7 @@ public: * Closing a connection is idempotent. Closing a connection that's already * closed does nothing. */ - void close(); + void close(sl = sl::current()); /// Seize control of a raw libpq connection. /** @warning Do not do this. Please. It's for very rare, very specific @@ -1140,14 +1160,15 @@ public: * a string, be sure that it's properly escaped and quoted. */ [[deprecated("To set session variables, use set_session_var.")]] void - set_variable(std::string_view var, std::string_view value) &; + set_variable( + std::string_view var, std::string_view value, sl loc = sl::current()) &; /// Read session variable, using SQL's `SHOW` command. /** @warning This executes an SQL query, so do not get or set variables while * a table stream or pipeline is active on the same connection. */ [[deprecated("Use get_var instead.")]] std::string - get_variable(std::string_view); + get_variable(std::string_view, sl loc = sl::current()); private: friend class connecting; @@ -1155,7 +1176,7 @@ private: { connect_nonblocking }; - connection(connect_mode, zview connection_string); + connection(connect_mode, zview connection_string, sl); /// For use by @ref seize_raw_connection. explicit connection(internal::pq::PGconn *raw_conn); @@ -1166,20 +1187,27 @@ private: * * Throws an exception if polling indicates that the connection has failed. */ - std::pair poll_connect(); + std::pair poll_connect(sl); // Initialise based on connection string. - void init(char const options[]); + void init(char const options[], sl); // Initialise based on parameter names and values. - void init(char const *params[], char const *values[]); + void init(char const *params[], char const *values[], sl); void set_up_notice_handlers(); - void complete_init(); + void complete_init(sl); result make_result( internal::pq::PGresult *pgr, std::shared_ptr const &query, - std::string_view desc = ""sv); + std::string_view desc, sl = sl::current()); - void PQXX_PRIVATE set_up_state(); + result make_result( + internal::pq::PGresult *pgr, std::shared_ptr const &query, + sl loc = sl::current()) + { + return make_result(pgr, query, "", loc); + } + + void PQXX_PRIVATE set_up_state(sl); int PQXX_PRIVATE PQXX_PURE status() const noexcept; @@ -1188,26 +1216,34 @@ private: * * Returns the number of bytes written, including the trailing zero. */ - std::size_t esc_to_buf(std::string_view text, char *buf) const; + std::size_t esc_to_buf(std::string_view text, char *buf, sl loc) const; friend class internal::gate::const_connection_largeobject; char const *PQXX_PURE err_msg() const noexcept; - result exec_prepared(std::string_view statement, internal::c_params const &); + result exec_prepared( + std::string_view statement, internal::c_params const &, + sl loc = sl::current()); /// Throw @ref usage_error if this connection is not in a movable state. - void check_movable() const; + void check_movable(sl) const; /// Throw @ref usage_error if not in a state where it can be move-assigned. - void check_overwritable() const; + void check_overwritable(sl) const; friend class internal::gate::connection_errorhandler; void PQXX_PRIVATE register_errorhandler(errorhandler *); void PQXX_PRIVATE unregister_errorhandler(errorhandler *) noexcept; friend class internal::gate::connection_transaction; - result exec(std::string_view, std::string_view = ""sv); + result exec(std::string_view query, sl loc) { return exec(query, "", loc); } + result exec(std::string_view, std::string_view, sl); result PQXX_PRIVATE - exec(std::shared_ptr const &, std::string_view = ""sv); + exec(std::shared_ptr const &, std::string_view, sl); + result PQXX_PRIVATE exec(std::shared_ptr const &query, sl loc) + { + return exec(query, "", loc); + } + void PQXX_PRIVATE register_transaction(transaction_base *); void PQXX_PRIVATE unregister_transaction(transaction_base *) noexcept; @@ -1222,7 +1258,7 @@ private: read_copy_line(); friend class internal::gate::connection_stream_to; - void PQXX_PRIVATE write_copy_line(std::string_view); + void PQXX_PRIVATE write_copy_line(std::string_view, sl); void PQXX_PRIVATE end_copy_write(); friend class internal::gate::connection_largeobject; @@ -1230,7 +1266,7 @@ private: friend class internal::gate::connection_notification_receiver; void add_receiver(notification_receiver *); - void remove_receiver(notification_receiver *) noexcept; + void remove_receiver(notification_receiver *, sl loc) noexcept; friend class internal::gate::connection_pipeline; void PQXX_PRIVATE start_exec(char const query[]); @@ -1241,7 +1277,8 @@ private: friend class internal::gate::connection_dbtransaction; friend class internal::gate::connection_sql_cursor; - result exec_params(std::string_view query, internal::c_params const &args); + result + exec_params(std::string_view query, internal::c_params const &args, sl); /// Connection handle. internal::pq::PGconn *m_conn = nullptr; @@ -1329,7 +1366,7 @@ class PQXX_LIBEXPORT connecting { public: /// Start connecting. - connecting(zview connection_string = ""_zv); + connecting(zview connection_string = ""_zv, sl = sl::current()); connecting(connecting const &) = delete; connecting(connecting &&) = default; @@ -1352,7 +1389,7 @@ public: } /// Progress towards completion (but don't block). - void process() &; + void process(sl loc = sl::current()) &; /// Is our connection finished? [[nodiscard]] constexpr bool done() const & noexcept @@ -1369,7 +1406,7 @@ public: * it on an rvalue instance of the class. If what you have is not an rvalue, * turn it into one by wrapping it in `std::move()`. */ - [[nodiscard]] connection produce() &&; + [[nodiscard]] connection produce(sl = sl::current()) &&; private: connection m_conn; @@ -1378,7 +1415,8 @@ private: }; -template inline std::string connection::quote(T const &t) const +template +inline std::string connection::quote(T const &t, sl loc) const { if (is_null(t)) { @@ -1403,7 +1441,7 @@ template inline std::string connection::quote(T const &t) const // incur some unnecessary memory allocations and deallocations. std::string buf{'\''}; buf.resize(2 + 2 * std::size(text) + 1); - auto const content_bytes{esc_to_buf(text, buf.data() + 1)}; + auto const content_bytes{esc_to_buf(text, buf.data() + 1, loc)}; auto const closing_quote{1 + content_bytes}; buf[closing_quote] = '\''; auto const end{closing_quote + 1}; @@ -1423,7 +1461,7 @@ inline std::string connection::quote_columns(STRINGS const &columns) const template -inline connection::connection(MAPPING const ¶ms) +inline connection::connection(MAPPING const ¶ms, sl loc) { check_version(); @@ -1441,7 +1479,7 @@ inline connection::connection(MAPPING const ¶ms) } keys.push_back(nullptr); values.push_back(nullptr); - init(std::data(keys), std::data(values)); + init(std::data(keys), std::data(values), loc); } } // namespace pqxx #endif diff --git a/include/pqxx/cursor.hxx b/include/pqxx/cursor.hxx index 0ff0f28a7..cb2ccfce8 100644 --- a/include/pqxx/cursor.hxx +++ b/include/pqxx/cursor.hxx @@ -182,8 +182,9 @@ public: */ 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} + bool hold, sl loc = sl::current()) : + m_cur{tx, query, cname, cursor_base::random_access, + up, op, hold, loc} {} /// Adopt an existing scrolling SQL cursor. @@ -193,11 +194,13 @@ public: * @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) : + stateless_cursor( + transaction_base &tx, std::string_view adopted_cursor, + sl loc = sl::current()) : m_cur{tx, adopted_cursor, op} { // Put cursor in known position - m_cur.move(cursor_base::backward_all()); + m_cur.move(cursor_base::backward_all(), loc); } /// Close this cursor. @@ -206,15 +209,15 @@ public: * Closing a cursor is idempotent. Closing a cursor that's already closed * does nothing. */ - void close() noexcept { m_cur.close(); } + void close(sl loc = sl::current()) noexcept { m_cur.close(loc); } /// 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() + [[nodiscard]] size_type size(sl loc = sl::current()) { - return internal::obtain_stateless_cursor_size(m_cur); + return internal::obtain_stateless_cursor_size(m_cur, loc); } /// Retrieve rows from begin_pos (inclusive) to end_pos (exclusive) @@ -229,10 +232,11 @@ public: * 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) + result retrieve( + difference_type begin_pos, difference_type end_pos, sl loc = sl::current()) { return internal::stateless_cursor_retrieve( - m_cur, result::difference_type(size()), begin_pos, end_pos); + m_cur, result::difference_type(size()), begin_pos, end_pos, loc); } /// Return this cursor's name. @@ -295,7 +299,8 @@ public: */ icursorstream( transaction_base &context, std::string_view query, - std::string_view basename, difference_type sstride = 1); + std::string_view basename, difference_type sstride = 1, + sl = sl::current()); /// Adopt existing SQL cursor. Use with care. /** Forms a cursor stream around an existing SQL cursor, as returned by e.g. @@ -324,7 +329,7 @@ public: */ icursorstream( transaction_base &context, field const &cname, difference_type sstride = 1, - cursor_base::ownership_policy op = cursor_base::owned); + cursor_base::ownership_policy op = cursor_base::owned, sl = sl::current()); /// Return `true` if this stream may still return more data. constexpr operator bool() const & noexcept { return not m_done; } @@ -338,9 +343,9 @@ public: * @return Reference to this very stream, to facilitate "chained" invocations * ("C.get(r1).get(r2);") */ - icursorstream &get(result &res) + icursorstream &get(result &res, sl loc = sl::current()) { - res = fetchblock(); + res = fetchblock(loc); return *this; } /// Read new value into given result object; same as `get(result&)`. @@ -361,27 +366,27 @@ public: * @return Reference to this stream itself, to facilitate "chained" * invocations. */ - icursorstream &ignore(std::streamsize n = 1) &; + icursorstream &ignore(std::streamsize n = 1, sl = sl::current()) &; /// 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) &; + void set_stride(difference_type stride, sl = sl::current()) &; [[nodiscard]] constexpr difference_type stride() const noexcept { return m_stride; } private: - result fetchblock(); + result fetchblock(sl); 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); + void service_iterators(difference_type, sl); internal::sql_cursor m_cur; @@ -439,12 +444,16 @@ public: result const &operator*() const { - refresh(); + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; + refresh(loc); return m_here; } result const *operator->() const { - refresh(); + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; + refresh(loc); return &m_here; } icursor_iterator &operator++(); @@ -472,7 +481,7 @@ public: } private: - void refresh() const; + void refresh(sl) const; friend class internal::gate::icursor_iterator_icursorstream; difference_type pos() const noexcept { return m_pos; } diff --git a/include/pqxx/except.hxx b/include/pqxx/except.hxx index 63cad9af9..cc08e5e94 100644 --- a/include/pqxx/except.hxx +++ b/include/pqxx/except.hxx @@ -17,10 +17,11 @@ # error "Include libpqxx headers as , not ." #endif -#include #include #include +#include "pqxx/types.hxx" + namespace pqxx { @@ -43,10 +44,10 @@ namespace pqxx /// Run-time failure encountered by libpqxx, similar to std::runtime_error. struct PQXX_LIBEXPORT failure : std::runtime_error { - explicit failure( - std::string const &, - std::source_location = std::source_location::current()); - std::source_location location; + explicit failure(std::string const &, sl = sl::current()); + + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; @@ -72,10 +73,8 @@ struct PQXX_LIBEXPORT failure : std::runtime_error */ struct PQXX_LIBEXPORT broken_connection : failure { - broken_connection(); - explicit broken_connection( - std::string const &, - std::source_location = std::source_location::current()); + broken_connection(sl loc = sl::current()); + explicit broken_connection(std::string const &, sl = sl::current()); }; @@ -90,18 +89,14 @@ struct PQXX_LIBEXPORT broken_connection : failure */ struct PQXX_LIBEXPORT protocol_violation : broken_connection { - explicit protocol_violation( - std::string const &, - std::source_location = std::source_location::current()); + explicit protocol_violation(std::string const &, sl = sl::current()); }; /// The caller attempted to set a variable to null, which is not allowed. struct PQXX_LIBEXPORT variable_set_to_null : failure { - explicit variable_set_to_null( - std::string const &, - std::source_location = std::source_location::current()); + explicit variable_set_to_null(std::string const &, sl = sl::current()); }; @@ -119,8 +114,7 @@ class PQXX_LIBEXPORT sql_error : public failure public: explicit sql_error( std::string const &whatarg = "", std::string Q = "", - char const *sqlstate = nullptr, - std::source_location = std::source_location::current()); + char const *sqlstate = nullptr, sl = sl::current()); virtual ~sql_error() noexcept override; /// The query whose execution triggered the exception @@ -140,9 +134,7 @@ public: */ struct PQXX_LIBEXPORT in_doubt_error : failure { - explicit in_doubt_error( - std::string const &, - std::source_location = std::source_location::current()); + explicit in_doubt_error(std::string const &, sl = sl::current()); }; @@ -151,8 +143,7 @@ struct PQXX_LIBEXPORT transaction_rollback : sql_error { explicit transaction_rollback( std::string const &whatarg, std::string const &q = "", - char const sqlstate[] = nullptr, - std::source_location = std::source_location::current()); + char const sqlstate[] = nullptr, sl = sl::current()); }; @@ -169,8 +160,7 @@ struct PQXX_LIBEXPORT serialization_failure : transaction_rollback { explicit serialization_failure( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr, - std::source_location = std::source_location::current()); + char const sqlstate[] = nullptr, sl = sl::current()); }; @@ -179,8 +169,7 @@ struct PQXX_LIBEXPORT statement_completion_unknown : transaction_rollback { explicit statement_completion_unknown( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr, - std::source_location = std::source_location::current()); + char const sqlstate[] = nullptr, sl = sl::current()); }; @@ -189,86 +178,78 @@ struct PQXX_LIBEXPORT deadlock_detected : transaction_rollback { explicit deadlock_detected( std::string const &whatarg, std::string const &q, - char const sqlstate[] = nullptr, - std::source_location = std::source_location::current()); + char const sqlstate[] = nullptr, sl = sl::current()); }; /// Internal error in libpqxx library struct PQXX_LIBEXPORT internal_error : std::logic_error { - explicit internal_error(std::string const &); + explicit internal_error(std::string const &, sl = sl::current()); + + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; /// 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 &, - std::source_location = std::source_location::current()); + explicit usage_error(std::string const &, sl = sl::current()); - std::source_location location; + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; /// Invalid argument passed to libpqxx, similar to std::invalid_argument struct PQXX_LIBEXPORT argument_error : std::invalid_argument { - explicit argument_error( - std::string const &, - std::source_location = std::source_location::current()); + explicit argument_error(std::string const &, sl = sl::current()); - std::source_location location; + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; /// Value conversion failed, e.g. when converting "Hello" to int. struct PQXX_LIBEXPORT conversion_error : std::domain_error { - explicit conversion_error( - std::string const &, - std::source_location = std::source_location::current()); + explicit conversion_error(std::string const &, sl = sl::current()); - std::source_location location; + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; /// Could not convert null value: target type does not support null. struct PQXX_LIBEXPORT unexpected_null : conversion_error { - explicit unexpected_null( - std::string const &, - std::source_location = std::source_location::current()); + explicit unexpected_null(std::string const &, sl = sl::current()); }; /// Could not convert value to string: not enough buffer space. struct PQXX_LIBEXPORT conversion_overrun : conversion_error { - explicit conversion_overrun( - std::string const &, - std::source_location = std::source_location::current()); + explicit conversion_overrun(std::string const &, sl = sl::current()); }; /// 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 &, - std::source_location = std::source_location::current()); + explicit range_error(std::string const &, sl = sl::current()); - std::source_location location; + /// A `std::source_location` as a hint to the origin of the problem. + sl location; }; /// Query returned an unexpected number of rows. struct PQXX_LIBEXPORT unexpected_rows : public range_error { - explicit unexpected_rows( - std::string const &msg, - std::source_location loc = std::source_location::current()) : + explicit unexpected_rows(std::string const &msg, sl loc = sl::current()) : range_error{msg, loc} {} }; @@ -279,8 +260,7 @@ struct PQXX_LIBEXPORT feature_not_supported : sql_error { explicit feature_not_supported( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -290,8 +270,7 @@ struct PQXX_LIBEXPORT data_exception : sql_error { explicit data_exception( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -300,8 +279,7 @@ struct PQXX_LIBEXPORT integrity_constraint_violation : sql_error { explicit integrity_constraint_violation( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -310,8 +288,7 @@ struct PQXX_LIBEXPORT restrict_violation : integrity_constraint_violation { explicit restrict_violation( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} }; @@ -320,8 +297,7 @@ 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, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} }; @@ -330,8 +306,7 @@ 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, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} }; @@ -340,8 +315,7 @@ struct PQXX_LIBEXPORT unique_violation : integrity_constraint_violation { explicit unique_violation( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} }; @@ -350,8 +324,7 @@ struct PQXX_LIBEXPORT check_violation : integrity_constraint_violation { explicit check_violation( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : integrity_constraint_violation{err, Q, sqlstate, loc} {} }; @@ -360,8 +333,7 @@ struct PQXX_LIBEXPORT invalid_cursor_state : sql_error { explicit invalid_cursor_state( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -370,8 +342,7 @@ 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, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -380,8 +351,7 @@ struct PQXX_LIBEXPORT invalid_cursor_name : sql_error { explicit invalid_cursor_name( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -393,8 +363,7 @@ struct PQXX_LIBEXPORT syntax_error : sql_error explicit syntax_error( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, int pos = -1, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, int pos = -1, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc}, error_position{pos} {} }; @@ -403,8 +372,7 @@ struct PQXX_LIBEXPORT undefined_column : syntax_error { explicit undefined_column( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : // TODO: Can we get the column? syntax_error{err, Q, sqlstate, -1, loc} {} @@ -414,8 +382,7 @@ struct PQXX_LIBEXPORT undefined_function : syntax_error { explicit undefined_function( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : // TODO: Can we get the column? syntax_error{err, Q, sqlstate, -1, loc} {} @@ -425,8 +392,7 @@ struct PQXX_LIBEXPORT undefined_table : syntax_error { explicit undefined_table( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : // TODO: Can we get the column? syntax_error{err, Q, sqlstate, -1, loc} {} @@ -436,8 +402,7 @@ struct PQXX_LIBEXPORT insufficient_privilege : sql_error { explicit insufficient_privilege( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -447,8 +412,7 @@ struct PQXX_LIBEXPORT insufficient_resources : sql_error { explicit insufficient_resources( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -457,8 +421,7 @@ struct PQXX_LIBEXPORT disk_full : insufficient_resources { explicit disk_full( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : insufficient_resources{err, Q, sqlstate, loc} {} }; @@ -467,8 +430,7 @@ struct PQXX_LIBEXPORT out_of_memory : insufficient_resources { explicit out_of_memory( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : insufficient_resources{err, Q, sqlstate, loc} {} }; @@ -476,8 +438,7 @@ struct PQXX_LIBEXPORT out_of_memory : insufficient_resources struct PQXX_LIBEXPORT too_many_connections : broken_connection { explicit too_many_connections( - std::string const &err, - std::source_location loc = std::source_location::current()) : + std::string const &err, sl loc = sl::current()) : broken_connection{err, loc} {} }; @@ -489,8 +450,7 @@ struct PQXX_LIBEXPORT plpgsql_error : sql_error { explicit plpgsql_error( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : sql_error{err, Q, sqlstate, loc} {} }; @@ -500,8 +460,7 @@ struct PQXX_LIBEXPORT plpgsql_raise : plpgsql_error { explicit plpgsql_raise( std::string const &err, std::string const &Q = "", - char const sqlstate[] = nullptr, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : plpgsql_error{err, Q, sqlstate, loc} {} }; @@ -510,8 +469,7 @@ 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, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : plpgsql_error{err, Q, sqlstate, loc} {} }; @@ -520,8 +478,7 @@ 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, - std::source_location loc = std::source_location::current()) : + char const sqlstate[] = nullptr, sl loc = sl::current()) : plpgsql_error{err, Q, sqlstate, loc} {} }; diff --git a/include/pqxx/field.hxx b/include/pqxx/field.hxx index 16fe5a93c..fc7d6a326 100644 --- a/include/pqxx/field.hxx +++ b/include/pqxx/field.hxx @@ -73,19 +73,19 @@ public: */ //@{ /// Column name. - [[nodiscard]] PQXX_PURE char const *name() const &; + [[nodiscard]] PQXX_PURE char const *name(sl = sl::current()) const &; /// Column type. - [[nodiscard]] oid PQXX_PURE type() const; + [[nodiscard]] oid PQXX_PURE type(sl loc = sl::current()) const; /// What table did this column come from? - [[nodiscard]] PQXX_PURE oid table() const; + [[nodiscard]] PQXX_PURE oid table(sl = sl::current()) 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; + [[nodiscard]] PQXX_PURE row_size_type table_column(sl = sl::current()) const; //@} /** @@ -108,9 +108,9 @@ public: * @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 & + [[nodiscard]] PQXX_PURE std::string_view view() const & noexcept { - return std::string_view(c_str(), size()); + return {c_str(), size()}; } /// Read as plain C string. @@ -123,7 +123,7 @@ public: * convert the value to your desired type using `to()` or `as()`. For * example: `f.as()`. */ - [[nodiscard]] PQXX_PURE char const *c_str() const &; + [[nodiscard]] PQXX_PURE char const *c_str() const & noexcept; /// Is this field's value null? [[nodiscard]] PQXX_PURE bool is_null() const noexcept; @@ -173,7 +173,11 @@ public: } /// Read value into obj; or leave obj untouched and return `false` if null. - template bool operator>>(T &obj) const { return to(obj); } + template + [[deprecated("Use to() or as().")]] 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 @@ -218,12 +222,12 @@ public: * (other than C-strings) because storage for the value can't safely be * allocated here */ - template T as() const + template T as(sl loc = sl::current()) const { if (is_null()) { if constexpr (not nullness::has_null) - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); else return nullness::null(); } @@ -245,15 +249,15 @@ public: /// Read SQL array contents as a @ref pqxx::array. template - array as_sql_array() const + array as_sql_array(sl loc = sl::current()) const { using array_type = array; // There's no such thing as a null SQL array. if (is_null()) - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); else - return array_type{this->view(), this->m_home.m_encoding}; + return array_type{this->view(), this->m_home.m_encoding, loc}; } /// Parse the field as an SQL array. @@ -268,7 +272,9 @@ public: "Instead, use as_sql_array() to convert to pqxx::array.")]] array_parser as_array() const & noexcept { +#include "pqxx/internal/ignore-deprecated-pre.hxx" return array_parser{c_str(), m_home.m_encoding}; +#include "pqxx/internal/ignore-deprecated-post.hxx" } //@} @@ -372,10 +378,10 @@ inline bool field::to(zview &obj, zview const &default_value) const } -template<> inline zview field::as() const +template<> inline zview field::as(sl loc) const { if (is_null()) - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); return zview{c_str(), size()}; } @@ -507,14 +513,15 @@ template /** Unlike the "regular" `from_string`, this knows how to deal with null * values. */ -template inline T from_string(field const &value) +template +inline T from_string(field const &value, sl loc = sl::current()) { if (value.is_null()) { if constexpr (nullness::has_null) return nullness::null(); else - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); } else { @@ -523,6 +530,8 @@ template inline T from_string(field const &value) } +// TODO: Can we make this generic across all "always-null" types? +// TODO: Do the same for streams. /// Convert a field's value to `nullptr_t`. /** Yes, you read that right. This conversion does nothing useful. It always * returns `nullptr`. @@ -531,11 +540,11 @@ template inline T from_string(field const &value) * @ref conversion_error. */ template<> -inline std::nullptr_t from_string(field const &value) +inline std::nullptr_t from_string(field const &value, sl loc) { if (not value.is_null()) throw conversion_error{ - "Extracting non-null field into nullptr_t variable."}; + "Extracting non-null field into nullptr_t variable.", loc}; return nullptr; } diff --git a/include/pqxx/internal/array-composite.hxx b/include/pqxx/internal/array-composite.hxx index 689dd7300..8e4747975 100644 --- a/include/pqxx/internal/array-composite.hxx +++ b/include/pqxx/internal/array-composite.hxx @@ -18,15 +18,15 @@ namespace pqxx::internal */ template inline std::size_t scan_double_quoted_string( - char const input[], std::size_t size, std::size_t pos) + char const input[], std::size_t size, std::size_t pos, sl loc) { // TODO: find_char<'"', '\\'>(). using scanner = glyph_scanner; - auto next{scanner::call(input, size, pos)}; + auto next{scanner::call(input, size, pos, loc)}; PQXX_ASSUME(next > pos); bool at_quote{false}; pos = next; - next = scanner::call(input, size, pos); + next = scanner::call(input, size, pos, loc); PQXX_ASSUME(next > pos); while (pos < size) { @@ -52,7 +52,7 @@ inline std::size_t scan_double_quoted_string( case '\\': // Backslash escape. Skip ahead by one more character. pos = next; - next = scanner::call(input, size, pos); + next = scanner::call(input, size, pos, loc); PQXX_ASSUME(next > pos); break; @@ -68,12 +68,12 @@ inline std::size_t scan_double_quoted_string( // Multibyte character. Carry on. } pos = next; - next = scanner::call(input, size, pos); + next = scanner::call(input, size, pos, loc); PQXX_ASSUME(next > pos); } if (not at_quote) throw argument_error{ - "Missing closing double-quote: " + std::string{input}}; + "Missing closing double-quote: " + std::string{input}, loc}; return pos; } @@ -82,7 +82,7 @@ inline std::size_t scan_double_quoted_string( /// Un-quote and un-escape a double-quoted SQL string. template inline std::string parse_double_quoted_string( - char const input[], std::size_t end, std::size_t pos) + char const input[], std::size_t end, std::size_t pos, sl loc) { std::string output; // Maximum output size is same as the input size, minus the opening and @@ -92,8 +92,8 @@ inline std::string parse_double_quoted_string( // TODO: Use find_char<...>(). using scanner = glyph_scanner; - auto here{scanner::call(input, end, pos)}, - next{scanner::call(input, end, here)}; + auto here{scanner::call(input, end, pos, loc)}, + next{scanner::call(input, end, here, loc)}; PQXX_ASSUME(here > pos); PQXX_ASSUME(next > here); while (here < end - 1) @@ -106,12 +106,12 @@ inline std::string parse_double_quoted_string( { // Skip escape. here = next; - next = scanner::call(input, end, here); + next = scanner::call(input, end, here, loc); PQXX_ASSUME(next > here); } output.append(input + here, input + next); here = next; - next = scanner::call(input, end, here); + next = scanner::call(input, end, here, loc); PQXX_ASSUME(next > here); } return output; @@ -127,16 +127,16 @@ inline std::string parse_double_quoted_string( * comma or a closing parenthesis. */ template -inline std::size_t -scan_unquoted_string(char const input[], std::size_t size, std::size_t pos) +inline std::size_t scan_unquoted_string( + char const input[], std::size_t size, std::size_t pos, sl loc) { using scanner = glyph_scanner; - auto next{scanner::call(input, size, pos)}; + auto next{scanner::call(input, size, pos, loc)}; PQXX_ASSUME(next > pos); while ((pos < size) and ((next - pos) > 1 or ((input[pos] != STOP) and ...))) { pos = next; - next = scanner::call(input, size, pos); + next = scanner::call(input, size, pos, loc); PQXX_ASSUME(next > pos); } return pos; @@ -146,7 +146,7 @@ scan_unquoted_string(char const input[], std::size_t size, std::size_t pos) /// Parse an unquoted array entry or cfield of a composite-type field. template inline std::string_view -parse_unquoted_string(char const input[], std::size_t end, std::size_t pos) +parse_unquoted_string(char const input[], std::size_t end, std::size_t pos, sl) { return {&input[pos], end - pos}; } @@ -179,13 +179,15 @@ parse_unquoted_string(char const input[], std::size_t end, std::size_t pos) template inline void parse_composite_field( std::size_t &index, std::string_view input, std::size_t &pos, T &field, - std::size_t last_field) + std::size_t last_field, sl loc) { assert(index <= last_field); - auto next{glyph_scanner::call(std::data(input), std::size(input), pos)}; + auto next{ + glyph_scanner::call(std::data(input), std::size(input), pos, loc)}; PQXX_ASSUME(next > pos); if ((next - pos) != 1) - throw conversion_error{"Non-ASCII character in composite-type syntax."}; + throw conversion_error{ + "Non-ASCII character in composite-type syntax.", loc}; // Expect a field. switch (input[pos]) @@ -199,15 +201,16 @@ inline void parse_composite_field( else throw conversion_error{ "Can't read composite field " + to_string(index) + ": C++ type " + - type_name + " does not support nulls."}; + type_name + " does not support nulls.", + loc}; break; case '"': { - auto const stop{ - scan_double_quoted_string(std::data(input), std::size(input), pos)}; + auto const stop{scan_double_quoted_string( + std::data(input), std::size(input), pos, loc)}; PQXX_ASSUME(stop > pos); auto const text{ - parse_double_quoted_string(std::data(input), stop, pos)}; + parse_double_quoted_string(std::data(input), stop, pos, loc)}; field = from_string(text); pos = stop; } @@ -215,7 +218,7 @@ inline void parse_composite_field( default: { auto const stop{scan_unquoted_string( - std::data(input), std::size(input), pos)}; + std::data(input), std::size(input), pos, loc)}; PQXX_ASSUME(stop >= pos); field = from_string(std::string_view{std::data(input) + pos, stop - pos}); @@ -225,36 +228,42 @@ inline void parse_composite_field( } // Expect a comma or a closing parenthesis. - next = glyph_scanner::call(std::data(input), std::size(input), pos); + next = + glyph_scanner::call(std::data(input), std::size(input), pos, loc); PQXX_ASSUME(next > pos); if ((next - pos) != 1) throw conversion_error{ "Unexpected non-ASCII character after composite field: " + - std::string{input}}; + std::string{input}, + loc}; 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)}; + "' in composite value where comma was expected: " + std::data(input), + loc}; } else { if (input[pos] == ',') throw conversion_error{ "Composite value contained more fields than the expected " + - to_string(last_field) + ": " + std::data(input)}; + to_string(last_field) + ": " + std::data(input), + loc}; if (input[pos] != ')' and input[pos] != ']') throw conversion_error{ "Composite value has unexpected characters where closing parenthesis " "was expected: " + - std::string{input}}; + std::string{input}, + loc}; if (next != std::size(input)) throw conversion_error{ "Composite value has unexpected text after closing parenthesis: " + - std::string{input}}; + std::string{input}, + loc}; } pos = next; @@ -266,12 +275,13 @@ inline void parse_composite_field( template using composite_field_parser = void (*)( std::size_t &index, std::string_view input, std::size_t &pos, T &field, - std::size_t last_field); + std::size_t last_field, sl loc); /// Look up implementation of parse_composite_field for ENC. template -composite_field_parser specialize_parse_composite_field(encoding_group enc) +composite_field_parser +specialize_parse_composite_field(encoding_group enc, sl loc) { switch (enc) { @@ -300,7 +310,8 @@ composite_field_parser specialize_parse_composite_field(encoding_group enc) case encoding_group::UTF8: return parse_composite_field; } - throw internal_error{concat("Unexpected encoding group code: ", enc, ".")}; + throw internal_error{ + concat("Unexpected encoding group code: ", enc, "."), loc}; } diff --git a/include/pqxx/internal/conversions.hxx b/include/pqxx/internal/conversions.hxx index 2a4ea8106..ff4f7806b 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -52,12 +52,12 @@ inline std::string state_buffer_overrun(HAVE have_bytes, NEED need_bytes) /// Throw exception for attempt to convert SQL NULL to given type. [[noreturn]] PQXX_LIBEXPORT PQXX_COLD void -throw_null_conversion(std::string const &type); +throw_null_conversion(std::string const &type, sl); /// Throw exception for attempt to convert SQL NULL to given type. [[noreturn]] PQXX_LIBEXPORT PQXX_COLD void -throw_null_conversion(std::string_view type); +throw_null_conversion(std::string_view type, sl); /// Deliberately nonfunctional conversion traits for `char` types. @@ -937,13 +937,14 @@ template struct string_traits return begin + budget; } - static DATA from_string(std::string_view text) + static DATA from_string(std::string_view text, sl loc = sl::current()) { auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; bytes buf; buf.resize(size); // XXX: Use std::as_writable_bytes. - pqxx::internal::unesc_bin(text, reinterpret_cast(buf.data())); + pqxx::internal::unesc_bin( + text, reinterpret_cast(buf.data()), loc); return buf; } }; diff --git a/include/pqxx/internal/encoding_group.hxx b/include/pqxx/internal/encoding_group.hxx index 7e2603471..c496c27ed 100644 --- a/include/pqxx/internal/encoding_group.hxx +++ b/include/pqxx/internal/encoding_group.hxx @@ -49,8 +49,8 @@ enum class encoding_group * 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); +using glyph_scanner_func = std::size_t( + char const buffer[], std::size_t buffer_len, std::size_t start, sl); /// Function type: "find first occurrence of specific any of ASCII characters." @@ -68,7 +68,7 @@ using glyph_scanner_func = * end of `haystack`. */ using char_finder_func = - std::size_t(std::string_view haystack, std::size_t start); + std::size_t(std::string_view haystack, std::size_t start, sl); } // namespace pqxx::internal #endif diff --git a/include/pqxx/internal/encodings.hxx b/include/pqxx/internal/encodings.hxx index 92aa341fc..959994f23 100644 --- a/include/pqxx/internal/encodings.hxx +++ b/include/pqxx/internal/encodings.hxx @@ -29,7 +29,7 @@ namespace pqxx::internal PQXX_PURE char const *name_encoding(int encoding_id); /// Convert libpq encoding enum value to its libpqxx group. -PQXX_LIBEXPORT encoding_group enc_group(int /* libpq encoding ID */); +PQXX_LIBEXPORT encoding_group enc_group(int /* libpq encoding ID */, sl); /// Look up the glyph scanner function for a given encoding group. @@ -37,7 +37,7 @@ PQXX_LIBEXPORT encoding_group enc_group(int /* libpq encoding ID */); * 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); +PQXX_LIBEXPORT glyph_scanner_func *get_glyph_scanner(encoding_group, sl); // TODO: Get rid of this one. Use compile-time-specialised version instead. @@ -50,14 +50,14 @@ PQXX_LIBEXPORT glyph_scanner_func *get_glyph_scanner(encoding_group); */ template inline std::size_t find_char( - glyph_scanner_func *scanner, std::string_view haystack, - std::size_t here = 0u) + glyph_scanner_func *scanner, std::string_view haystack, std::size_t here, + sl loc) { auto const sz{std::size(haystack)}; auto const data{std::data(haystack)}; while (here < sz) { - auto next{scanner(data, sz, here)}; + auto next{scanner(data, sz, here, loc)}; PQXX_ASSUME(next > here); // (For some reason gcc had a problem with a right-fold here. But clang // was fine.) @@ -86,12 +86,12 @@ inline std::size_t find_char( template inline void for_glyphs( encoding_group enc, CALLABLE callback, char const buffer[], - std::size_t buffer_len, std::size_t start = 0) + std::size_t buffer_len, std::size_t start, sl loc) { - auto const scan{get_glyph_scanner(enc)}; + auto const scan{get_glyph_scanner(enc, loc)}; for (std::size_t here = start, next; here < buffer_len; here = next) { - next = scan(buffer, buffer_len, here); + next = scan(buffer, buffer_len, here, loc); PQXX_ASSUME(next > here); callback(buffer + here, buffer + next); } @@ -110,7 +110,7 @@ get_byte(char const buffer[], std::size_t offset) noexcept [[noreturn]] PQXX_COLD void throw_for_encoding_error( char const *encoding_name, char const buffer[], std::size_t start, - std::size_t count) + std::size_t count, sl loc) { std::stringstream s; s << "Invalid byte sequence for encoding " << encoding_name << " at byte " @@ -121,7 +121,7 @@ get_byte(char const buffer[], std::size_t offset) noexcept if (i + 1 < count) s << " "; } - throw pqxx::argument_error{s.str()}; + throw pqxx::argument_error{s.str(), loc}; } @@ -144,7 +144,7 @@ template struct glyph_scanner // TODO: Convert to use string_view? /// Find the next glyph in `buffer` after position `start`. PQXX_PURE static std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start); + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl); }; @@ -159,7 +159,7 @@ namespace */ template PQXX_PURE inline std::size_t -find_ascii_char(std::string_view haystack, std::size_t here) +find_ascii_char(std::string_view haystack, std::size_t here, sl loc) { // We only know how to search for ASCII characters. It's an optimisation // assumption in the code below. @@ -171,7 +171,7 @@ find_ascii_char(std::string_view haystack, std::size_t here) { // Look up the next character boundary. This can be quite costly, so we // desperately want the call inlined. - auto next{glyph_scanner::call(data, sz, here)}; + auto next{glyph_scanner::call(data, sz, here, loc)}; PQXX_ASSUME(next > here); // (For some reason gcc had a problem with a right-fold here. But clang @@ -208,7 +208,7 @@ find_ascii_char(std::string_view haystack, std::size_t here) */ template PQXX_PURE std::size_t -find_s_ascii_char(std::string_view haystack, std::size_t here) +find_s_ascii_char(std::string_view haystack, std::size_t here, sl loc) { // We only know how to search for ASCII characters. It's an optimisation // assumption in the code below. @@ -221,7 +221,7 @@ find_s_ascii_char(std::string_view haystack, std::size_t here) // ASCII-range byte. while ((... and (data[here] != NEEDLE))) { - auto const next = glyph_scanner::call(data, sz, here); + auto const next = glyph_scanner::call(data, sz, here, loc); PQXX_ASSUME(next > here); here = next; } @@ -231,8 +231,8 @@ find_s_ascii_char(std::string_view haystack, std::size_t here) template<> struct glyph_scanner { - static PQXX_PURE constexpr std::size_t - call(char const /* buffer */[], std::size_t buffer_len, std::size_t start) + static PQXX_PURE constexpr std::size_t call( + char const /* buffer */[], std::size_t buffer_len, std::size_t start, sl) { // TODO: Don't bother with npos. Let the caller check. if (start >= buffer_len) [[unlikely]] @@ -247,7 +247,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -258,13 +258,13 @@ template<> struct glyph_scanner if (not between_inc(byte1, 0x81, 0xfe) or (start + 2 > buffer_len)) [[unlikely]] - throw_for_encoding_error("BIG5", buffer, start, 1); + throw_for_encoding_error("BIG5", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if ( not between_inc(byte2, 0x40, 0x7e) and not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("BIG5", buffer, start, 2); + throw_for_encoding_error("BIG5", buffer, start, 2, loc); return start + 2; } @@ -286,7 +286,7 @@ depending on the specific extension: template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) return std::string::npos; @@ -297,11 +297,11 @@ template<> struct glyph_scanner if (not between_inc(byte1, 0xa1, 0xf7) or start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("EUC_CN", buffer, start, 1); + throw_for_encoding_error("EUC_CN", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_CN", buffer, start, 2); + throw_for_encoding_error("EUC_CN", buffer, start, 2, loc); return start + 2; } @@ -316,7 +316,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) return std::string::npos; @@ -326,13 +326,13 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("EUC_JP", buffer, start, 1); + throw_for_encoding_error("EUC_JP", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if (byte1 == 0x8e) { if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_JP", buffer, start, 2); + throw_for_encoding_error("EUC_JP", buffer, start, 2, loc); return start + 2; } @@ -340,7 +340,7 @@ template<> struct glyph_scanner if (between_inc(byte1, 0xa1, 0xfe)) { if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_JP", buffer, start, 2); + throw_for_encoding_error("EUC_JP", buffer, start, 2, loc); return start + 2; } @@ -351,12 +351,12 @@ template<> struct glyph_scanner if ( not between_inc(byte2, 0xa1, 0xfe) or not between_inc(byte3, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_JP", buffer, start, 3); + throw_for_encoding_error("EUC_JP", buffer, start, 3, loc); return start + 3; } - throw_for_encoding_error("EUC_JP", buffer, start, 1); + throw_for_encoding_error("EUC_JP", buffer, start, 1, loc); } }; @@ -365,7 +365,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -376,11 +376,11 @@ template<> struct glyph_scanner if (not between_inc(byte1, 0xa1, 0xfe) or start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("EUC_KR", buffer, start, 1); + throw_for_encoding_error("EUC_KR", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_KR", buffer, start, 1); + throw_for_encoding_error("EUC_KR", buffer, start, 1, loc); return start + 2; } @@ -391,7 +391,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -401,19 +401,19 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("EUC_KR", buffer, start, 1); + throw_for_encoding_error("EUC_KR", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0xa1, 0xfe)) { if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("EUC_KR", buffer, start, 2); + throw_for_encoding_error("EUC_KR", buffer, start, 2, loc); return start + 2; } if (byte1 != 0x8e or start + 4 > buffer_len) [[unlikely]] - throw_for_encoding_error("EUC_KR", buffer, start, 1); + throw_for_encoding_error("EUC_KR", buffer, start, 1, loc); if ( between_inc(byte2, 0xa1, 0xb0) and @@ -421,7 +421,7 @@ template<> struct glyph_scanner between_inc(get_byte(buffer, start + 3), 0xa1, 0xfe)) return start + 4; - [[unlikely]] throw_for_encoding_error("EUC_KR", buffer, start, 4); + [[unlikely]] throw_for_encoding_error("EUC_KR", buffer, start, 4, loc); } }; @@ -430,7 +430,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -439,22 +439,25 @@ template<> struct glyph_scanner if (byte1 < 0x80) return start + 1; if (byte1 == 0x80) - throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); + throw_for_encoding_error( + "GB18030", buffer, start, buffer_len - start, loc); if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); + throw_for_encoding_error( + "GB18030", buffer, start, buffer_len - start, loc); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte2, 0x40, 0xfe)) { if (byte2 == 0x7f) [[unlikely]] - throw_for_encoding_error("GB18030", buffer, start, 2); + throw_for_encoding_error("GB18030", buffer, start, 2, loc); return start + 2; } if (start + 4 > buffer_len) [[unlikely]] - throw_for_encoding_error("GB18030", buffer, start, buffer_len - start); + throw_for_encoding_error( + "GB18030", buffer, start, buffer_len - start, loc); if ( between_inc(byte2, 0x30, 0x39) and @@ -462,7 +465,7 @@ template<> struct glyph_scanner between_inc(get_byte(buffer, start + 3), 0x30, 0x39)) return start + 4; - [[unlikely]] throw_for_encoding_error("GB18030", buffer, start, 4); + [[unlikely]] throw_for_encoding_error("GB18030", buffer, start, 4, loc); } }; @@ -471,7 +474,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -481,7 +484,7 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("GBK", buffer, start, 1); + throw_for_encoding_error("GBK", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if ( @@ -499,7 +502,7 @@ template<> struct glyph_scanner byte2 != 0x7f)) return start + 2; - [[unlikely]] throw_for_encoding_error("GBK", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("GBK", buffer, start, 2, loc); } }; @@ -516,7 +519,7 @@ CJKV Information Processing by Ken Lunde, pg. 269: template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -526,7 +529,7 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("JOHAB", buffer, start, 1); + throw_for_encoding_error("JOHAB", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start)}; if ( @@ -536,7 +539,7 @@ template<> struct glyph_scanner (between_inc(byte2, 0x31, 0x7e) or between_inc(byte2, 0x91, 0xfe)))) return start + 2; - [[unlikely]] throw_for_encoding_error("JOHAB", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("JOHAB", buffer, start, 2, loc); } }; @@ -551,7 +554,7 @@ using PostgreSQL 9.2.23. Use this at your own risk. template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -561,14 +564,14 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("MULE_INTERNAL", buffer, start, 1); + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 1, loc); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0x81, 0x8d) and byte2 >= 0xa0) return start + 2; if (start + 3 > buffer_len) [[unlikely]] - throw_for_encoding_error("MULE_INTERNAL", buffer, start, 2); + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 2, loc); if ( ((byte1 == 0x9a and between_inc(byte2, 0xa0, 0xdf)) or @@ -578,7 +581,7 @@ template<> struct glyph_scanner return start + 3; if (start + 4 > buffer_len) [[unlikely]] - throw_for_encoding_error("MULE_INTERNAL", buffer, start, 3); + throw_for_encoding_error("MULE_INTERNAL", buffer, start, 3, loc); if ( ((byte1 == 0x9c and between_inc(byte2, 0xf0, 0xf4)) or @@ -587,7 +590,8 @@ template<> struct glyph_scanner get_byte(buffer, start + 4) >= 0xa0) return start + 4; - [[unlikely]] throw_for_encoding_error("MULE_INTERNAL", buffer, start, 4); + [[unlikely]] throw_for_encoding_error( + "MULE_INTERNAL", buffer, start, 4, loc); } }; @@ -604,7 +608,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) return std::string::npos; @@ -616,19 +620,19 @@ template<> struct glyph_scanner if ( not between_inc(byte1, 0x81, 0x9f) and not between_inc(byte1, 0xe0, 0xfc)) [[unlikely]] - throw_for_encoding_error("SJIS", buffer, start, 1); + throw_for_encoding_error("SJIS", buffer, start, 1, loc); if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("SJIS", buffer, start, buffer_len - start); + throw_for_encoding_error("SJIS", buffer, start, buffer_len - start, loc); auto const byte2{get_byte(buffer, start + 1)}; if (byte2 == 0x7f) [[unlikely]] - throw_for_encoding_error("SJIS", buffer, start, 2); + throw_for_encoding_error("SJIS", buffer, start, 2, loc); if (between_inc(byte2, 0x40, 0x9e) or between_inc(byte2, 0x9f, 0xfc)) return start + 2; - [[unlikely]] throw_for_encoding_error("SJIS", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("SJIS", buffer, start, 2, loc); } }; @@ -637,7 +641,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -647,7 +651,7 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("UHC", buffer, start, buffer_len - start); + throw_for_encoding_error("UHC", buffer, start, buffer_len - start, loc); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0x80, 0xc6)) @@ -657,18 +661,18 @@ template<> struct glyph_scanner between_inc(byte2, 0x80, 0xfe)) return start + 2; - [[unlikely]] throw_for_encoding_error("UHC", buffer, start, 2); + [[unlikely]] throw_for_encoding_error("UHC", buffer, start, 2, loc); } if (between_inc(byte1, 0xa1, 0xfe)) { if (not between_inc(byte2, 0xa1, 0xfe)) [[unlikely]] - throw_for_encoding_error("UHC", buffer, start, 2); + throw_for_encoding_error("UHC", buffer, start, 2, loc); return start + 2; } - throw_for_encoding_error("UHC", buffer, start, 1); + throw_for_encoding_error("UHC", buffer, start, 1, loc); } }; @@ -677,7 +681,7 @@ template<> struct glyph_scanner template<> struct glyph_scanner { static PQXX_PURE std::size_t - call(char const buffer[], std::size_t buffer_len, std::size_t start) + call(char const buffer[], std::size_t buffer_len, std::size_t start, sl loc) { if (start >= buffer_len) [[unlikely]] return std::string::npos; @@ -687,19 +691,19 @@ template<> struct glyph_scanner return start + 1; if (start + 2 > buffer_len) [[unlikely]] - throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start, loc); auto const byte2{get_byte(buffer, start + 1)}; if (between_inc(byte1, 0xc0, 0xdf)) { if (not between_inc(byte2, 0x80, 0xbf)) [[unlikely]] - throw_for_encoding_error("UTF8", buffer, start, 2); + throw_for_encoding_error("UTF8", buffer, start, 2, loc); return start + 2; } if (start + 3 > buffer_len) [[unlikely]] - throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start, loc); auto const byte3{get_byte(buffer, start + 2)}; if (between_inc(byte1, 0xe0, 0xef)) @@ -707,11 +711,11 @@ template<> struct glyph_scanner if (between_inc(byte2, 0x80, 0xbf) and between_inc(byte3, 0x80, 0xbf)) return start + 3; - [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 3); + [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 3, loc); } if (start + 4 > buffer_len) [[unlikely]] - throw_for_encoding_error("UTF8", buffer, start, buffer_len - start); + throw_for_encoding_error("UTF8", buffer, start, buffer_len - start, loc); if (between_inc(byte1, 0xf0, 0xf7)) { @@ -720,10 +724,10 @@ template<> struct glyph_scanner between_inc(get_byte(buffer, start + 3), 0x80, 0xbf)) return start + 4; - [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 4); + [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 4, loc); } - [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 1); + [[unlikely]] throw_for_encoding_error("UTF8", buffer, start, 1, loc); } }; @@ -774,7 +778,7 @@ map_ascii_search_group(encoding_group enc) noexcept */ template PQXX_PURE constexpr inline char_finder_func * -get_char_finder(encoding_group enc) +get_char_finder(encoding_group enc, sl loc) { auto const as_if{map_ascii_search_group(enc)}; switch (as_if) @@ -796,8 +800,10 @@ get_char_finder(encoding_group enc) return pqxx::internal::find_ascii_char; default: - throw pqxx::internal_error{concat( - "Unexpected encoding group: ", as_if, " (mapped from ", enc, ").")}; + throw pqxx::internal_error{ + concat( + "Unexpected encoding group: ", as_if, " (mapped from ", enc, ")."), + loc}; } } @@ -808,7 +814,7 @@ get_char_finder(encoding_group enc) */ template PQXX_PURE constexpr inline char_finder_func * -get_s_char_finder(encoding_group enc) +get_s_char_finder(encoding_group enc, sl loc) { auto const as_if{map_ascii_search_group(enc)}; switch (as_if) @@ -831,8 +837,10 @@ get_s_char_finder(encoding_group enc) return pqxx::internal::find_s_ascii_char; default: - throw pqxx::internal_error{concat( - "Unexpected encoding group: ", as_if, " (mapped from ", enc, ").")}; + throw pqxx::internal_error{ + concat( + "Unexpected encoding group: ", as_if, " (mapped from ", enc, ")."), + loc}; } } } // namespace pqxx::internal diff --git a/include/pqxx/internal/gates/connection-notification_receiver.hxx b/include/pqxx/internal/gates/connection-notification_receiver.hxx index 0bcb2db17..7fc6a2744 100644 --- a/include/pqxx/internal/gates/connection-notification_receiver.hxx +++ b/include/pqxx/internal/gates/connection-notification_receiver.hxx @@ -21,9 +21,9 @@ class PQXX_PRIVATE connection_notification_receiver : callgate { home().add_receiver(receiver); } - void remove_receiver(notification_receiver *receiver) noexcept + void remove_receiver(notification_receiver *receiver, sl loc) noexcept { - home().remove_receiver(receiver); + home().remove_receiver(receiver, loc); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/gates/connection-pipeline.hxx b/include/pqxx/internal/gates/connection-pipeline.hxx index ff9acd65a..23259e428 100644 --- a/include/pqxx/internal/gates/connection-pipeline.hxx +++ b/include/pqxx/internal/gates/connection-pipeline.hxx @@ -13,12 +13,12 @@ class PQXX_PRIVATE connection_pipeline : callgate 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(); } + void cancel_query(sl loc) { home().cancel_query(loc); } bool consume_input() noexcept { return home().consume_input(); } bool is_busy() const noexcept { return home().is_busy(); } - int encoding_id() { return home().encoding_id(); } + int encoding_id(sl loc) { return home().encoding_id(loc); } auto get_notice_waiters() const { return home().m_notice_waiters; } }; diff --git a/include/pqxx/internal/gates/connection-sql_cursor.hxx b/include/pqxx/internal/gates/connection-sql_cursor.hxx index 51a889844..55d26efbf 100644 --- a/include/pqxx/internal/gates/connection-sql_cursor.hxx +++ b/include/pqxx/internal/gates/connection-sql_cursor.hxx @@ -14,6 +14,6 @@ class PQXX_PRIVATE connection_sql_cursor : callgate connection_sql_cursor(reference x) : super(x) {} - result exec(char const query[]) { return home().exec(query); } + result exec(char const query[], sl loc) { return home().exec(query, loc); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/gates/connection-stream_to.hxx b/include/pqxx/internal/gates/connection-stream_to.hxx index a6974fb21..ea44bc8ea 100644 --- a/include/pqxx/internal/gates/connection-stream_to.hxx +++ b/include/pqxx/internal/gates/connection-stream_to.hxx @@ -11,7 +11,10 @@ class PQXX_PRIVATE connection_stream_to : callgate connection_stream_to(reference x) : super(x) {} - void write_copy_line(std::string_view line) { home().write_copy_line(line); } + void write_copy_line(std::string_view line, sl loc) + { + home().write_copy_line(line, loc); + } void end_copy_write() { home().end_copy_write(); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/gates/connection-transaction.hxx b/include/pqxx/internal/gates/connection-transaction.hxx index 3a7546a4f..e94bfd409 100644 --- a/include/pqxx/internal/gates/connection-transaction.hxx +++ b/include/pqxx/internal/gates/connection-transaction.hxx @@ -13,9 +13,15 @@ class PQXX_PRIVATE connection_transaction : callgate connection_transaction(reference x) : super(x) {} - template result exec(STRING query, std::string_view desc) + template + result exec(STRING query, std::string_view desc, sl loc) { - return home().exec(query, desc); + return home().exec(query, desc, loc); + } + + template result exec(STRING query, sl loc) + { + return home().exec(query, "", loc); } void register_transaction(transaction_base *t) @@ -28,18 +34,22 @@ class PQXX_PRIVATE connection_transaction : callgate } auto read_copy_line() { return home().read_copy_line(); } - void write_copy_line(std::string_view line) { home().write_copy_line(line); } + void write_copy_line(std::string_view line, sl loc) + { + home().write_copy_line(line, loc); + } void end_copy_write() { home().end_copy_write(); } - result - exec_prepared(std::string_view statement, internal::c_params const &args) + result exec_prepared( + std::string_view statement, internal::c_params const &args, sl loc) { - return home().exec_prepared(statement, args); + return home().exec_prepared(statement, args, loc); } - result exec_params(std::string_view query, internal::c_params const &args) + result + exec_params(std::string_view query, internal::c_params const &args, sl loc) { - return home().exec_params(query, args); + return home().exec_params(query, args, loc); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx b/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx index 56056d5ef..90bd0d494 100644 --- a/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx +++ b/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx @@ -24,9 +24,9 @@ class PQXX_PRIVATE icursorstream_icursor_iterator : callgate return home().forward(n); } - void service_iterators(icursorstream::difference_type p) + void service_iterators(icursorstream::difference_type p, sl loc) { - home().service_iterators(p); + home().service_iterators(p, loc); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/gates/result-creation.hxx b/include/pqxx/internal/gates/result-creation.hxx index f82bbda5f..b67c7d943 100644 --- a/include/pqxx/internal/gates/result-creation.hxx +++ b/include/pqxx/internal/gates/result-creation.hxx @@ -18,9 +18,11 @@ class PQXX_PRIVATE result_creation : callgate return result(rhs, query, notice_waiters, enc); } - void check_status(std::string_view desc = ""sv) const + void check_status(std::string_view desc, sl loc) const { - return home().check_status(desc); + return home().check_status(desc, loc); } + + void check_status(sl loc) const { return home().check_status("", loc); } }; } // namespace pqxx::internal::gate diff --git a/include/pqxx/internal/result_iter.hxx b/include/pqxx/internal/result_iter.hxx index 9eea49861..faba17b28 100644 --- a/include/pqxx/internal/result_iter.hxx +++ b/include/pqxx/internal/result_iter.hxx @@ -31,35 +31,39 @@ public: /// Construct an "end" iterator. result_iter() = default; - explicit result_iter(result const &home) : + explicit result_iter(result const &home, sl loc = sl::current()) : m_home{&home}, m_size{std::size(home)} { if (not std::empty(home)) - read(); + read(loc); } result_iter(result_iter const &) = default; result_iter &operator++() { + sl loc{sl::current()}; m_index++; if (m_index >= m_size) m_home = nullptr; else - read(); + read(loc); return *this; } /// Comparison only works for comparing to end(). - bool operator==(result_iter const &rhs) const + bool operator==(result_iter const &rhs) const noexcept { return m_home == rhs.m_home; } - bool operator!=(result_iter const &rhs) const { return not(*this == rhs); } + bool operator!=(result_iter const &rhs) const noexcept + { + return not(*this == rhs); + } - value_type const &operator*() const { return m_value; } + value_type const &operator*() const noexcept { return m_value; } private: - void read() { (*m_home)[m_index].convert(m_value); } + void read(sl loc) { (*m_home)[m_index].convert(m_value, loc); } result const *m_home{nullptr}; result::size_type m_index{0}; @@ -100,7 +104,7 @@ template inline auto pqxx::result::iter() const template -inline void pqxx::result::for_each(CALLABLE &&func) const +inline void pqxx::result::for_each(CALLABLE &&func, sl loc) const { using args_tuple = internal::args_t; constexpr auto sz{std::tuple_size_v}; @@ -111,11 +115,13 @@ inline void pqxx::result::for_each(CALLABLE &&func) const 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", ".")}; + throw usage_error{ + internal::concat( + "Callback to for_each takes ", sz, "parameter", (sz == 1) ? "" : "s", + ", but result set has ", cols, "field", (cols == 1) ? "" : "s", "."), + loc}; using pass_tuple = pqxx::internal::strip_types_t; - for (auto const r : *this) std::apply(func, r.as_tuple()); + for (auto const r : *this) std::apply(func, r.as_tuple(loc)); } #endif diff --git a/include/pqxx/internal/result_iterator.hxx b/include/pqxx/internal/result_iterator.hxx index 7b911b5ae..6af22b861 100644 --- a/include/pqxx/internal/result_iterator.hxx +++ b/include/pqxx/internal/result_iterator.hxx @@ -65,10 +65,10 @@ public: */ //@{ /// Dereference the iterator. - [[nodiscard]] pointer operator->() const { return this; } + [[nodiscard]] pointer operator->() const noexcept { return this; } /// Dereference the iterator. - [[nodiscard]] reference operator*() const { return *this; } + [[nodiscard]] reference operator*() const noexcept { return *this; } //@} /** @@ -196,10 +196,10 @@ public: using reference = iterator_type::reference; /// Create an iterator, but in an unusable state. - const_reverse_result_iterator() = default; + const_reverse_result_iterator() noexcept = default; /// Copy an iterator. - const_reverse_result_iterator(const_reverse_result_iterator const &rhs) = - default; + const_reverse_result_iterator( + const_reverse_result_iterator const &rhs) noexcept = default; /// Copy a reverse iterator from a regular iterator. explicit const_reverse_result_iterator(const_result_iterator const &rhs) : const_result_iterator{rhs} diff --git a/include/pqxx/internal/sql_cursor.hxx b/include/pqxx/internal/sql_cursor.hxx index 4d711273a..947eab344 100644 --- a/include/pqxx/internal/sql_cursor.hxx +++ b/include/pqxx/internal/sql_cursor.hxx @@ -34,25 +34,31 @@ 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); + cursor_base::ownership_policy op, bool hold, sl = sl::current()); sql_cursor( transaction_base &t, std::string_view cname, cursor_base::ownership_policy op); - ~sql_cursor() noexcept { close(); } + ~sql_cursor() noexcept + { + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; + close(loc); + } - result fetch(difference_type rows, difference_type &displacement); - result fetch(difference_type rows) + result fetch(difference_type rows, difference_type &displacement, sl); + result fetch(difference_type rows, sl loc) { difference_type d = 0; - return fetch(rows, d); + return fetch(rows, d, loc); } - difference_type move(difference_type rows, difference_type &displacement); - difference_type move(difference_type rows) + difference_type + move(difference_type rows, difference_type &displacement, sl); + difference_type move(difference_type rows, sl loc) { difference_type d = 0; - return move(rows, d); + return move(rows, d, loc); } /// Current position, or -1 for unknown @@ -76,13 +82,13 @@ public: /// Return zero-row result for this cursor. result const &empty_result() const noexcept { return m_empty_result; } - void close() noexcept; + void close(sl loc) 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 &); + void init_empty_result(transaction_base &, sl); /// Connection in which this cursor lives. connection &m_home; @@ -107,9 +113,9 @@ private: }; -PQXX_LIBEXPORT result_size_type obtain_stateless_cursor_size(sql_cursor &); +PQXX_LIBEXPORT result_size_type obtain_stateless_cursor_size(sql_cursor &, sl); PQXX_LIBEXPORT result stateless_cursor_retrieve( sql_cursor &, result::difference_type size, - result::difference_type begin_pos, result::difference_type end_pos); + result::difference_type begin_pos, result::difference_type end_pos, sl); } // namespace pqxx::internal #endif diff --git a/include/pqxx/internal/stream_iterator.hxx b/include/pqxx/internal/stream_iterator.hxx index cd85b5b69..0e7ddf686 100644 --- a/include/pqxx/internal/stream_iterator.hxx +++ b/include/pqxx/internal/stream_iterator.hxx @@ -34,36 +34,36 @@ public: /// Construct an "end" iterator. stream_from_input_iterator() = default; - explicit stream_from_input_iterator(stream_t &home) : m_home(&home) + explicit stream_from_input_iterator(stream_t &home, sl loc) : m_home(&home) { - advance(); + advance(loc); } stream_from_input_iterator(stream_from_input_iterator const &) = default; stream_from_input_iterator &operator++() { - advance(); + advance(sl::current()); return *this; } - value_type const &operator*() const { return m_value; } + value_type const &operator*() const noexcept { return m_value; } /// Comparison only works for comparing to end(). - bool operator==(stream_from_input_iterator const &rhs) const + bool operator==(stream_from_input_iterator const &rhs) const noexcept { return m_home == rhs.m_home; } /// Comparison only works for comparing to end(). - bool operator!=(stream_from_input_iterator const &rhs) const + bool operator!=(stream_from_input_iterator const &rhs) const noexcept { return not(*this == rhs); } private: - void advance() + void advance(sl loc) { if (m_home == nullptr) - throw usage_error{"Moving stream_from iterator beyond end()."}; + throw usage_error{"Moving stream_from iterator beyond end().", loc}; if (not((*m_home) >> m_value)) m_home = nullptr; } @@ -81,7 +81,10 @@ public: using stream_t = stream_from; using iterator = stream_from_input_iterator; explicit stream_input_iteration(stream_t &home) : m_home{home} {} - iterator begin() const { return iterator{m_home}; } + iterator begin(sl loc = sl::current()) const + { + return iterator{m_home, loc}; + } iterator end() const { return {}; } private: diff --git a/include/pqxx/internal/stream_query.hxx b/include/pqxx/internal/stream_query.hxx index 3cd73699b..83ee08622 100644 --- a/include/pqxx/internal/stream_query.hxx +++ b/include/pqxx/internal/stream_query.hxx @@ -82,10 +82,10 @@ public: using line_handle = std::unique_ptr; /// Execute `query` on `tx`, stream results. - inline stream_query(transaction_base &tx, std::string_view query); + inline stream_query(transaction_base &tx, std::string_view query, sl loc); /// Execute `query` on `tx`, stream results. inline stream_query( - transaction_base &tx, std::string_view query, params const &); + transaction_base &tx, std::string_view query, params const &, sl loc); stream_query(stream_query &&) = delete; stream_query &operator=(stream_query &&) = delete; @@ -115,7 +115,7 @@ public: auto end() const & { return stream_query_end_iterator{}; } /// Parse and convert the latest line of data we received. - std::tuple parse_line(zview line) & + std::tuple parse_line(zview line, sl loc) & { assert(not done()); @@ -139,7 +139,7 @@ public: // Folding expression: scan and unescape each field, and convert it to its // requested type. - std::tuple data{parse_field(line, offset, write)...}; + std::tuple data{parse_field(line, offset, write, loc)...}; assert(offset == line_size + 1u); return data; @@ -153,7 +153,7 @@ private: /** This is the only encoding-dependent code in the class. All we need to * store after that is this function pointer. */ - static inline char_finder_func *get_finder(transaction_base const &tx); + static inline char_finder_func *get_finder(transaction_base const &tx, sl); /// Scan and unescape a field into the row buffer. /** The row buffer is `m_row`. @@ -171,7 +171,7 @@ private: * one greater than the size of the line, pointing at the terminating zero. */ std::tuple - read_field(zview line, std::size_t offset, char *write) + read_field(zview line, std::size_t offset, char *write, sl loc) { #if !defined(NDEBUG) auto const line_size{std::size(line)}; @@ -211,7 +211,7 @@ private: assert(lp[offset] != '\0'); // Beginning of the next character of interest (or the end of the line). - auto const stop_char{m_char_finder(line, offset)}; + auto const stop_char{m_char_finder(line, offset, loc)}; PQXX_ASSUME(stop_char > offset); assert(stop_char < (line_size + 1)); @@ -266,14 +266,14 @@ private: * @return Field value converted to TARGET type. */ template - TARGET parse_field(zview line, std::size_t &offset, char *&write) + TARGET parse_field(zview line, std::size_t &offset, char *&write, sl loc) { using field_type = std::remove_cvref_t; using nullity = nullness; assert(offset <= std::size(line)); - auto [new_offset, new_write, text]{read_field(line, offset, write)}; + auto [new_offset, new_write, text]{read_field(line, offset, write, loc)}; PQXX_ASSUME(new_offset > offset); PQXX_ASSUME(new_write >= write); offset = new_offset; @@ -290,7 +290,7 @@ private: if constexpr (nullity::has_null) return nullity::null(); else - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); } else { diff --git a/include/pqxx/internal/stream_query_impl.hxx b/include/pqxx/internal/stream_query_impl.hxx index 829730f79..4e2f247b3 100644 --- a/include/pqxx/internal/stream_query_impl.hxx +++ b/include/pqxx/internal/stream_query_impl.hxx @@ -10,38 +10,43 @@ namespace pqxx::internal { template inline stream_query::stream_query( - transaction_base &tx, std::string_view query) : - transaction_focus{tx, "stream_query"}, m_char_finder{get_finder(tx)} + transaction_base &tx, std::string_view query, sl loc) : + transaction_focus{tx, "stream_query"}, + m_char_finder{get_finder(tx, loc)} { - auto const r{tx.exec(internal::concat("COPY (", query, ") TO STDOUT"))}; - r.expect_columns(sizeof...(TYPE)); - r.expect_rows(0); + auto const r{tx.exec(internal::concat("COPY (", query, ") TO STDOUT"), loc)}; + r.expect_columns(sizeof...(TYPE), loc); + r.expect_rows(0, loc); register_me(); } template inline stream_query::stream_query( - transaction_base &tx, std::string_view query, params const &parms) : - transaction_focus{tx, "stream_query"}, m_char_finder{get_finder(tx)} + transaction_base &tx, std::string_view query, params const &parms, sl loc) : + transaction_focus{tx, "stream_query"}, + m_char_finder{get_finder(tx, loc)} { - auto const r{tx.exec(internal::concat("COPY (", query, ") TO STDOUT"), parms) - .no_rows()}; + auto const r{ + tx.exec(internal::concat("COPY (", query, ") TO STDOUT"), parms, loc) + .no_rows(loc)}; if (r.columns() != sizeof...(TYPE)) - throw usage_error{concat( - "Parsing query stream with wrong number of columns: " - "code expects ", - sizeof...(TYPE), " but query returns ", r.columns(), ".")}; + throw usage_error{ + concat( + "Parsing query stream with wrong number of columns: " + "code expects ", + sizeof...(TYPE), " but query returns ", r.columns(), "."), + loc}; register_me(); } template inline char_finder_func * -stream_query::get_finder(transaction_base const &tx) +stream_query::get_finder(transaction_base const &tx, sl loc) { - auto const group{enc_group(tx.conn().encoding_id())}; - return get_s_char_finder<'\t', '\\'>(group); + auto const group{enc_group(tx.conn().encoding_id(loc), loc)}; + return get_s_char_finder<'\t', '\\'>(group, loc); } @@ -88,7 +93,8 @@ public: /// Dereference. There's no caching in here, so don't repeat calls. value_type operator*() const { - return m_home->parse_line(zview{m_line.get(), m_line_size}); + sl loc{sl::current()}; + return m_home->parse_line(zview{m_line.get(), m_line_size}, loc); } /// Are we at the end? diff --git a/include/pqxx/internal/wait.hxx b/include/pqxx/internal/wait.hxx index 7a82e6553..6cc81b121 100644 --- a/include/pqxx/internal/wait.hxx +++ b/include/pqxx/internal/wait.hxx @@ -1,6 +1,9 @@ #if !defined(PQXX_WAIT_HXX) # define PQXX_WAIT_HXX +# include "pqxx/types.hxx" + + namespace pqxx::internal { /// Wait. @@ -13,6 +16,6 @@ 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); + unsigned microseconds = 0, sl = sl::current()); } // namespace pqxx::internal #endif diff --git a/include/pqxx/nontransaction.hxx b/include/pqxx/nontransaction.hxx index 334c20aef..44eae7ada 100644 --- a/include/pqxx/nontransaction.hxx +++ b/include/pqxx/nontransaction.hxx @@ -72,10 +72,10 @@ public: register_transaction(); } - virtual ~nontransaction() override { close(); } + virtual ~nontransaction() override { close(sl::current()); } private: - virtual void do_commit() override {} + virtual void do_commit(sl) override {} }; } // namespace pqxx #endif diff --git a/include/pqxx/params.hxx b/include/pqxx/params.hxx index 3957d3545..5f00ceb1f 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -146,7 +146,7 @@ public: * 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; + pqxx::internal::c_params make_c_params(sl loc) const; private: /// Recursively append a pack of params. @@ -215,11 +215,13 @@ public: std::string get() const { return std::string(std::data(m_buf), m_len); } /// Move on to the next parameter. - void next() & + void next(sl loc = sl::current()) & { if (m_current >= max_params) - throw range_error{pqxx::internal::concat( - "Too many parameters in one statement: limit is ", max_params, ".")}; + throw range_error{ + pqxx::internal::concat( + "Too many parameters in one statement: limit is ", max_params, "."), + loc}; PQXX_ASSUME(m_current > 0); ++m_current; if (m_current % 10 == 0) @@ -231,7 +233,7 @@ public: char *const end{string_traits::into_buf( data + 1, data + std::size(m_buf), m_current)}; // (Subtract because we don't include the trailing zero.) - m_len = check_cast(end - data, "placeholders counter") - 1; + m_len = check_cast(end - data, "placeholders counter", loc) - 1; } else { diff --git a/include/pqxx/pipeline.hxx b/include/pqxx/pipeline.hxx index 9d39933b1..f6ca05c39 100644 --- a/include/pqxx/pipeline.hxx +++ b/include/pqxx/pipeline.hxx @@ -60,15 +60,17 @@ public: /// Start a pipeline. - explicit pipeline(transaction_base &t) : transaction_focus{t, s_classname} + explicit pipeline(transaction_base &t, sl loc = sl::current()) : + transaction_focus{t, s_classname} { - init(); + init(loc); } /// Start a pipeline. Assign it a name, for more helpful error messages. - pipeline(transaction_base &t, std::string_view tname) : + pipeline( + transaction_base &t, std::string_view tname, sl loc = sl::current()) : transaction_focus{t, s_classname, tname} { - init(); + init(loc); } /// Close the pipeline. @@ -81,7 +83,7 @@ public: * * @return Identifier for this query, unique only within this pipeline. */ - query_id insert(std::string_view) &; + query_id insert(std::string_view, sl = sl::current()) &; /// Wait for all ongoing or pending operations to complete, and detach. /** Detaches from the transaction when done. @@ -90,7 +92,7 @@ public: * 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(); + void complete(sl = sl::current()); /// Forget all ongoing or pending operations and retrieved results. /** Queries already sent to the backend may still be completed, depending @@ -102,7 +104,7 @@ public: * * Detaches from the transaction when done. */ - void flush(); + void flush(sl = sl::current()); /// Cancel ongoing query, if any. /** May cancel any or all of the queries that have been inserted at this @@ -113,7 +115,7 @@ public: * Therefore, either use this function in a nontransaction, or abort the * transaction after calling it. */ - void cancel(); + void cancel(sl = sl::current()); /// Is result for given query available? [[nodiscard]] bool is_finished(query_id) const; @@ -125,14 +127,14 @@ public: * than the one in which their queries were inserted, errors may "propagate" * to subsequent queries. */ - result retrieve(query_id qid) + result retrieve(query_id qid, sl loc = sl::current()) { - return retrieve(m_queries.find(qid)).second; + return retrieve(m_queries.find(qid), loc).second; } /// Retrieve oldest unretrieved result (possibly wait for one). /** @return The query's identifier and its result set. */ - std::pair retrieve(); + std::pair retrieve(sl = sl::current()); [[nodiscard]] bool empty() const noexcept { return std::empty(m_queries); } @@ -153,7 +155,7 @@ public: /// Resume retained query emission. Harmless when not needed. - void resume() &; + void resume(sl loc = sl::current()) &; private: struct PQXX_PRIVATE Query @@ -168,7 +170,7 @@ private: using QueryMap = std::map; - void init(); + void init(sl); void attach(); void detach(); @@ -190,7 +192,7 @@ private: return m_issuedrange.second != m_issuedrange.first; } - PQXX_PRIVATE void issue(); + PQXX_PRIVATE void issue(sl); /// The given query failed; never issue anything beyond that. void set_error_at(query_id qid) noexcept @@ -201,20 +203,21 @@ private: } /// Throw pqxx::internal_error. - [[noreturn]] PQXX_PRIVATE void internal_error(std::string const &err); + [[noreturn]] PQXX_PRIVATE void internal_error(std::string const &err, sl); - PQXX_PRIVATE bool obtain_result(bool expect_none = false); + PQXX_PRIVATE bool obtain_result(bool expect_none, sl); - PQXX_PRIVATE void obtain_dummy(); - PQXX_PRIVATE void get_further_available_results(); + PQXX_PRIVATE void obtain_dummy(sl); + PQXX_PRIVATE void get_further_available_results(sl); PQXX_PRIVATE void check_end_results(); /// Receive any results that happen to be available; it's not urgent. - PQXX_PRIVATE void receive_if_available(); + PQXX_PRIVATE void receive_if_available(sl loc); /// Receive results, up to stop if possible. - PQXX_PRIVATE void receive(pipeline::QueryMap::const_iterator stop); - std::pair retrieve(pipeline::QueryMap::iterator); + PQXX_PRIVATE void receive(pipeline::QueryMap::const_iterator stop, sl); + std::pair + retrieve(pipeline::QueryMap::iterator, sl); QueryMap m_queries; std::pair m_issuedrange; diff --git a/include/pqxx/range.hxx b/include/pqxx/range.hxx index ef5afa8ff..4735a6631 100644 --- a/include/pqxx/range.hxx +++ b/include/pqxx/range.hxx @@ -47,10 +47,12 @@ private: public: inclusive_bound() = delete; - constexpr explicit inclusive_bound(TYPE const &value) : m_value{value} + constexpr explicit inclusive_bound( + TYPE const &value, sl loc = sl::current()) : + m_value{value} { if (is_null(value)) - throw argument_error{"Got null value as an inclusive range bound."}; + throw argument_error{"Got null value as an inclusive range bound.", loc}; } [[nodiscard]] constexpr TYPE const &get() const & noexcept @@ -86,10 +88,12 @@ private: public: exclusive_bound() = delete; - constexpr explicit exclusive_bound(TYPE const &value) : m_value{value} + constexpr explicit exclusive_bound( + TYPE const &value, sl loc = sl::current()) : + m_value{value} { if (is_null(value)) - throw argument_error{"Got null value as an exclusive range bound."}; + throw argument_error{"Got null value as an exclusive range bound.", loc}; } [[nodiscard]] constexpr TYPE const &get() const & noexcept @@ -248,15 +252,18 @@ public: * or * @ref exclusive_bound. */ - constexpr range(range_bound lower, range_bound upper) : + constexpr range( + range_bound lower, range_bound upper, sl loc = sl::current()) : 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(), ").")}; + throw range_error{ + internal::concat( + "Range's lower bound (", *lower.value(), + ") is greater than its upper bound (", *upper.value(), ")."), + loc}; } /// Create an empty range. @@ -408,13 +415,13 @@ template struct string_traits> return generic_to_buf(begin, end, value); } - static inline char * - into_buf(char *begin, char *end, range const &value) + static inline char *into_buf( + char *begin, char *end, range const &value, sl loc = sl::current()) { if (value.empty()) { if ((end - begin) <= std::ssize(s_empty)) - throw conversion_overrun{s_overrun.c_str()}; + throw conversion_overrun{s_overrun.c_str(), loc}; char *here = begin + s_empty.copy(begin, std::size(s_empty)); *here++ = '\0'; return here; @@ -422,7 +429,7 @@ template struct string_traits> else { if (end - begin < 4) - throw conversion_overrun{s_overrun.c_str()}; + throw conversion_overrun{s_overrun.c_str(), loc}; char *here = begin; *here++ = (static_cast(value.lower_bound().is_inclusive() ? '[' : '(')); @@ -436,7 +443,7 @@ template struct string_traits> if (upper != nullptr) here = string_traits::into_buf(here, end, *upper) - 1; if ((end - here) < 2) - throw conversion_overrun{s_overrun.c_str()}; + throw conversion_overrun{s_overrun.c_str(), loc}; *here++ = static_cast(value.upper_bound().is_inclusive() ? ']' : ')'); *here++ = '\0'; @@ -444,10 +451,11 @@ template struct string_traits> } } - [[nodiscard]] static inline range from_string(std::string_view text) + [[nodiscard]] static inline range + from_string(std::string_view text, sl loc = sl::current()) { if (std::size(text) < 3) - throw pqxx::conversion_error{err_bad_input(text)}; + throw pqxx::conversion_error{err_bad_input(text), loc}; bool left_inc{false}; switch (text[0]) { @@ -463,11 +471,11 @@ template struct string_traits> (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)}; + throw pqxx::conversion_error{err_bad_input(text), loc}; return {}; break; - default: throw pqxx::conversion_error{err_bad_input(text)}; + default: throw pqxx::conversion_error{err_bad_input(text), loc}; } // The field parser uses this to track which field it's parsing, and @@ -482,16 +490,16 @@ template struct string_traits> // We reuse the same field parser we use for composite values and arrays. auto const field_parser{ pqxx::internal::specialize_parse_composite_field>( - pqxx::internal::encoding_group::UTF8)}; - field_parser(index, text, pos, lower, last); - field_parser(index, text, pos, upper, last); + pqxx::internal::encoding_group::UTF8, loc)}; + field_parser(index, text, pos, lower, last, loc); + field_parser(index, text, pos, upper, last, loc); // We need one more character: the closing parenthesis or bracket. if (pos != std::size(text)) - throw pqxx::conversion_error{err_bad_input(text)}; + throw pqxx::conversion_error{err_bad_input(text), loc}; char const closing{text[pos - 1]}; if (closing != ')' and closing != ']') - throw pqxx::conversion_error{err_bad_input(text)}; + throw pqxx::conversion_error{err_bad_input(text), loc}; bool const right_inc{closing == ']'}; range_bound lower_bound{no_bound{}}, upper_bound{no_bound{}}; diff --git a/include/pqxx/result.hxx b/include/pqxx/result.hxx index bd0da6f29..4c7be08f4 100644 --- a/include/pqxx/result.hxx +++ b/include/pqxx/result.hxx @@ -142,10 +142,10 @@ public: */ template 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_reverse_iterator rbegin() const noexcept; + [[nodiscard]] const_reverse_iterator crbegin() const noexcept; + [[nodiscard]] const_reverse_iterator rend() const noexcept; + [[nodiscard]] const_reverse_iterator crend() const noexcept; [[nodiscard]] const_iterator begin() const noexcept; [[nodiscard]] const_iterator cbegin() const noexcept; @@ -179,10 +179,10 @@ public: #endif // PQXX_HAVE_MULTIDIM /// Index a row by number, but check that the row number is valid. - row at(size_type) const; + row at(size_type, sl = sl::current()) const; /// Index a field by row number and column number. - field at(size_type, row_size_type) const; + field at(size_type, row_size_type, sl = sl::current()) const; /// Let go of the result's data. /** Use this if you need to deallocate the result data earlier than you can @@ -206,16 +206,19 @@ public: [[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; + [[nodiscard]] row_size_type + column_number(zview name, sl = sl::current()) const; /// Name of column with this number (throws exception if it doesn't exist) - [[nodiscard]] char const *column_name(row_size_type number) const &; + [[nodiscard]] char const * + column_name(row_size_type number, sl = sl::current()) const &; /// Server-side storage size for field of column's type, in bytes. /** Returns the size of the server's internal representation of the column's * data type. A negative value indicates the data type is variable-length. */ - [[nodiscard]] int column_storage(row_size_type number) const; + [[nodiscard]] int + column_storage(row_size_type number, sl = sl::current()) const; /// Type modifier of the column with this number. /** The meaning of modifier values is type-specific; they typically indicate @@ -231,30 +234,34 @@ public: [[nodiscard]] int column_type_modifier(row_size_type number) const noexcept; /// Return column's type, as an OID from the system catalogue. - [[nodiscard]] oid column_type(row_size_type col_num) const; + [[nodiscard]] oid + column_type(row_size_type col_num, sl = sl::current()) const; /// Return column's type, as an OID from the system catalogue. - [[nodiscard]] oid column_type(zview col_name) const + [[nodiscard]] oid column_type(zview col_name, sl loc = sl::current()) const { - return column_type(column_number(col_name)); + return column_type(column_number(col_name, loc)); } /// What table did this column come from? - [[nodiscard]] oid column_table(row_size_type col_num) const; + [[nodiscard]] oid + column_table(row_size_type col_num, sl = sl::current()) const; /// What table did this column come from? - [[nodiscard]] oid column_table(zview col_name) const + [[nodiscard]] oid column_table(zview col_name, sl loc = sl::current()) const { - return column_table(column_number(col_name)); + return column_table(column_number(col_name, loc), loc); } /// What column in its table did this column come from? - [[nodiscard]] row_size_type table_column(row_size_type col_num) const; + [[nodiscard]] row_size_type + table_column(row_size_type col_num, sl = sl::current()) const; /// What column in its table did this column come from? - [[nodiscard]] row_size_type table_column(zview col_name) const + [[nodiscard]] row_size_type + table_column(zview col_name, sl loc = sl::current()) const { - return table_column(column_number(col_name)); + return table_column(column_number(col_name), loc); } //@} @@ -265,7 +272,7 @@ public: /** @return Identifier of inserted row if exactly one row was inserted, or * @ref oid_none otherwise. */ - [[nodiscard]] PQXX_PURE oid inserted_oid() const; + [[nodiscard]] PQXX_PURE oid inserted_oid(sl = sl::current()) const; /// If command was `INSERT`, `UPDATE`, or `DELETE`: number of affected rows. /** @return Number of affected rows if last command was `INSERT`, `UPDATE`, @@ -312,25 +319,30 @@ public: * The parameter types must have conversions from PostgreSQL's string format * defined; see @ref datatypes. */ - template inline void for_each(CALLABLE &&func) const; + template + inline void for_each(CALLABLE &&func, sl = sl::current()) const; /// Check that result contains exactly `n` rows. /** @return The result itself, for convenience. * @throw @ref unexpected_rows if the actual count is not equal to `n`. */ - result expect_rows(size_type n) const + result expect_rows(size_type n, sl loc = sl::current()) const { auto const sz{size()}; if (sz != n) { // TODO: See whether result contains a generated statement. if (not m_query or m_query->empty()) - throw unexpected_rows{pqxx::internal::concat( - "Expected ", n, " row(s) from query, got ", sz, ".")}; + throw unexpected_rows{ + pqxx::internal::concat( + "Expected ", n, " row(s) from query, got ", sz, "."), + loc}; else - throw unexpected_rows{pqxx::internal::concat( - "Expected ", n, " row(s) from query '", *m_query, "', got ", sz, - ".")}; + throw unexpected_rows{ + pqxx::internal::concat( + "Expected ", n, " row(s) from query '", *m_query, "', got ", sz, + "."), + loc}; } return *this; } @@ -339,7 +351,7 @@ public: /** @return @ref pqxx::row * @throw @ref unexpected_rows if the actual count is not equal to `n`. */ - row one_row() const; + row one_row(sl = sl::current()) const; /// Expect that result contains at moost one row, and return as optional. /** Returns an empty `std::optional` if the result is empty, or if it has @@ -347,12 +359,12 @@ public: * * @throw @ref unexpected_rows is the row count is not 0 or 1. */ - std::optional opt_row() const; + std::optional opt_row(sl = sl::current()) const; /// Expect that result contains no rows. Return result for convenience. - result no_rows() const + result no_rows(sl loc = sl::current()) const { - expect_rows(0); + expect_rows(0, loc); return *this; } @@ -360,18 +372,23 @@ public: /** @return The result itself, for convenience. * @throw @ref usage_error otherwise. */ - result expect_columns(row_size_type cols) const + result expect_columns(row_size_type cols, sl loc = sl::current()) const { auto const actual{columns()}; if (actual != cols) { // TODO: See whether result contains a generated statement. if (not m_query or m_query->empty()) - throw usage_error{pqxx::internal::concat( - "Expected 1 column from query, got ", actual, ".")}; + throw usage_error{ + pqxx::internal::concat( + "Expected 1 column from query, got ", actual, "."), + loc}; else - throw usage_error{pqxx::internal::concat( - "Expected 1 column from query '", *m_query, "', got ", actual, ".")}; + throw usage_error{ + pqxx::internal::concat( + "Expected 1 column from query '", *m_query, "', got ", actual, + "."), + loc}; } return *this; } @@ -380,7 +397,7 @@ public: /** @return The one @ref pqxx::field in the result. * @throw @ref usage_error otherwise. */ - field one_field() const; + field one_field(sl = sl::current()) const; private: using data_pointer = std::shared_ptr; @@ -422,7 +439,7 @@ private: std::shared_ptr const &waiters, internal::encoding_group enc); - PQXX_PRIVATE void check_status(std::string_view desc = ""sv) const; + PQXX_PRIVATE void check_status(std::string_view desc, sl loc) const; friend class pqxx::internal::gate::result_connection; friend class pqxx::internal::gate::result_row; @@ -430,9 +447,9 @@ private: operator bool() const noexcept { return m_data.get() != nullptr; } [[noreturn]] PQXX_PRIVATE PQXX_COLD void - throw_sql_error(std::string const &Err, std::string const &Query) const; + throw_sql_error(std::string const &Err, std::string const &Query, sl) const; PQXX_PRIVATE PQXX_PURE int errorposition() const; - PQXX_PRIVATE std::string status_error() const; + PQXX_PRIVATE std::string status_error(sl) const; friend class pqxx::internal::gate::result_sql_cursor; PQXX_PURE char const *cmd_status() const noexcept; diff --git a/include/pqxx/robusttransaction.hxx b/include/pqxx/robusttransaction.hxx index a9618dd99..6f785a048 100644 --- a/include/pqxx/robusttransaction.hxx +++ b/include/pqxx/robusttransaction.hxx @@ -30,8 +30,8 @@ public: protected: basic_robusttransaction( - connection &cx, zview begin_command, std::string_view tname); - basic_robusttransaction(connection &cx, zview begin_command); + connection &cx, zview begin_command, std::string_view tname, sl); + basic_robusttransaction(connection &cx, zview begin_command, sl); private: using IDType = unsigned long; @@ -40,10 +40,10 @@ private: std::string m_xid; int m_backendpid = -1; - void init(zview begin_command); + void init(zview begin_command, sl); // @warning This function will become `final`. - virtual void do_commit() override; + virtual void do_commit(sl) override; }; } // namespace pqxx::internal @@ -105,12 +105,13 @@ public: /** Create robusttransaction of given name. * @param cx Connection inside which this robusttransaction should live. */ - explicit robusttransaction(connection &cx) : + explicit robusttransaction(connection &cx, sl loc = sl::current()) : internal::basic_robusttransaction{ - cx, pqxx::internal::begin_cmd} + cx, pqxx::internal::begin_cmd, + loc} {} - virtual ~robusttransaction() noexcept override { close(); } + virtual ~robusttransaction() noexcept override { close(sl::current()); } }; /** diff --git a/include/pqxx/row.hxx b/include/pqxx/row.hxx index d8ac841f2..dd66cc199 100644 --- a/include/pqxx/row.hxx +++ b/include/pqxx/row.hxx @@ -97,11 +97,13 @@ public: */ [[nodiscard]] reference operator[](zview col_name) const; - reference at(size_type) const; + /// Address a field by number, but check that the number is in range. + reference at(size_type, sl = sl::current()) const; + /** Address field by name. * @warning This is much slower than indexing by number, or iterating. */ - reference at(zview col_name) const; + reference at(zview col_name, sl = sl::current()) const; [[nodiscard]] constexpr size_type size() const noexcept { return m_end; } @@ -116,24 +118,25 @@ public: */ //@{ /// Number of given column (throws exception if it doesn't exist). - [[nodiscard]] size_type column_number(zview col_name) const; + [[nodiscard]] size_type + column_number(zview col_name, sl = sl::current()) const; /// Return a column's type. - [[nodiscard]] oid column_type(size_type) const; + [[nodiscard]] oid column_type(size_type, sl = sl::current()) const; /// Return a column's type. - [[nodiscard]] oid column_type(zview col_name) const + [[nodiscard]] oid column_type(zview col_name, sl loc = sl::current()) const { - return column_type(column_number(col_name)); + return column_type(column_number(col_name, loc), loc); } /// What table did this column come from? - [[nodiscard]] oid column_table(size_type col_num) const; + [[nodiscard]] oid column_table(size_type col_num, sl = sl::current()) const; /// What table did this column come from? - [[nodiscard]] oid column_table(zview col_name) const + [[nodiscard]] oid column_table(zview col_name, sl loc = sl::current()) const { - return column_table(column_number(col_name)); + return column_table(column_number(col_name, loc), loc); } /// What column number in its table did this result column come from? @@ -144,12 +147,13 @@ public: * @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; + [[nodiscard]] size_type table_column(size_type, sl = sl::current()) const; /// What column number in its table did this result column come from? - [[nodiscard]] size_type table_column(zview col_name) const + [[nodiscard]] size_type + table_column(zview col_name, sl loc = sl::current()) const { - return table_column(column_number(col_name)); + return table_column(column_number(col_name, loc), loc); } //@} @@ -167,10 +171,10 @@ public: * @throw usage_error If the number of columns in the `row` does not match * the number of fields in `t`. */ - template void to(Tuple &t) const + template void to(Tuple &t, sl loc = sl::current()) const { - check_size(std::tuple_size_v); - convert(t); + check_size(std::tuple_size_v, loc); + convert(t, loc); } /// Extract entire row's values into a tuple. @@ -182,11 +186,12 @@ public: * @throw usage_error If the number of columns in the `row` does not match * the number of fields in `t`. */ - template std::tuple as() const + template + std::tuple as(sl loc = sl::current()) const { - check_size(sizeof...(TYPE)); + check_size(sizeof...(TYPE), loc); using seq = std::make_index_sequence; - return get_tuple>(seq{}); + return get_tuple>(seq{}, loc); } [[deprecated("Swap iterators, not rows.")]] void swap(row &) noexcept; @@ -197,29 +202,32 @@ protected: row(result 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 + void check_size(size_type expected, sl loc) const { if (size() != expected) - throw usage_error{internal::concat( - "Tried to extract ", expected, " field(s) from a row of ", size(), - ".")}; + throw usage_error{ + internal::concat( + "Tried to extract ", expected, " field(s) from a row of ", size(), + "."), + loc}; } /// 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 TUPLE as_tuple() const + template TUPLE as_tuple(sl loc) const { using seq = std::make_index_sequence>; - return get_tuple(seq{}); + return get_tuple(seq{}, loc); } template friend class pqxx::internal::result_iter; /// Convert entire row to tuple fields, without checking row size. - template void convert(Tuple &t) const + template void convert(Tuple &t, sl loc) const { - extract_fields(t, std::make_index_sequence>{}); + extract_fields( + t, std::make_index_sequence>{}, loc); } friend class field; @@ -239,17 +247,17 @@ protected: private: template - void extract_fields(Tuple &t, std::index_sequence) const + void extract_fields(Tuple &t, std::index_sequence, sl loc) const { - (extract_value(t), ...); + (extract_value(t, loc), ...); } template - void extract_value(Tuple &t) const; + void extract_value(Tuple &t, sl loc) const; /// Convert row's values as a new tuple. template - auto get_tuple(std::index_sequence) const + auto get_tuple(std::index_sequence, sl) const { return std::make_tuple(get_field()...); } @@ -543,7 +551,7 @@ const_row_iterator::operator-(const_row_iterator const &i) const noexcept template -inline void row::extract_value(Tuple &t) const +inline void row::extract_value(Tuple &t, sl) const { using field_type = std::remove_cvref_t(t))>; field const f{m_result, m_index, index}; diff --git a/include/pqxx/stream_from.hxx b/include/pqxx/stream_from.hxx index 09f927263..37c387fd0 100644 --- a/include/pqxx/stream_from.hxx +++ b/include/pqxx/stream_from.hxx @@ -256,7 +256,7 @@ public: * @warning The return type may change in the future, to support C++20 * coroutine-based usage. */ - std::vector const *read_row() &; + std::vector const *read_row(sl loc = sl::current()) &; /// Read a raw line of text from the COPY command. /** @warning Do not use this unless you really know what you're doing. */ @@ -276,9 +276,9 @@ private: std::string_view columns, from_table_t, int); template - void extract_fields(Tuple &t, std::index_sequence) const + void extract_fields(Tuple &t, std::index_sequence, sl loc) const { - (extract_value(t), ...); + (extract_value(t, loc), ...); } pqxx::internal::char_finder_func *m_char_finder; @@ -294,10 +294,10 @@ private: void close(); template - void extract_value(Tuple &) const; + void extract_value(Tuple &, sl loc) const; /// Read a line of COPY data, write `m_row` and `m_fields`. - void parse_line(); + void parse_line(sl); }; @@ -320,13 +320,15 @@ inline stream_from::stream_from( {} +// TODO: How can we pass std::source_location? template inline stream_from &stream_from::operator>>(Tuple &t) { + sl loc{sl::current()}; if (m_finished) [[unlikely]] return *this; static constexpr auto tup_size{std::tuple_size_v}; m_fields.reserve(tup_size); - parse_line(); + parse_line(loc); if (m_finished) [[unlikely]] return *this; @@ -335,13 +337,13 @@ template inline stream_from &stream_from::operator>>(Tuple &t) "Tried to extract ", tup_size, " field(s) from a stream of ", std::size(m_fields), ".")}; - extract_fields(t, std::make_index_sequence{}); + extract_fields(t, std::make_index_sequence{}, loc); return *this; } template -inline void stream_from::extract_value(Tuple &t) const +inline void stream_from::extract_value(Tuple &t, sl loc) const { using field_type = std::remove_cvref_t(t))>; using nullity = nullness; @@ -349,14 +351,14 @@ inline void stream_from::extract_value(Tuple &t) const if constexpr (nullity::always_null) { if (std::data(m_fields[index]) != nullptr) - throw conversion_error{"Streaming non-null value into null field."}; + throw conversion_error{"Streaming non-null value into null field.", loc}; } else if (std::data(m_fields[index]) == nullptr) { if constexpr (nullity::has_null) std::get(t) = nullity::null(); else - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); } else { diff --git a/include/pqxx/stream_to.hxx b/include/pqxx/stream_to.hxx index ff9df1043..565a24d1f 100644 --- a/include/pqxx/stream_to.hxx +++ b/include/pqxx/stream_to.hxx @@ -102,9 +102,10 @@ public: * 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 = "") + transaction_base &tx, std::string_view path, std::string_view columns = "", + sl loc = sl::current()) { - return {tx, path, columns}; + return {tx, path, columns, loc}; } /// Create a `stream_to` writing to a named table and columns. @@ -202,7 +203,8 @@ public: */ template stream_to &operator<<(Row const &row) { - write_row(row); + sl loc{sl::current()}; + write_row(row, loc); return *this; } @@ -220,26 +222,28 @@ public: * * The preferred way to insert a row is @c write_values. */ - template void write_row(Row const &row) + template void write_row(Row const &row, sl loc = sl::current()) { - fill_buffer(row); - write_buffer(); + fill_buffer(row, loc); + write_buffer(loc); } + // TODO: How can we pass std::source_location here? /// Insert values as a row. /** This is the recommended way of inserting data. Pass your field values, * of any convertible type. */ template void write_values(Ts const &...fields) { + auto loc{sl::current()}; fill_buffer(fields...); - write_buffer(); + write_buffer(loc); } private: /// Stream a pre-quoted table name and columns list. stream_to( - transaction_base &tx, std::string_view path, std::string_view columns); + transaction_base &tx, std::string_view path, std::string_view columns, sl); bool m_finished = false; @@ -253,12 +257,12 @@ private: internal::char_finder_func *m_finder; /// Write a row of raw text-format data into the destination table. - void write_raw_line(std::string_view); + void write_raw_line(std::string_view, sl); /// Write a row of data from @c m_buffer into the destination table. /** Resets the buffer for the next row. */ - void write_buffer(); + void write_buffer(sl); /// COPY encoding for a null field, plus subsequent separator. static constexpr std::string_view null_field{"\\N\t"}; @@ -283,7 +287,7 @@ private: } /// Append escaped version of @c data to @c m_buffer, plus a tab. - void escape_field_to_buffer(std::string_view data); + void escape_field_to_buffer(std::string_view data, sl loc); /// Append string representation for @c f to @c m_buffer. /** This is for the general case, where the field may contain a value. @@ -294,7 +298,7 @@ private: */ template std::enable_if_t::always_null> - append_to_buffer(Field const &f) + append_to_buffer(Field const &f, sl loc) { // 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 @@ -335,7 +339,7 @@ private: { // This string may need escaping. m_field_buf.resize(budget); - escape_field_to_buffer(f); + escape_field_to_buffer(f, loc); } else if constexpr ( std::is_same_v> or @@ -345,7 +349,7 @@ private: // Optional string. It's not null (we checked for that above), so... // Treat like a string. m_field_buf.resize(budget); - escape_field_to_buffer(f.value()); + escape_field_to_buffer(f.value(), loc); } // TODO: Support deleter template argument on unique_ptr. else if constexpr ( @@ -360,7 +364,7 @@ private: // Effectively also an optional string. It's not null (we checked // for that above). m_field_buf.resize(budget); - escape_field_to_buffer(*f); + escape_field_to_buffer(*f, loc); } else { @@ -369,7 +373,7 @@ private: 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)); + traits::to_buf(data, data + std::size(m_field_buf), f), loc); } } } @@ -383,7 +387,7 @@ private: */ template std::enable_if_t::always_null> - append_to_buffer(Field const &) + append_to_buffer(Field const &, sl) { m_buffer.append(null_field); } @@ -392,7 +396,7 @@ private: template std::enable_if_t< not std::is_same_v, char>> - fill_buffer(Container const &c) + fill_buffer(Container const &c, sl loc) { // To avoid unnecessary allocations and deallocations, we run through c // twice: once to determine how much buffer space we may need, and once to @@ -400,7 +404,7 @@ private: 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); + for (auto const &f : c) append_to_buffer(f, loc); } /// Estimate how many buffer bytes we need to write tuple. @@ -413,24 +417,26 @@ private: /// Write tuple of fields to @c m_buffer. template - void append_tuple(Tuple const &t, std::index_sequence) + void append_tuple(Tuple const &t, std::index_sequence, sl loc) { - (append_to_buffer(std::get(t)), ...); + (append_to_buffer(std::get(t), loc), ...); } /// Write raw COPY line into @c m_buffer, based on a tuple of fields. - template void fill_buffer(std::tuple const &t) + template + void fill_buffer(std::tuple const &t, sl loc) { using indexes = std::make_index_sequence; m_buffer.reserve(budget_tuple(t, indexes{})); - append_tuple(t, indexes{}); + append_tuple(t, indexes{}, loc); } + // TODO: How can we pass std::source_location here? /// Write raw COPY line into @c m_buffer, based on varargs fields. template void fill_buffer(const Ts &...fields) { - (..., append_to_buffer(fields)); + (..., append_to_buffer(fields, sl::current())); } constexpr static std::string_view s_classname{"stream_to"}; diff --git a/include/pqxx/subtransaction.hxx b/include/pqxx/subtransaction.hxx index bb9564c07..ee1712115 100644 --- a/include/pqxx/subtransaction.hxx +++ b/include/pqxx/subtransaction.hxx @@ -40,6 +40,7 @@ namespace pqxx * do_firstpart(tx); * * // Attempt to delete our temporary table if it already existed. + * // (In reality you would just use the IF EXISTS option to DROP TABLE.) * try * { * subtransaction S(tx, "droptemp"); @@ -78,10 +79,17 @@ class PQXX_LIBEXPORT subtransaction : public transaction_focus, { public: /// Nest a subtransaction nested in another transaction. - explicit subtransaction(dbtransaction &t, std::string_view tname = ""sv); + explicit subtransaction( + dbtransaction &t, std::string_view tname, sl = sl::current()); + + /// Nest a subtransaction nested in another transaction. + explicit subtransaction(dbtransaction &t, sl loc = sl::current()) : + subtransaction(t, "", loc) + {} /// Nest a subtransaction in another subtransaction. - explicit subtransaction(subtransaction &t, std::string_view name = ""sv); + explicit subtransaction( + subtransaction &t, std::string_view name = ""sv, sl loc = sl::current()); virtual ~subtransaction() noexcept override; @@ -90,7 +98,7 @@ private: { return quote_name(transaction_focus::name()); } - virtual void do_commit() override; + virtual void do_commit(sl) override; }; } // namespace pqxx #endif diff --git a/include/pqxx/time.hxx b/include/pqxx/time.hxx index effed05e0..7976562af 100644 --- a/include/pqxx/time.hxx +++ b/include/pqxx/time.hxx @@ -65,11 +65,12 @@ template<> struct PQXX_LIBEXPORT string_traits return generic_to_buf(begin, end, value); } - static char * - into_buf(char *begin, char *end, std::chrono::year_month_day const &value); + static char *into_buf( + char *begin, char *end, std::chrono::year_month_day const &value, + sl = sl::current()); [[nodiscard]] static std::chrono::year_month_day - from_string(std::string_view text); + from_string(std::string_view text, sl = sl::current()); [[nodiscard]] static std::size_t size_buffer(std::chrono::year_month_day const &) noexcept diff --git a/include/pqxx/transaction.hxx b/include/pqxx/transaction.hxx index 6e6844ce1..81bb4b569 100644 --- a/include/pqxx/transaction.hxx +++ b/include/pqxx/transaction.hxx @@ -25,14 +25,17 @@ class PQXX_LIBEXPORT basic_transaction : public dbtransaction { protected: basic_transaction( - connection &cx, zview begin_command, std::string_view tname); - basic_transaction(connection &cx, zview begin_command, std::string &&tname); - basic_transaction(connection &cx, zview begin_command); + connection &cx, zview begin_command, std::string_view tname, + sl = sl::current()); + basic_transaction( + connection &cx, zview begin_command, std::string &&tname, + sl = sl::current()); + basic_transaction(connection &cx, zview begin_command, sl = sl::current()); virtual ~basic_transaction() noexcept override = 0; private: - virtual void do_commit() override; + virtual void do_commit(sl) override; }; } // namespace pqxx::internal @@ -77,9 +80,9 @@ public: * @param tname Optional name for transaction. Must begin with a letter and * may contain letters and digits only. */ - transaction(connection &cx, std::string_view tname) : + transaction(connection &cx, std::string_view tname, sl loc = sl::current()) : internal::basic_transaction{ - cx, internal::begin_cmd, tname} + cx, internal::begin_cmd, tname, loc} {} /// Begin a transaction. @@ -87,12 +90,12 @@ public: * @param cx Connection for this transaction to operate on. * may contain letters and digits only. */ - explicit transaction(connection &cx) : + explicit transaction(connection &cx, sl loc = sl::current()) : internal::basic_transaction{ - cx, internal::begin_cmd} + cx, internal::begin_cmd, loc} {} - virtual ~transaction() noexcept override { close(); } + virtual ~transaction() noexcept override { close(sl::current()); } }; diff --git a/include/pqxx/transaction_base.hxx b/include/pqxx/transaction_base.hxx index 19942e01e..17dd839cf 100644 --- a/include/pqxx/transaction_base.hxx +++ b/include/pqxx/transaction_base.hxx @@ -172,13 +172,13 @@ public: * @ref robusttransaction which takes some special precautions to reduce this * risk. */ - void commit(); + void commit(sl = sl::current()); /// Abort the transaction. /** No special effort is required to call this function; it will be called * implicitly when the transaction is destructed. */ - void abort(); + void abort(sl = sl::current()); /** * @ingroup escaping-functions @@ -190,12 +190,19 @@ public: * functions on the connection object. */ //@{ + [[nodiscard]] std::string esc(char const str[], sl loc = sl::current()) + { + return conn().esc(str, loc); + } + + // TODO: De-templatise this so we can pass std::source_location. /// Escape string for use as SQL string literal in this transaction. template [[nodiscard]] auto esc(ARGS &&...args) const { return conn().esc(std::forward(args)...); } + // TODO: De-templatise this so we can pass std::source_location. /// Escape binary data for use as SQL string literal in this transaction. /** Raw, binary data is treated differently from regular strings. Binary * strings are never interpreted as text, so they may safely include byte @@ -217,30 +224,35 @@ public: /** Takes a binary string as escaped by PostgreSQL, and returns a restored * copy of the original binary data. */ - [[nodiscard]] bytes unesc_bin(zview text) { return conn().unesc_bin(text); } + [[nodiscard]] bytes unesc_bin(zview text, sl loc = sl::current()) + { + return conn().unesc_bin(text, loc); + } /// Unescape binary data, e.g. from a `bytea` field. /** Takes a binary string as escaped by PostgreSQL, and returns a restored * copy of the original binary data. */ - [[nodiscard]] bytes unesc_bin(char const text[]) + [[nodiscard]] bytes unesc_bin(char const text[], sl loc = sl::current()) { - return conn().unesc_bin(text); + return conn().unesc_bin(text, loc); } /// Represent object as SQL string, including quoting & escaping. /** Nulls are recognized and represented as SQL nulls. */ - template [[nodiscard]] std::string quote(T const &t) const + template + [[nodiscard]] std::string quote(T const &t, sl loc = sl::current()) const { - return conn().quote(t); + return conn().quote(t, loc); } /// Binary-escape and quote a binary string for use as an SQL constant. /** For binary data you can also just use @ref quote(data). */ template - [[nodiscard]] std::string quote_raw(DATA const &data) const + [[nodiscard]] std::string + quote_raw(DATA const &data, sl loc = sl::current()) const { - return conn().quote_raw(data); + return conn().quote_raw(data, loc); } /// Escape an SQL identifier for use in a query. @@ -250,10 +262,11 @@ public: } /// Escape string for literal LIKE match. - [[nodiscard]] std::string - esc_like(std::string_view bin, char escape_char = '\\') const + [[nodiscard]] std::string esc_like( + std::string_view bin, char escape_char = '\\', + sl loc = sl::current()) const { - return conn().esc_like(bin, escape_char); + return conn().esc_like(bin, escape_char, loc); } //@} @@ -299,13 +312,14 @@ public: * @return A result set describing the query's or command's result. */ [[deprecated("The desc parameter is going away.")]] - result exec(std::string_view query, std::string_view desc); + result + exec(std::string_view query, std::string_view desc, sl = sl::current()); // TODO: Wrap PQdescribePrepared(). - result exec(std::string_view query, params parms) + result exec(std::string_view query, params parms, sl loc = sl::current()) { - return internal_exec_params(query, parms.make_c_params()); + return internal_exec_params(query, parms.make_c_params(loc), loc); } /// Execute a command. @@ -313,7 +327,7 @@ public: * @param query Query or command to execute. * @return A result set describing the query's or command's result. */ - result exec(std::string_view query); + result exec(std::string_view query, sl = sl::current()); /// Execute a command. /** @@ -427,9 +441,9 @@ public: * @throw unexpected_rows If the query did not return exactly 1 row. * @throw usage_error If the row did not contain exactly 1 field. */ - template TYPE query_value(zview query) + template TYPE query_value(zview query, sl loc = sl::current()) { - return exec(query).one_field().as(); + return exec(query, loc).one_field(loc).as(loc); } /// Perform query returning exactly one row, and convert its fields. @@ -441,9 +455,12 @@ public: * the number of fields in the tuple. */ template - [[nodiscard]] std::tuple query1(zview query) + [[nodiscard]] std::tuple query1(zview query, sl loc = sl::current()) { - return exec(query).expect_columns(sizeof...(TYPE)).one_row().as(); + return exec(query, loc) + .expect_columns(sizeof...(TYPE), loc) + .one_row(loc) + .as(loc); } /// Query at most one row of data, and if there is one, convert it. @@ -455,15 +472,17 @@ public: * the number of fields in the tuple. */ template - [[nodiscard]] std::optional> query01(zview query) + [[nodiscard]] std::optional> + query01(zview query, sl loc = sl::current()) { - std::optional const r{exec(query).opt_row()}; + std::optional const r{exec(query, loc).opt_row(loc)}; if (r) - return {r->as()}; + return {r->as(loc)}; else return {}; } + // C++20: Update type requirements. /// Execute a query, in streaming fashion; loop over the results row by row. /** Converts the rows to `std::tuple`, of the column types you specify. * @@ -517,18 +536,18 @@ public: * requirements may loosen a bit once libpqxx moves on to C++20. */ template - [[nodiscard]] auto stream(std::string_view query) & + [[nodiscard]] auto stream(std::string_view query, sl loc = sl::current()) & { - return pqxx::internal::stream_query{*this, query}; + return pqxx::internal::stream_query{*this, query, loc}; } /// Execute a query, in streaming fashion; loop over the results row by row. /** Like @ref stream(std::string_view), but with parameters. */ template - [[nodiscard]] auto stream(std::string_view query, params parms) & + [[nodiscard]] auto stream(std::string_view query, params parms, sl loc) & { - return pqxx::internal::stream_query{*this, query, parms}; + return pqxx::internal::stream_query{*this, query, parms, loc}; } // C++20: Concept like std::invocable, but without specifying param types. @@ -562,21 +581,23 @@ public: * @ref datatypes. */ template - auto for_stream(std::string_view query, CALLABLE &&func) + auto + for_stream(std::string_view query, CALLABLE &&func, sl loc = sl::current()) { + // TODO: Can we pass loc into func if appropriate? using param_types = pqxx::internal::strip_types_t>; param_types const *const sample{nullptr}; - auto data_stream{stream_like(query, sample)}; + auto data_stream{stream_like(query, sample, loc)}; for (auto const &fields : data_stream) std::apply(func, fields); } template [[deprecated( "pqxx::transaction_base::for_each is now called for_stream.")]] auto - for_each(std::string_view query, CALLABLE &&func) + for_each(std::string_view query, CALLABLE &&func, sl loc = sl::current()) { - return for_stream(query, std::forward(func)); + return for_stream(query, std::forward(func), loc); } /// Execute query, read full results, then iterate rows of data. @@ -611,17 +632,17 @@ public: * @return Something you can iterate using "range `for`" syntax. The actual * type details may change. */ - template auto query(zview query) + template auto query(zview query, sl loc = sl::current()) { - return exec(query).iter(); + return exec(query, loc).iter(); } /// Perform query, expect given number of rows, iterate results. template [[deprecated("Use query() instead, and call expect_rows() on the result.")]] - auto query_n(result::size_type rows, zview query) + auto query_n(result::size_type rows, zview query, sl loc = sl::current()) { - return exec(query).expect_rows(rows).iter(); + return exec(query, loc).expect_rows(rows, loc).iter(); } // C++20: Concept like std::invocable, but without specifying param types. @@ -634,9 +655,10 @@ public: * 2. The `exec` functions are faster for small results, but slower for large * results. */ - template void for_query(zview query, CALLABLE &&func) + template + void for_query(zview query, CALLABLE &&func, sl loc = sl::current()) { - exec(query).for_each(std::forward(func)); + exec(query).for_each(std::forward(func), loc); } /** @@ -678,7 +700,7 @@ public: [[deprecated("Use exec(zview, params) instead.")]] result exec_params(std::string_view query, Args &&...args) { - return exec(query, params{args...}); + return exec(query, params{args...}, sl::current()); } // Execute parameterised statement, expect a single-row result. @@ -709,8 +731,10 @@ public: [[deprecated("Use exec(), and call expect_rows() on the result.")]] result exec_params_n(std::size_t rows, zview query, Args &&...args) { - return exec(query, params{args...}) - .expect_rows(check_cast(rows, "number of rows")); + sl loc{sl::current()}; + return exec(query, params{args...}, loc) + .expect_rows( + check_cast(rows, "number of rows", loc), loc); } // Execute parameterised statement, expect exactly a given number of rows. @@ -757,9 +781,10 @@ public: * @return Something you can iterate using "range `for`" syntax. The actual * type details may change. */ - template auto query(zview query, params const &parms) + template + auto query(zview query, params const &parms, sl loc = sl::current()) { - return exec(query, parms).iter(); + return exec(query, parms, loc).iter(); } /// Perform query parameterised, expect given number of rows, iterate @@ -786,9 +811,13 @@ public: * @throw unexpected_rows If the query did not return exactly 1 row. * @throw usage_error If the row did not contain exactly 1 field. */ - template TYPE query_value(zview query, params const &parms) + template + TYPE query_value(zview query, params const &parms, sl loc = sl::current()) { - return exec(query, parms).expect_columns(1).one_field().as(); + return exec(query, parms, loc) + .expect_columns(1, loc) + .one_field(loc) + .as(loc); } /// Perform query returning exactly one row, and convert its fields. @@ -801,9 +830,10 @@ public: */ template [[nodiscard]] - std::tuple query1(zview query, params const &parms) + std::tuple + query1(zview query, params const &parms, sl loc = sl::current()) { - return exec(query, parms).one_row().as(); + return exec(query, parms, loc).one_row(loc).as(loc); } /// Query at most one row of data, and if there is one, convert it. @@ -816,11 +846,11 @@ public: */ template [[nodiscard]] std::optional> - query01(zview query, params const &parms) + query01(zview query, params const &parms, sl loc = sl::current()) { - std::optional r{exec(query, parms).opt_row()}; + std::optional r{exec(query, parms, loc).opt_row(loc)}; if (r) - return {r->as()}; + return {r->as(loc)}; else return {}; } @@ -839,9 +869,10 @@ public: * results. */ template - void for_query(zview query, CALLABLE &&func, params const &parms) + void for_query( + zview query, CALLABLE &&func, params const &parms, sl loc = sl::current()) { - exec(query, parms).for_each(std::forward(func)); + exec(query, parms, loc).for_each(std::forward(func), loc); } /// Send a notification. @@ -860,7 +891,25 @@ public: * receive. If you leave this out, they will receive an empty string as the * payload. */ - void notify(std::string_view channel, std::string_view payload = {}); + void notify( + std::string_view channel, std::string_view payload, sl = sl::current()); + + /// Send a notification (without payload). + /** Convenience shorthand for executing a "NOTIFY" command. Most of the + * logic for handling _incoming_ notifications is in @ref pqxx::connection + * (particularly @ref pqxx::connection::listen), but _outgoing_ + * notifications happen here. + * + * Unless this transaction is a nontransaction, the actual notification only + * goes out once the outer transaction is committed. + * + * @param channel Name of the "channel" on which clients will need to be + * listening in order to receive this notification. + */ + void notify(std::string_view channel, sl loc = sl::current()) + { + notify(channel, {}, loc); + } //@} /// Execute a prepared statement, with optional arguments. @@ -872,10 +921,10 @@ public: } /// Execute a prepared statement taking no parameters. - result exec(prepped statement) + result exec(prepped statement, sl loc = sl::current()) { params pp; - return internal_exec_prepared(statement, pp.make_c_params()); + return internal_exec_prepared(statement, pp.make_c_params(loc), loc); } /// Execute prepared statement, read full results, iterate rows of data. @@ -885,9 +934,35 @@ public: * type details may change. */ template - auto query(prepped statement, params const &parms = {}) + auto query(prepped statement, params const &parms, sl loc = sl::current()) { - return exec(statement, parms).iter(); + return exec(statement, parms, loc).iter(); + } + + /// Execute prepared statement, read full results, iterate rows of data. + /** Like @ref query(zview), but using a prepared statement. + * + * @return Something you can iterate using "range `for`" syntax. The actual + * type details may change. + */ + template + auto query(prepped statement, sl loc = sl::current()) + { + return exec(statement, {}, loc).iter(); + } + + /// Perform prepared statement returning exactly 1 value. + /** This is just like @ref query_value(zview), but using a prepared + * statement. + */ + template + TYPE + query_value(prepped statement, params const &parms, sl loc = sl::current()) + { + return exec(statement, parms, loc) + .expect_columns(1, loc) + .one_field(loc) + .as(loc); } /// Perform prepared statement returning exactly 1 value. @@ -895,9 +970,12 @@ public: * statement. */ template - TYPE query_value(prepped statement, params const &parms = {}) + TYPE query_value(prepped statement, sl loc = sl::current()) { - return exec(statement, parms).expect_columns(1).one_field().as(); + return exec(statement, {}, loc) + .expect_columns(1, loc) + .one_field(loc) + .as(loc); } // C++20: Concept like std::invocable, but without specifying param types. @@ -905,18 +983,27 @@ public: /** This is just like @ref for_query(zview), but using a prepared statement. */ template - void for_query(prepped statement, CALLABLE &&func, params const &parms = {}) + void for_query( + prepped statement, CALLABLE &&func, params const &parms, + sl loc = sl::current()) { - exec(statement, parms).for_each(std::forward(func)); + exec(statement, parms, loc).for_each(std::forward(func), loc); } - // TODO: stream() with prepped. - // TODO: stream_like() with prepped. + // C++20: Concept like std::invocable, but without specifying param types. + /// Execute prepared statement, load result, perform `func` for each row. + /** This is just like @ref for_query(zview), but using a prepared statement. + */ + template + void for_query(prepped statement, CALLABLE &&func, sl loc = sl::current()) + { + exec(statement, {}, loc).for_each(std::forward(func), loc); + } /// Execute a prepared statement with parameters. - result exec(prepped statement, params const &parms) + result exec(prepped statement, params const &parms, sl loc = sl::current()) { - return internal_exec_prepared(statement, parms.make_c_params()); + return internal_exec_prepared(statement, parms.make_c_params(loc), loc); } /// Execute a prepared statement, and expect a single-row result. @@ -927,7 +1014,8 @@ public: "Use exec(string_view, params) and call one_row() on the result.")]] row exec_prepared1(zview statement, Args &&...args) { - return exec(prepped{statement}, params{args...}).one_row(); + sl loc{sl::current()}; + return exec(prepped{statement}, params{args...}).one_row(loc); } /// Execute a prepared statement, and expect a result with zero rows. @@ -938,7 +1026,8 @@ public: "Use exec(prepped, params), and call no_rows() on the result.")]] result exec_prepared0(zview statement, Args &&...args) { - return exec(prepped{statement}, params{args...}).no_rows(); + sl loc{sl::current()}; + return exec(prepped{statement}, params{args...}).no_rows(loc); } /// Execute a prepared statement, expect a result with given number of rows. @@ -951,7 +1040,9 @@ public: result exec_prepared_n(result::size_type rows, zview statement, Args &&...args) { - return exec(pqxx::prepped{statement}, params{args...}).expect_rows(rows); + sl loc{sl::current()}; + return exec(pqxx::prepped{statement}, params{args...}) + .expect_rows(rows, loc); } /** @@ -1025,16 +1116,16 @@ protected: void register_transaction(); /// End transaction. To be called by implementing class' destructor. - void close() noexcept; + void close(sl = sl::current()) noexcept; /// To be implemented by derived implementation class: commit transaction. - virtual void do_commit() = 0; + virtual void do_commit(sl) = 0; /// Transaction type-specific way of aborting a transaction. /** @warning This will become "final", since this function can be called * from the implementing class destructor. */ - virtual void do_abort(); + virtual void do_abort(sl); /// Set the rollback command. void set_rollback_cmd(std::shared_ptr cmd) @@ -1043,9 +1134,16 @@ protected: } /// Execute query on connection directly. - result direct_exec(std::string_view, std::string_view desc = ""sv); - result - direct_exec(std::shared_ptr, std::string_view desc = ""sv); + result direct_exec(std::string_view, std::string_view desc, sl); + result direct_exec(std::string_view query, sl loc) + { + return direct_exec(query, "", loc); + } + result direct_exec(std::shared_ptr, std::string_view desc, sl); + result direct_exec(std::shared_ptr query, sl loc) + { + return direct_exec(query, "", loc); + } private: enum class status @@ -1059,10 +1157,10 @@ private: PQXX_PRIVATE void check_pending_error(); result internal_exec_prepared( - std::string_view statement, internal::c_params const &args); + std::string_view statement, internal::c_params const &args, sl); - result - internal_exec_params(std::string_view query, internal::c_params const &args); + result internal_exec_params( + std::string_view query, internal::c_params const &args, sl); /// Describe this transaction to humans, e.g. "transaction 'foo'". [[nodiscard]] std::string description() const; @@ -1075,9 +1173,11 @@ private: /// Like @ref stream(), but takes a tuple rather than a parameter pack. template - auto stream_like(std::string_view query, std::tuple const *) + auto stream_like( + std::string_view query, std::tuple const *, + sl loc = sl::current()) { - return stream(query); + return stream(query, loc); } connection &m_conn; diff --git a/include/pqxx/transactor.hxx b/include/pqxx/transactor.hxx index a188941b1..79bb16402 100644 --- a/include/pqxx/transactor.hxx +++ b/include/pqxx/transactor.hxx @@ -97,7 +97,8 @@ namespace pqxx * @return Whatever your callback returns. */ template -inline auto perform(TRANSACTION_CALLBACK &&callback, int attempts = 3) +inline auto +perform(TRANSACTION_CALLBACK &&callback, int attempts, sl loc = sl::current()) -> std::invoke_result_t { if (attempts <= 0) @@ -145,7 +146,45 @@ inline auto perform(TRANSACTION_CALLBACK &&callback, int attempts = 3) continue; } } - throw pqxx::internal_error{"No outcome reached on perform()."}; + throw pqxx::internal_error{"No outcome reached on perform().", loc}; +} + + +/// Simple way to execute a transaction with automatic retry. +/** + * Executes your transaction code as a callback. Repeats it until it completes + * normally, or it throws an error other than the few libpqxx-generated + * exceptions that the framework understands, or after a given number of failed + * attempts, or if the transaction ends in an "in-doubt" state. + * + * (An in-doubt state is one where libpqxx cannot determine whether the server + * finally committed a transaction or not. This can happen if the network + * connection to the server is lost just while we're waiting for its reply to + * a "commit" statement. The server may have completed the commit, or not, but + * it can't tell you because there's no longer a connection. + * + * Using this still takes a bit of care. If your callback makes use of data + * from the database, you'll probably have to query that data within your + * callback. If the attempt to perform your callback fails, and the framework + * tries again, you'll be in a new transaction and the data in the database may + * have changed under your feet. + * + * Also be careful about changing variables or data structures from within + * your callback. The run may still fail, and perhaps get run again. The + * ideal way to do it (in most cases) is to return your result from your + * callback, and change your program's data state only after @ref perform + * completes successfully. + * + * @param callback Transaction code that can be called with no arguments. + * @param attempts Maximum number of times to attempt performing callback. + * Must be greater than zero. + * @return Whatever your callback returns. + */ +template +inline auto perform(TRANSACTION_CALLBACK &&callback, sl loc = sl::current()) + -> std::invoke_result_t +{ + return perform(callback, 3, loc); } } // namespace pqxx //@} diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index 523693db9..11fb0ad72 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -17,11 +17,15 @@ #include #include #include +#include #include namespace pqxx { +/// Conenience alias for `std::source_location`. It's just too long. +using sl = std::source_location; + /// Number of rows in a result set. using result_size_type = int; diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index 64aedfa13..dd2c4d672 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -88,7 +88,7 @@ template inline constexpr void ignore_unused(T &&...) noexcept * or both floating-point types. */ template -inline TO check_cast(FROM value, std::string_view description) +inline TO check_cast(FROM value, std::string_view description, sl loc) { static_assert(std::is_arithmetic_v); static_assert(std::is_arithmetic_v); @@ -110,7 +110,8 @@ inline TO check_cast(FROM value, std::string_view description) if constexpr (std::is_signed_v) { if (value < to_limits::lowest()) - throw range_error{internal::cat2("Cast underflow: "sv, description)}; + throw range_error{ + internal::cat2("Cast underflow: "sv, description), loc}; } else { @@ -118,8 +119,10 @@ inline TO check_cast(FROM value, std::string_view description) // there may not be a good broader type in which the compiler can even // perform our check. if (value < 0) - throw range_error{internal::cat2( - "Casting negative value to unsigned type: "sv, description)}; + throw range_error{ + internal::cat2( + "Casting negative value to unsigned type: "sv, description), + loc}; } } else @@ -137,13 +140,14 @@ inline TO check_cast(FROM value, std::string_view description) if constexpr (from_max > to_max) { if (std::cmp_greater(value, to_max)) - throw range_error{internal::cat2("Cast overflow: "sv, description)}; + throw range_error{ + internal::cat2("Cast overflow: "sv, description), loc}; } } else if constexpr ((from_limits::max)() > (to_limits::max)()) { if (value > (to_limits::max)()) - throw range_error{internal::cat2("Cast overflow: ", description)}; + throw range_error{internal::cat2("Cast overflow: ", description), loc}; } return static_cast(value); @@ -457,11 +461,11 @@ std::string PQXX_LIBEXPORT esc_bin(bytes_view binary_data); /// Reconstitute binary data from its escaped version. void PQXX_LIBEXPORT -unesc_bin(std::string_view escaped_data, std::byte buffer[]); +unesc_bin(std::string_view escaped_data, std::byte buffer[], sl loc); /// Reconstitute binary data from its escaped version. -bytes PQXX_LIBEXPORT unesc_bin(std::string_view escaped_data); +bytes PQXX_LIBEXPORT unesc_bin(std::string_view escaped_data, sl loc); /// Helper for determining a function's parameter types. diff --git a/src/array.cxx b/src/array.cxx index 5d5594ee4..5d76af9d0 100644 --- a/src/array.cxx +++ b/src/array.cxx @@ -28,39 +28,39 @@ namespace pqxx /// Scan to next glyph in the buffer. Assumes there is one. template [[nodiscard]] std::string::size_type -array_parser::scan_glyph(std::string::size_type pos) const +array_parser::scan_glyph(std::string::size_type pos, sl loc) const { return pqxx::internal::glyph_scanner::call( - std::data(m_input), std::size(m_input), pos); + std::data(m_input), std::size(m_input), pos, loc); } /// Scan to next glyph in a substring. Assumes there is one. template std::string::size_type array_parser::scan_glyph( - std::string::size_type pos, std::string::size_type end) const + std::string::size_type pos, std::string::size_type end, sl loc) const { return pqxx::internal::glyph_scanner::call( - std::data(m_input), end, pos); + std::data(m_input), end, pos, loc); } /// Find the end of a double-quoted SQL string in an SQL array. template -std::string::size_type array_parser::scan_double_quoted_string() const +std::string::size_type array_parser::scan_double_quoted_string(sl loc) const { return pqxx::internal::scan_double_quoted_string( - std::data(m_input), std::size(m_input), m_pos); + std::data(m_input), std::size(m_input), m_pos, loc); } /// Parse a double-quoted SQL string: un-quote it and un-escape it. template -std::string -array_parser::parse_double_quoted_string(std::string::size_type end) const +std::string array_parser::parse_double_quoted_string( + std::string::size_type end, sl loc) const { return pqxx::internal::parse_double_quoted_string( - std::data(m_input), end, m_pos); + std::data(m_input), end, m_pos, loc); } @@ -68,10 +68,10 @@ array_parser::parse_double_quoted_string(std::string::size_type end) const /** Assumes UTF-8 or an ASCII-superset single-byte encoding. */ template -std::string::size_type array_parser::scan_unquoted_string() const +std::string::size_type array_parser::scan_unquoted_string(sl loc) const { return pqxx::internal::scan_unquoted_string( - std::data(m_input), std::size(m_input), m_pos); + std::data(m_input), std::size(m_input), m_pos, loc); } @@ -81,49 +81,52 @@ std::string::size_type array_parser::scan_unquoted_string() const */ template std::string_view -array_parser::parse_unquoted_string(std::string::size_type end) const +array_parser::parse_unquoted_string(std::string::size_type end, sl loc) const { return pqxx::internal::parse_unquoted_string( - std::data(m_input), end, m_pos); + std::data(m_input), end, m_pos, loc); } array_parser::array_parser( std::string_view input, internal::encoding_group enc) : - m_input{input}, m_impl{specialize_for_encoding(enc)} + m_input{input}, m_impl{specialize_for_encoding(enc, sl::current())} {} template -std::pair array_parser::parse_array_step() +std::pair +array_parser::parse_array_step(sl loc) { std::string value{}; if (m_pos >= std::size(m_input)) return std::make_pair(juncture::done, value); - auto [found, end] = [this, &value] { - if (scan_glyph(m_pos) - m_pos > 1) + auto [found, end] = [this, &value, loc] { + if (scan_glyph(m_pos, loc) - m_pos > 1) { // Non-ASCII unquoted string. - auto const endpoint = scan_unquoted_string(); - value = parse_unquoted_string(endpoint); + auto const endpoint = scan_unquoted_string(loc); + value = parse_unquoted_string(endpoint, loc); return std::tuple{juncture::string_value, endpoint}; } else switch (m_input[m_pos]) { - case '\0': throw failure{"Unexpected zero byte in array."}; - case '{': return std::tuple{juncture::row_start, scan_glyph(m_pos)}; - case '}': return std::tuple{juncture::row_end, scan_glyph(m_pos)}; + case '\0': throw failure{"Unexpected zero byte in array.", loc}; + case '{': + return std::tuple{juncture::row_start, scan_glyph(m_pos, loc)}; + case '}': + return std::tuple{juncture::row_end, scan_glyph(m_pos, loc)}; case '"': { - auto const endpoint = scan_double_quoted_string(); - value = parse_double_quoted_string(endpoint); + auto const endpoint = scan_double_quoted_string(loc); + value = parse_double_quoted_string(endpoint, loc); return std::tuple{juncture::string_value, endpoint}; } default: { - auto const endpoint = scan_unquoted_string(); - value = parse_unquoted_string(endpoint); + auto const endpoint = scan_unquoted_string(loc); + value = parse_unquoted_string(endpoint, loc); if (value == "NULL") { // In this one situation, as a special case, NULL means a null @@ -144,7 +147,7 @@ std::pair array_parser::parse_array_step() // Skip a trailing field separator, if present. if (end < std::size(m_input)) { - auto next{scan_glyph(end)}; + auto next{scan_glyph(end, loc)}; if (((next - end) == 1) and (m_input[end] == ',')) [[unlikely]] end = next; } @@ -154,8 +157,8 @@ std::pair array_parser::parse_array_step() } -array_parser::implementation -array_parser::specialize_for_encoding(pqxx::internal::encoding_group enc) +array_parser::implementation array_parser::specialize_for_encoding( + pqxx::internal::encoding_group enc, sl loc) { using encoding_group = pqxx::internal::encoding_group; @@ -180,7 +183,7 @@ array_parser::specialize_for_encoding(pqxx::internal::encoding_group enc) PQXX_ENCODING_CASE(UTF8); } [[unlikely]] throw pqxx::internal_error{ - pqxx::internal::concat("Unsupported encoding code: ", enc, ".")}; + pqxx::internal::concat("Unsupported encoding code: ", enc, "."), loc}; #undef PQXX_ENCODING_CASE } diff --git a/src/blob.cxx b/src/blob.cxx index 2f6edaea2..ed0a332a4 100644 --- a/src/blob.cxx +++ b/src/blob.cxx @@ -43,52 +43,60 @@ std::string pqxx::blob::errmsg(connection const *cx) } -pqxx::blob pqxx::blob::open_internal(dbtransaction &tx, oid id, int mode) +pqxx::blob +pqxx::blob::open_internal(dbtransaction &tx, oid id, int mode, sl loc) { auto &cx{tx.conn()}; int const fd{lo_open(raw_conn(&cx), id, mode)}; if (fd == -1) - throw pqxx::failure{internal::concat( - "Could not open binary large object ", id, ": ", errmsg(&cx))}; + throw pqxx::failure{ + internal::concat( + "Could not open binary large object ", id, ": ", errmsg(&cx)), + loc}; return {cx, fd}; } -pqxx::oid pqxx::blob::create(dbtransaction &tx, oid id) +pqxx::oid pqxx::blob::create(dbtransaction &tx, oid id, sl loc) { oid const actual_id{lo_create(raw_conn(tx), id)}; if (actual_id == 0) - throw failure{internal::concat( - "Could not create binary large object: ", errmsg(&tx.conn()))}; + throw failure{ + internal::concat( + "Could not create binary large object: ", errmsg(&tx.conn())), + loc}; return actual_id; } -void pqxx::blob::remove(dbtransaction &tx, oid id) +void pqxx::blob::remove(dbtransaction &tx, oid id, sl loc) { if (id == 0) - throw usage_error{"Trying to delete binary large object without an ID."}; + throw usage_error{ + "Trying to delete binary large object without an ID.", loc}; if (lo_unlink(raw_conn(tx), id) == -1) - throw failure{internal::concat( - "Could not delete large object ", id, ": ", errmsg(&tx.conn()))}; + throw failure{ + internal::concat( + "Could not delete large object ", id, ": ", errmsg(&tx.conn())), + loc}; } -pqxx::blob pqxx::blob::open_r(dbtransaction &tx, oid id) +pqxx::blob pqxx::blob::open_r(dbtransaction &tx, oid id, sl loc) { - return open_internal(tx, id, INV_READ); + return open_internal(tx, id, INV_READ, loc); } -pqxx::blob pqxx::blob::open_w(dbtransaction &tx, oid id) +pqxx::blob pqxx::blob::open_w(dbtransaction &tx, oid id, sl loc) { - return open_internal(tx, id, INV_WRITE); + return open_internal(tx, id, INV_WRITE, loc); } -pqxx::blob pqxx::blob::open_rw(dbtransaction &tx, oid id) +pqxx::blob pqxx::blob::open_rw(dbtransaction &tx, oid id, sl loc) { - return open_internal(tx, id, INV_READ | INV_WRITE); + return open_internal(tx, id, INV_READ | INV_WRITE, loc); } @@ -137,111 +145,120 @@ void pqxx::blob::close() } -std::size_t pqxx::blob::raw_read(std::byte buf[], std::size_t size) +std::size_t pqxx::blob::raw_read(std::byte buf[], std::size_t size, sl loc) { if (m_conn == nullptr) - throw usage_error{"Attempt to read from a closed binary large object."}; + throw usage_error{ + "Attempt to read from a closed binary large object.", loc}; if (size > chunk_limit) throw range_error{ - "Reads from a binary large object must be less than 2 GB at once."}; + "Reads from a binary large object must be less than 2 GB at once.", loc}; auto data{reinterpret_cast(buf)}; int const received{lo_read(raw_conn(m_conn), m_fd, data, size)}; if (received < 0) throw failure{ - internal::concat("Could not read from binary large object: ", errmsg())}; + internal::concat("Could not read from binary large object: ", errmsg()), + loc}; return static_cast(received); } -std::size_t pqxx::blob::read(bytes &buf, std::size_t size) +std::size_t pqxx::blob::read(bytes &buf, std::size_t size, sl loc) { buf.resize(size); - auto const received{raw_read(std::data(buf), size)}; + auto const received{raw_read(std::data(buf), size, loc)}; buf.resize(received); return static_cast(received); } -void pqxx::blob::raw_write(bytes_view data) +void pqxx::blob::raw_write(bytes_view data, sl loc) { if (m_conn == nullptr) - throw usage_error{"Attempt to write to a closed binary large object."}; + throw usage_error{ + "Attempt to write to a closed binary large object.", loc}; auto const sz{std::size(data)}; if (sz > chunk_limit) - throw range_error{"Write to binary large object exceeds 2GB limit."}; + throw range_error{"Write to binary large object exceeds 2GB limit.", loc}; auto ptr{reinterpret_cast(std::data(data))}; int const written{lo_write(raw_conn(m_conn), m_fd, ptr, sz)}; if (written < 0) throw failure{ - internal::concat("Write to binary large object failed: ", errmsg())}; + internal::concat("Write to binary large object failed: ", errmsg()), + loc}; } -void pqxx::blob::resize(std::int64_t size) +void pqxx::blob::resize(std::int64_t size, sl loc) { if (m_conn == nullptr) - throw usage_error{"Attempt to resize a closed binary large object."}; + throw usage_error{"Attempt to resize a closed binary large object.", loc}; if (lo_truncate64(raw_conn(m_conn), m_fd, size) < 0) throw failure{ - internal::concat("Binary large object truncation failed: ", errmsg())}; + internal::concat("Binary large object truncation failed: ", errmsg()), + loc}; } -std::int64_t pqxx::blob::tell() const +std::int64_t pqxx::blob::tell(sl loc) const { if (m_conn == nullptr) - throw usage_error{"Attempt to tell() a closed binary large object."}; + throw usage_error{"Attempt to tell() a closed binary large object.", loc}; std::int64_t const offset{lo_tell64(raw_conn(m_conn), m_fd)}; if (offset < 0) - throw failure{internal::concat( - "Error reading binary large object position: ", errmsg())}; + throw failure{ + internal::concat( + "Error reading binary large object position: ", errmsg()), + loc}; return offset; } -std::int64_t pqxx::blob::seek(std::int64_t offset, int whence) +std::int64_t pqxx::blob::seek(std::int64_t offset, int whence, sl loc) { if (m_conn == nullptr) - throw usage_error{"Attempt to seek() a closed binary large object."}; + throw usage_error{"Attempt to seek() a closed binary large object.", loc}; std::int64_t const seek_result{ lo_lseek64(raw_conn(m_conn), m_fd, offset, whence)}; if (seek_result < 0) - throw failure{internal::concat( - "Error during seek on binary large object: ", errmsg())}; + throw failure{ + internal::concat("Error during seek on binary large object: ", errmsg()), + loc}; return seek_result; } -std::int64_t pqxx::blob::seek_abs(std::int64_t offset) +std::int64_t pqxx::blob::seek_abs(std::int64_t offset, sl loc) { - return this->seek(offset, SEEK_SET); + return this->seek(offset, SEEK_SET, loc); } -std::int64_t pqxx::blob::seek_rel(std::int64_t offset) +std::int64_t pqxx::blob::seek_rel(std::int64_t offset, sl loc) { - return this->seek(offset, SEEK_CUR); + return this->seek(offset, SEEK_CUR, loc); } -std::int64_t pqxx::blob::seek_end(std::int64_t offset) +std::int64_t pqxx::blob::seek_end(std::int64_t offset, sl loc) { - return this->seek(offset, SEEK_END); + return this->seek(offset, SEEK_END, loc); } -pqxx::oid pqxx::blob::from_buf(dbtransaction &tx, bytes_view data, oid id) +pqxx::oid +pqxx::blob::from_buf(dbtransaction &tx, bytes_view data, oid id, sl loc) { - oid const actual_id{create(tx, id)}; + oid const actual_id{create(tx, id, loc)}; try { - open_w(tx, actual_id).write(data); + open_w(tx, actual_id, loc).write(data, loc); } catch (std::exception const &) { try { - remove(tx, id); + remove(tx, id, loc); } catch (std::exception const &e) { @@ -260,33 +277,34 @@ pqxx::oid pqxx::blob::from_buf(dbtransaction &tx, bytes_view data, oid id) } -void pqxx::blob::append_from_buf(dbtransaction &tx, bytes_view data, oid id) +void pqxx::blob::append_from_buf( + dbtransaction &tx, bytes_view data, oid id, sl loc) { if (std::size(data) > chunk_limit) throw range_error{ - "Writes to a binary large object must be less than 2 GB at once."}; - blob b{open_w(tx, id)}; - b.seek_end(); - b.write(data); + "Writes to a binary large object must be less than 2 GB at once.", loc}; + blob b{open_w(tx, id, loc)}; + b.seek_end(0, loc); + b.write(data, loc); } void pqxx::blob::to_buf( - dbtransaction &tx, oid id, bytes &buf, std::size_t max_size) + dbtransaction &tx, oid id, bytes &buf, std::size_t max_size, sl loc) { - open_r(tx, id).read(buf, max_size); + open_r(tx, id, loc).read(buf, max_size, loc); } std::size_t pqxx::blob::append_to_buf( dbtransaction &tx, oid id, std::int64_t offset, bytes &buf, - std::size_t append_max) + std::size_t append_max, sl loc) { if (append_max > chunk_limit) throw range_error{ - "Reads from a binary large object must be less than 2 GB at once."}; - auto b{open_r(tx, id)}; - b.seek_abs(offset); + "Reads from a binary large object must be less than 2 GB at once.", loc}; + auto b{open_r(tx, id, loc)}; + b.seek_abs(offset, loc); auto const org_size{std::size(buf)}; buf.resize(org_size + append_max); try @@ -305,31 +323,38 @@ std::size_t pqxx::blob::append_to_buf( } -pqxx::oid pqxx::blob::from_file(dbtransaction &tx, zview path) +pqxx::oid pqxx::blob::from_file(dbtransaction &tx, zview path, sl loc) { auto id{lo_import(raw_conn(tx), path.c_str())}; if (id == 0) - throw failure{internal::concat( - "Could not import '", path, "' as a binary large object: ", errmsg(tx))}; + throw failure{ + internal::concat( + "Could not import '", path, + "' as a binary large object: ", errmsg(tx)), + loc}; return id; } -pqxx::oid pqxx::blob::from_file(dbtransaction &tx, zview path, oid id) +pqxx::oid pqxx::blob::from_file(dbtransaction &tx, zview path, oid id, sl loc) { auto actual_id{lo_import_with_oid(raw_conn(tx), path.c_str(), id)}; if (actual_id == 0) - throw failure{internal::concat( - "Could not import '", path, "' as binary large object ", id, ": ", - errmsg(tx))}; + throw failure{ + internal::concat( + "Could not import '", path, "' as binary large object ", id, ": ", + errmsg(tx)), + loc}; return actual_id; } -void pqxx::blob::to_file(dbtransaction &tx, oid id, zview path) +void pqxx::blob::to_file(dbtransaction &tx, oid id, zview path, sl loc) { if (lo_export(raw_conn(tx), id, path.c_str()) < 0) - throw failure{internal::concat( - "Could not export binary large object ", id, " to file '", path, - "': ", errmsg(tx))}; + throw failure{ + internal::concat( + "Could not export binary large object ", id, " to file '", path, + "': ", errmsg(tx)), + loc}; } diff --git a/src/connection.cxx b/src/connection.cxx index 8a7c5cbd9..1054b4b17 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -105,19 +105,19 @@ void PQXX_COLD PQXX_LIBEXPORT pqxx::internal::skip_init_ssl(int skips) noexcept } -pqxx::connection::connection(connection &&rhs) : +pqxx::connection::connection(connection &&rhs, sl loc) : m_conn{rhs.m_conn}, m_notice_waiters{std::move(rhs.m_notice_waiters)}, m_notification_handlers{std::move(rhs.m_notification_handlers)}, m_unique_id{rhs.m_unique_id} { - rhs.check_movable(); + rhs.check_movable(loc); rhs.m_conn = nullptr; } pqxx::connection::connection( - connection::connect_mode, zview connection_string) : + connection::connect_mode, zview connection_string, sl loc) : m_conn{PQconnectStart(connection_string.c_str())} { if (m_conn == nullptr) @@ -130,7 +130,7 @@ pqxx::connection::connection( std::string const msg{PQerrorMessage(m_conn)}; PQfinish(m_conn); m_conn = nullptr; - throw pqxx::broken_connection{msg}; + throw pqxx::broken_connection{msg, loc}; } } @@ -141,24 +141,24 @@ pqxx::connection::connection(internal::pq::PGconn *raw_conn) : m_conn{raw_conn} } -std::pair pqxx::connection::poll_connect() +std::pair pqxx::connection::poll_connect(sl loc) { switch (PQconnectPoll(m_conn)) { case PGRES_POLLING_FAILED: - throw pqxx::broken_connection{PQerrorMessage(m_conn)}; + throw pqxx::broken_connection{PQerrorMessage(m_conn), loc}; case PGRES_POLLING_READING: return std::make_pair(true, false); case PGRES_POLLING_WRITING: return std::make_pair(false, true); case PGRES_POLLING_OK: if (not is_open()) - throw pqxx::broken_connection{PQerrorMessage(m_conn)}; + throw pqxx::broken_connection{PQerrorMessage(m_conn), loc}; [[likely]] return std::make_pair(false, false); case PGRES_POLLING_ACTIVE: throw internal_error{ - "Nonblocking connection poll returned obsolete 'active' state."}; + "Nonblocking connection poll returned obsolete 'active' state.", loc}; default: throw internal_error{ - "Nonblocking connection poll returned unknown value."}; + "Nonblocking connection poll returned unknown value.", loc}; } } @@ -178,16 +178,16 @@ void pqxx::connection::set_up_notice_handlers() } -void pqxx::connection::complete_init() +void pqxx::connection::complete_init(sl loc) { if (m_conn == nullptr) throw std::bad_alloc{}; try { if (not is_open()) - throw broken_connection{PQerrorMessage(m_conn)}; + throw broken_connection{PQerrorMessage(m_conn), loc}; - set_up_state(); + set_up_state(loc); } catch (std::exception const &) { @@ -198,51 +198,55 @@ void pqxx::connection::complete_init() } -void pqxx::connection::init(char const options[]) +void pqxx::connection::init(char const options[], sl loc) { m_conn = PQconnectdb(options); set_up_notice_handlers(); - complete_init(); + complete_init(loc); } -void pqxx::connection::init(char const *params[], char const *values[]) +void pqxx::connection::init(char const *params[], char const *values[], sl loc) { m_conn = PQconnectdbParams(params, values, 0); set_up_notice_handlers(); - complete_init(); + complete_init(loc); } -void pqxx::connection::check_movable() const +void pqxx::connection::check_movable(sl loc) const { if (m_trans) - throw pqxx::usage_error{"Moving a connection with a transaction open."}; + throw pqxx::usage_error{ + "Moving a connection with a transaction open.", loc}; if (not std::empty(m_receivers)) throw pqxx::usage_error{ - "Moving a connection with notification receivers registered."}; + "Moving a connection with notification receivers registered.", loc}; } -void pqxx::connection::check_overwritable() const +void pqxx::connection::check_overwritable(sl loc) const { if (m_trans) throw pqxx::usage_error{ - "Moving a connection onto one with a transaction open."}; + "Moving a connection onto one with a transaction open.", loc}; if (not std::empty(m_receivers)) throw usage_error{ "Moving a connection onto one " - "with notification receivers registered."}; + "with notification receivers registered.", + loc}; } +// TODO: How can we pass std::source_location here? pqxx::connection &pqxx::connection::operator=(connection &&rhs) { - check_overwritable(); - rhs.check_movable(); + auto loc{sl::current()}; + check_overwritable(loc); + rhs.check_movable(loc); // Close our old connection, if any. - close(); + close(loc); m_conn = std::exchange(rhs.m_conn, nullptr); m_unique_id = rhs.m_unique_id; @@ -255,7 +259,7 @@ pqxx::connection &pqxx::connection::operator=(connection &&rhs) pqxx::result pqxx::connection::make_result( internal::pq::PGresult *pgr, std::shared_ptr const &query, - std::string_view desc) + std::string_view desc, sl loc) { std::shared_ptr const smart{ pgr, internal::clear_result}; @@ -264,12 +268,12 @@ pqxx::result pqxx::connection::make_result( if (is_open()) throw failure(err_msg()); else - throw broken_connection{"Lost connection to the database server."}; + throw broken_connection{"Lost connection to the database server.", loc}; } - auto const enc{internal::enc_group(encoding_id())}; + auto const enc{internal::enc_group(encoding_id(loc), loc)}; auto r{pqxx::internal::gate::result_creation::create( smart, query, m_notice_waiters, enc)}; - pqxx::internal::gate::result_creation{r}.check_status(desc); + pqxx::internal::gate::result_creation{r}.check_status(desc, loc); return r; } @@ -308,27 +312,27 @@ int PQXX_COLD pqxx::connection::server_version() const noexcept void pqxx::connection::set_variable( - std::string_view var, std::string_view value) & + std::string_view var, std::string_view value, sl loc) & { - exec(internal::concat("SET ", quote_name(var), "=", value)); + exec(internal::concat("SET ", quote_name(var), "=", value), loc); } -std::string pqxx::connection::get_variable(std::string_view var) +std::string pqxx::connection::get_variable(std::string_view var, sl loc) { - return exec(internal::concat("SHOW ", quote_name(var))) + return exec(internal::concat("SHOW ", quote_name(var)), loc) .at(0) .at(0) .as(std::string{}); } -std::string pqxx::connection::get_var(std::string_view var) +std::string pqxx::connection::get_var(std::string_view var, sl loc) { // (Variables can't be null, so far as I can make out.) - return exec(internal::concat("SHOW "sv, quote_name(var))) - .one_field() - .as(); + return exec(internal::concat("SHOW "sv, quote_name(var)), loc) + .one_field(loc) + .as(loc); } @@ -336,21 +340,23 @@ std::string pqxx::connection::get_var(std::string_view var) * recovered because the physical connection to the database was lost and is * being reset, or that may not have been initialized yet. */ -void pqxx::connection::set_up_state() +void pqxx::connection::set_up_state(sl loc) { if (auto const proto_ver{protocol_version()}; proto_ver < 3) { if (proto_ver == 0) - throw broken_connection{"No connection."}; + throw broken_connection{"No connection.", loc}; else throw feature_not_supported{ - "Unsupported frontend/backend protocol version; 3.0 is the minimum."}; + "Unsupported frontend/backend protocol version; 3.0 is the minimum.", + "[connect]", nullptr, loc}; } constexpr int oldest_server{90000}; if (server_version() <= oldest_server) throw feature_not_supported{ - "Unsupported server version; 9.0 is the minimum."}; + "Unsupported server version; 9.0 is the minimum.", "[connect]", nullptr, + loc}; } @@ -410,12 +416,14 @@ void PQXX_COLD pqxx::connection::add_receiver(pqxx::notification_receiver *n) void pqxx::connection::listen( - std::string_view channel, notification_handler handler) + std::string_view channel, notification_handler handler, sl loc) { if (m_trans != nullptr) - throw usage_error{pqxx::internal::concat( - "Attempting to listen for notifications on '", channel, - "' while transaction is active.")}; + throw usage_error{ + pqxx::internal::concat( + "Attempting to listen for notifications on '", channel, + "' while transaction is active."), + loc}; std::string str_name{channel}; @@ -434,7 +442,8 @@ void pqxx::connection::listen( else { // We had no handler installed for this name. Start listening. - exec(pqxx::internal::concat("LISTEN ", quote_name(channel))).no_rows(); + exec(pqxx::internal::concat("LISTEN ", quote_name(channel)), loc) + .no_rows(); m_notification_handlers.emplace_hint(pos, channel, std::move(handler)); } } @@ -445,15 +454,16 @@ void pqxx::connection::listen( if (pos != handlers_end) { // Yes, we had a handler for this name. Remove it. - exec(pqxx::internal::concat("UNLISTEN ", quote_name(channel))).no_rows(); + exec(pqxx::internal::concat("UNLISTEN ", quote_name(channel)), loc) + .no_rows(); m_notification_handlers.erase(pos); } } } -void PQXX_COLD -pqxx::connection::remove_receiver(pqxx::notification_receiver *T) noexcept +void PQXX_COLD pqxx::connection::remove_receiver( + pqxx::notification_receiver *T, sl loc) noexcept { if (T == nullptr) return; @@ -477,7 +487,7 @@ pqxx::connection::remove_receiver(pqxx::notification_receiver *T) noexcept bool const gone{R.second == ++R.first}; m_receivers.erase(i); if (gone) - exec(internal::concat("UNLISTEN ", quote_name(needle.first))); + exec(internal::concat("UNLISTEN ", quote_name(needle.first)), loc); } } catch (std::exception const &e) @@ -514,7 +524,7 @@ constexpr int buf_size{500u}; } // namespace -void PQXX_COLD pqxx::connection::cancel_query() +void PQXX_COLD pqxx::connection::cancel_query(sl loc) { std::unique_ptr const cancel{ PQgetCancel(m_conn), wrap_pgfreecancel}; @@ -525,12 +535,13 @@ void PQXX_COLD pqxx::connection::cancel_query() auto const err{errbuf.data()}; auto const c{PQcancel(cancel.get(), err, buf_size)}; if (c == 0) [[unlikely]] - throw pqxx::sql_error{std::string{err, std::size(errbuf)}, "[cancel]"}; + throw pqxx::sql_error{ + std::string{err, std::size(errbuf)}, "[cancel]", nullptr, loc}; } #if defined(_WIN32) || __has_include() -void pqxx::connection::set_blocking(bool block) & +void pqxx::connection::set_blocking(bool block, sl loc) & { auto const fd{sock()}; # if defined _WIN32 @@ -540,7 +551,7 @@ void pqxx::connection::set_blocking(bool block) & std::array errbuf{}; char const *err{pqxx::internal::error_string(WSAGetLastError(), errbuf)}; throw broken_connection{ - internal::concat("Could not set socket's blocking mode: ", err)}; + internal::concat("Could not set socket's blocking mode: ", err), loc}; } # else // _WIN32 std::array errbuf{}; @@ -549,7 +560,7 @@ void pqxx::connection::set_blocking(bool block) & { char const *const err{pqxx::internal::error_string(errno, errbuf)}; throw broken_connection{ - internal::concat("Could not get socket state: ", err)}; + internal::concat("Could not get socket state: ", err), loc}; } if (block) flags |= O_NONBLOCK; @@ -559,7 +570,7 @@ void pqxx::connection::set_blocking(bool block) & { char const *const err{pqxx::internal::error_string(errno, errbuf)}; throw broken_connection{ - internal::concat("Could not set socket's blocking mode: ", err)}; + internal::concat("Could not set socket's blocking mode: ", err), loc}; } # endif // _WIN32 } @@ -587,10 +598,10 @@ notify_ptr get_notif(pqxx::internal::pq::PGconn *cx) } // namespace -int pqxx::connection::get_notifs() +int pqxx::connection::get_notifs(sl loc) { if (not consume_input()) - throw broken_connection{"Connection lost."}; + throw broken_connection{"Connection lost.", loc}; // Even if somehow we receive notifications during our transaction, don't // deliver them. @@ -654,25 +665,25 @@ int pqxx::connection::get_notifs() } -char const *PQXX_COLD pqxx::connection::dbname() const +char const *PQXX_COLD pqxx::connection::dbname() const noexcept { return PQdb(m_conn); } -char const *PQXX_COLD pqxx::connection::username() const +char const *PQXX_COLD pqxx::connection::username() const noexcept { return PQuser(m_conn); } -char const *PQXX_COLD pqxx::connection::hostname() const +char const *PQXX_COLD pqxx::connection::hostname() const noexcept { return PQhost(m_conn); } -char const *PQXX_COLD pqxx::connection::port() const +char const *PQXX_COLD pqxx::connection::port() const noexcept { return PQport(m_conn); } @@ -710,17 +721,17 @@ std::vector pqxx::result -pqxx::connection::exec(std::string_view query, std::string_view desc) +pqxx::connection::exec(std::string_view query, std::string_view desc, sl loc) { - return exec(std::make_shared(query), desc); + return exec(std::make_shared(query), desc, loc); } pqxx::result pqxx::connection::exec( - std::shared_ptr const &query, std::string_view desc) + std::shared_ptr const &query, std::string_view desc, sl loc) { auto res{make_result(PQexec(m_conn, query->c_str()), query, desc)}; - get_notifs(); + get_notifs(loc); return res; } @@ -735,7 +746,8 @@ std::string pqxx::connection::encrypt_password( } -void pqxx::connection::prepare(char const name[], char const definition[]) & +void pqxx::connection::prepare( + char const name[], char const definition[], sl) & { auto const q{std::make_shared( pqxx::internal::concat("[PREPARE ", name, "]"))}; @@ -745,35 +757,35 @@ void pqxx::connection::prepare(char const name[], char const definition[]) & } -void pqxx::connection::prepare(char const definition[]) & +void pqxx::connection::prepare(char const definition[], sl loc) & { - this->prepare("", definition); + this->prepare("", definition, loc); } -void pqxx::connection::unprepare(std::string_view name) +void pqxx::connection::unprepare(std::string_view name, sl loc) { - exec(internal::concat("DEALLOCATE ", quote_name(name))); + exec(internal::concat("DEALLOCATE ", quote_name(name)), loc); } pqxx::result pqxx::connection::exec_prepared( - std::string_view statement, internal::c_params const &args) + std::string_view statement, internal::c_params const &args, sl loc) { auto const q{std::make_shared(statement)}; auto const pq_result{PQexecPrepared( m_conn, q->c_str(), - check_cast(std::size(args.values), "exec_prepared"sv), + check_cast(std::size(args.values), "exec_prepared"sv, loc), args.values.data(), args.lengths.data(), reinterpret_cast(args.formats.data()), static_cast(format::text))}; auto r{make_result(pq_result, q, statement)}; - get_notifs(); + get_notifs(loc); return r; } -void pqxx::connection::close() +void pqxx::connection::close(sl) { // Just in case PQfinish() doesn't handle nullptr nicely. if (m_conn == nullptr) @@ -899,15 +911,15 @@ pqxx::connection::read_copy_line() } -void pqxx::connection::write_copy_line(std::string_view line) +void pqxx::connection::write_copy_line(std::string_view line, sl loc) { static std::string const err_prefix{"Error writing to table: "}; auto const size{check_cast( - std::ssize(line), "Line in stream_to is too long to process."sv)}; + std::ssize(line), "Line in stream_to is too long to process."sv, loc)}; if (PQputCopyData(m_conn, line.data(), size) <= 0) [[unlikely]] - throw failure{err_prefix + err_msg()}; + throw failure{err_prefix + err_msg(), loc}; if (PQputCopyData(m_conn, "\n", 1) <= 0) [[unlikely]] - throw failure{err_prefix + err_msg()}; + throw failure{err_prefix + err_msg(), loc}; } @@ -946,22 +958,23 @@ pqxx::internal::pq::PGresult *pqxx::connection::get_result() } -size_t pqxx::connection::esc_to_buf(std::string_view text, char *buf) const +size_t +pqxx::connection::esc_to_buf(std::string_view text, char *buf, sl loc) const { int err{0}; auto const copied{ PQescapeStringConn(m_conn, buf, text.data(), std::size(text), &err)}; if (err) [[unlikely]] - throw argument_error{err_msg()}; + throw argument_error{err_msg(), loc}; return copied; } -std::string pqxx::connection::esc(std::string_view text) const +std::string pqxx::connection::esc(std::string_view text, sl loc) const { std::string buf; buf.resize(2 * std::size(text) + 1); - auto const copied{esc_to_buf(text, buf.data())}; + auto const copied{esc_to_buf(text, buf.data(), loc)}; buf.resize(copied); return buf; } @@ -1004,14 +1017,14 @@ std::string pqxx::connection::quote_table(table_path path) const } -std::string -pqxx::connection::esc_like(std::string_view text, char escape_char) const +std::string pqxx::connection::esc_like( + std::string_view text, char escape_char, sl loc) const { std::string out; out.reserve(std::size(text)); // TODO: Rewrite using a char_finder. internal::for_glyphs( - internal::enc_group(encoding_id()), + internal::enc_group(encoding_id(loc), loc), [&out, escape_char](char const *gbegin, char const *gend) { if ((gend - gbegin == 1) and (*gbegin == '_' or *gbegin == '%')) [[unlikely]] @@ -1019,34 +1032,34 @@ pqxx::connection::esc_like(std::string_view text, char escape_char) const for (; gbegin != gend; ++gbegin) out.push_back(*gbegin); }, - text.data(), std::size(text)); + text.data(), std::size(text), 0u, loc); return out; } -int pqxx::connection::await_notification() +int pqxx::connection::await_notification(sl loc) { - int notifs = get_notifs(); + int notifs = get_notifs(loc); if (notifs == 0) { [[likely]] internal::wait_fd(socket_of(m_conn), true, false, 10, 0); - notifs = get_notifs(); + notifs = get_notifs(loc); } return notifs; } int pqxx::connection::await_notification( - std::time_t seconds, long microseconds) + std::time_t seconds, long microseconds, sl loc) { - int const notifs = get_notifs(); + int const notifs = get_notifs(loc); if (notifs == 0) { [[likely]] internal::wait_fd( socket_of(m_conn), true, false, - check_cast(seconds, "Seconds out of range."), - check_cast(microseconds, "Microseconds out of range.")); - return get_notifs(); + check_cast(seconds, "Seconds out of range.", loc), + check_cast(microseconds, "Microseconds out of range.", loc)); + return get_notifs(loc); } return notifs; } @@ -1062,13 +1075,14 @@ std::string pqxx::connection::adorn_name(std::string_view n) } -std::string pqxx::connection::get_client_encoding() const +std::string pqxx::connection::get_client_encoding(sl loc) const { - return internal::name_encoding(encoding_id()); + return internal::name_encoding(encoding_id(loc)); } -void PQXX_COLD pqxx::connection::set_client_encoding(char const encoding[]) & +void PQXX_COLD +pqxx::connection::set_client_encoding(char const encoding[], sl loc) & { switch (auto const retval{PQsetClientEncoding(m_conn, encoding)}; retval) { @@ -1078,17 +1092,18 @@ void PQXX_COLD pqxx::connection::set_client_encoding(char const encoding[]) & case -1: [[unlikely]] if (is_open()) - throw failure{"Setting client encoding failed."}; + throw failure{"Setting client encoding failed.", loc}; else - throw broken_connection{"Lost connection to the database server."}; + throw broken_connection{"Lost connection to the database server.", loc}; default: - [[unlikely]] throw internal_error{internal::concat( - "Unexpected result from PQsetClientEncoding: ", retval)}; + [[unlikely]] throw internal_error{ + internal::concat("Unexpected result from PQsetClientEncoding: ", retval), + loc}; } } -int pqxx::connection::encoding_id() const +int pqxx::connection::encoding_id(sl loc) const { int const enc{PQclientEncoding(m_conn)}; if (enc == -1) @@ -1100,26 +1115,26 @@ int pqxx::connection::encoding_id() const // TODO: Make pqxx::result::result(...) do all the checking. [[unlikely]] if (is_open()) - throw failure{"Could not obtain client encoding."}; + throw failure{"Could not obtain client encoding.", loc}; else - throw broken_connection{"Lost connection to the database server."}; + throw broken_connection{"Lost connection to the database server.", loc}; } [[likely]] return enc; } pqxx::result pqxx::connection::exec_params( - std::string_view query, internal::c_params const &args) + std::string_view query, internal::c_params const &args, sl loc) { auto const q{std::make_shared(query)}; auto const pq_result{PQexecParams( m_conn, q->c_str(), - check_cast(std::size(args.values), "exec_params"sv), nullptr, + check_cast(std::size(args.values), "exec_params"sv, loc), nullptr, args.values.data(), args.lengths.data(), reinterpret_cast(args.formats.data()), static_cast(format::text))}; auto r{make_result(pq_result, q)}; - get_notifs(); + get_notifs(loc); return r; } @@ -1197,16 +1212,16 @@ std::string pqxx::connection::connection_string() const #if defined(_WIN32) || __has_include() -pqxx::connecting::connecting(zview connection_string) : - m_conn{connection::connect_nonblocking, connection_string} +pqxx::connecting::connecting(zview connection_string, sl loc) : + m_conn{connection::connect_nonblocking, connection_string, loc} {} #endif // defined(_WIN32) || __has_include( #if defined(_WIN32) || __has_include() -void pqxx::connecting::process() & +void pqxx::connecting::process(sl loc) & { - auto const [reading, writing]{m_conn.poll_connect()}; + auto const [reading, writing]{m_conn.poll_connect(loc)}; m_reading = reading; m_writing = writing; } @@ -1214,12 +1229,12 @@ void pqxx::connecting::process() & #if defined(_WIN32) || __has_include() -pqxx::connection pqxx::connecting::produce() && +pqxx::connection pqxx::connecting::produce(sl loc) && { if (!done()) throw usage_error{ - "Tried to produce a nonblocking connection before it was done."}; - m_conn.complete_init(); - return std::move(m_conn); + "Tried to produce a nonblocking connection before it was done.", loc}; + m_conn.complete_init(loc); + return {std::move(m_conn), loc}; } #endif // defined(_WIN32) || __has_include( diff --git a/src/cursor.cxx b/src/cursor.cxx index 3e12c0fc5..fc576f335 100644 --- a/src/cursor.cxx +++ b/src/cursor.cxx @@ -31,20 +31,20 @@ pqxx::cursor_base::cursor_base( pqxx::result::size_type -pqxx::internal::obtain_stateless_cursor_size(sql_cursor &cur) +pqxx::internal::obtain_stateless_cursor_size(sql_cursor &cur, sl loc) { if (cur.endpos() == -1) - cur.move(cursor_base::all()); + cur.move(cursor_base::all(), loc); return result::size_type(cur.endpos() - 1); } pqxx::result pqxx::internal::stateless_cursor_retrieve( sql_cursor &cur, result::difference_type size, - result::difference_type begin_pos, result::difference_type end_pos) + result::difference_type begin_pos, result::difference_type end_pos, sl loc) { if (begin_pos < 0 or begin_pos > size) - throw range_error{"Starting position out of range"}; + throw range_error{"Starting position out of range", loc}; if (end_pos < -1) end_pos = -1; @@ -55,14 +55,14 @@ pqxx::result pqxx::internal::stateless_cursor_retrieve( return cur.empty_result(); int const direction{((begin_pos < end_pos) ? 1 : -1)}; - cur.move((begin_pos - direction) - (cur.pos() - 1)); - return cur.fetch(end_pos - begin_pos); + cur.move((begin_pos - direction) - (cur.pos() - 1), loc); + return cur.fetch(end_pos - begin_pos, loc); } pqxx::icursorstream::icursorstream( transaction_base &context, std::string_view query, std::string_view basename, - difference_type sstride) : + difference_type sstride, sl loc) : m_cur{ context, query, @@ -70,20 +70,21 @@ pqxx::icursorstream::icursorstream( cursor_base::forward_only, cursor_base::read_only, cursor_base::owned, - false}, + false, + loc}, m_stride{sstride}, m_realpos{0}, m_reqpos{0}, m_iterators{nullptr}, m_done{false} { - set_stride(sstride); + set_stride(sstride, loc); } pqxx::icursorstream::icursorstream( transaction_base &context, field const &cname, difference_type sstride, - cursor_base::ownership_policy op) : + cursor_base::ownership_policy op, sl loc) : m_cur{context, cname.c_str(), op}, m_stride{sstride}, m_realpos{0}, @@ -91,22 +92,22 @@ pqxx::icursorstream::icursorstream( m_iterators{nullptr}, m_done{false} { - set_stride(sstride); + set_stride(sstride, loc); } -void pqxx::icursorstream::set_stride(difference_type stride) & +void pqxx::icursorstream::set_stride(difference_type stride, sl loc) & { if (stride < 1) throw argument_error{ - internal::concat("Attempt to set cursor stride to ", stride)}; + internal::concat("Attempt to set cursor stride to ", stride), loc}; m_stride = stride; } -pqxx::result pqxx::icursorstream::fetchblock() +pqxx::result pqxx::icursorstream::fetchblock(sl loc) { - result r{m_cur.fetch(m_stride)}; + result r{m_cur.fetch(m_stride, loc)}; m_realpos += std::size(r); if (std::empty(r)) m_done = true; @@ -114,9 +115,9 @@ pqxx::result pqxx::icursorstream::fetchblock() } -pqxx::icursorstream &pqxx::icursorstream::ignore(std::streamsize n) & +pqxx::icursorstream &pqxx::icursorstream::ignore(std::streamsize n, sl loc) & { - auto offset{m_cur.move(difference_type(n))}; + auto offset{m_cur.move(difference_type(n), loc)}; m_realpos += offset; if (offset < n) m_done = true; @@ -165,7 +166,7 @@ void pqxx::icursorstream::remove_iterator(icursor_iterator *i) const noexcept } -void pqxx::icursorstream::service_iterators(difference_type topos) +void pqxx::icursorstream::service_iterators(difference_type topos, sl loc) { if (topos < m_realpos) return; @@ -186,7 +187,7 @@ void pqxx::icursorstream::service_iterators(difference_type topos) auto const readpos{i->first}; if (readpos > m_realpos) ignore(readpos - m_realpos); - result const r{fetchblock()}; + result const r{fetchblock(loc)}; for (; i != todo_end and i->first == readpos; ++i) pqxx::internal::gate::icursor_iterator_icursorstream{*i->second}.fill(r); } @@ -288,12 +289,14 @@ pqxx::icursor_iterator::operator=(icursor_iterator const &rhs) noexcept bool pqxx::icursor_iterator::operator==(icursor_iterator const &rhs) const { + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; if (m_stream == rhs.m_stream) return pos() == rhs.pos(); if (m_stream != nullptr and rhs.m_stream != nullptr) return false; - refresh(); - rhs.refresh(); + refresh(loc); + rhs.refresh(loc); return std::empty(m_here) and std::empty(rhs.m_here); } @@ -302,17 +305,19 @@ bool pqxx::icursor_iterator::operator<(icursor_iterator const &rhs) const { if (m_stream == rhs.m_stream) return pos() < rhs.pos(); - refresh(); - rhs.refresh(); + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; + refresh(loc); + rhs.refresh(loc); return not std::empty(m_here); } -void pqxx::icursor_iterator::refresh() const +void pqxx::icursor_iterator::refresh(sl loc) const { if (m_stream != nullptr) pqxx::internal::gate::icursorstream_icursor_iterator{*m_stream} - .service_iterators(pos()); + .service_iterators(pos(), loc); } diff --git a/src/encodings.cxx b/src/encodings.cxx index 2e8d80b4c..9571d71d7 100644 --- a/src/encodings.cxx +++ b/src/encodings.cxx @@ -165,14 +165,14 @@ PQXX_PURE char const *name_encoding(int encoding_id) } -encoding_group enc_group(int libpq_enc_id) +encoding_group enc_group(int libpq_enc_id, sl) { // TODO: Can we safely do this without using string representation? return enc_group(name_encoding(libpq_enc_id)); } -PQXX_PURE glyph_scanner_func *get_glyph_scanner(encoding_group enc) +PQXX_PURE glyph_scanner_func *get_glyph_scanner(encoding_group enc, sl loc) { #define CASE_GROUP(ENC) \ case encoding_group::ENC: return glyph_scanner::call @@ -194,7 +194,7 @@ PQXX_PURE glyph_scanner_func *get_glyph_scanner(encoding_group enc) [[likely]] CASE_GROUP(UTF8); } throw usage_error{ - internal::concat("Unsupported encoding group code ", enc, ".")}; + internal::concat("Unsupported encoding group code ", enc, "."), loc}; #undef CASE_GROUP } diff --git a/src/except.cxx b/src/except.cxx index 75088c483..2bd97aaa8 100644 --- a/src/except.cxx +++ b/src/except.cxx @@ -15,37 +15,36 @@ #include "pqxx/internal/header-post.hxx" -pqxx::failure::failure(std::string const &whatarg, std::source_location loc) : +pqxx::failure::failure(std::string const &whatarg, sl loc) : std::runtime_error{whatarg}, location{loc} {} -pqxx::broken_connection::broken_connection() : - failure{"Connection to database failed."} +pqxx::broken_connection::broken_connection(sl loc) : + failure{"Connection to database failed.", loc} {} pqxx::broken_connection::broken_connection( - std::string const &whatarg, std::source_location loc) : + std::string const &whatarg, sl loc) : failure{whatarg, loc} {} pqxx::protocol_violation::protocol_violation( - std::string const &whatarg, std::source_location loc) : + std::string const &whatarg, sl loc) : broken_connection{whatarg, loc} {} pqxx::variable_set_to_null::variable_set_to_null( - std::string const &whatarg, std::source_location loc) : + std::string const &whatarg, sl loc) : failure{whatarg, loc} {} pqxx::sql_error::sql_error( - std::string const &whatarg, std::string Q, char const *sqlstate, - std::source_location loc) : + std::string const &whatarg, std::string Q, char const *sqlstate, sl loc) : failure{whatarg, loc}, m_query{std::move(Q)}, m_sqlstate{sqlstate ? sqlstate : ""} @@ -67,76 +66,72 @@ PQXX_PURE std::string const &pqxx::sql_error::sqlstate() const noexcept } -pqxx::in_doubt_error::in_doubt_error( - std::string const &whatarg, std::source_location loc) : +pqxx::in_doubt_error::in_doubt_error(std::string const &whatarg, sl loc) : failure{whatarg, loc} {} pqxx::transaction_rollback::transaction_rollback( std::string const &whatarg, std::string const &q, char const sqlstate[], - std::source_location loc) : + sl loc) : sql_error{whatarg, q, sqlstate, loc} {} pqxx::serialization_failure::serialization_failure( std::string const &whatarg, std::string const &q, char const sqlstate[], - std::source_location loc) : + sl loc) : transaction_rollback{whatarg, q, sqlstate, loc} {} pqxx::statement_completion_unknown::statement_completion_unknown( std::string const &whatarg, std::string const &q, char const sqlstate[], - std::source_location loc) : + sl loc) : transaction_rollback{whatarg, q, sqlstate, loc} {} pqxx::deadlock_detected::deadlock_detected( std::string const &whatarg, std::string const &q, char const sqlstate[], - std::source_location loc) : + sl loc) : transaction_rollback{whatarg, q, sqlstate, loc} {} -pqxx::internal_error::internal_error(std::string const &whatarg) : - std::logic_error{internal::concat("libpqxx internal error: ", whatarg)} +pqxx::internal_error::internal_error(std::string const &whatarg, sl loc) : + std::logic_error{ + internal::concat("libpqxx internal error: ", whatarg)}, + location{loc} {} -pqxx::usage_error::usage_error( - std::string const &whatarg, std::source_location loc) : +pqxx::usage_error::usage_error(std::string const &whatarg, sl loc) : std::logic_error{whatarg}, location{loc} {} -pqxx::argument_error::argument_error( - std::string const &whatarg, std::source_location loc) : +pqxx::argument_error::argument_error(std::string const &whatarg, sl loc) : invalid_argument{whatarg}, location{loc} {} -pqxx::conversion_error::conversion_error( - std::string const &whatarg, std::source_location loc) : +pqxx::conversion_error::conversion_error(std::string const &whatarg, sl loc) : domain_error{whatarg}, location{loc} {} -pqxx::unexpected_null::unexpected_null( - std::string const &whatarg, std::source_location loc) : +pqxx::unexpected_null::unexpected_null(std::string const &whatarg, sl loc) : conversion_error{whatarg, loc} {} pqxx::conversion_overrun::conversion_overrun( - std::string const &whatarg, std::source_location loc) : + std::string const &whatarg, sl loc) : conversion_error{whatarg, loc} {} -pqxx::range_error::range_error( - std::string const &whatarg, std::source_location loc) : +pqxx::range_error::range_error(std::string const &whatarg, sl loc) : out_of_range{whatarg}, location{loc} {} diff --git a/src/field.cxx b/src/field.cxx index 10f15961f..6c0d359e0 100644 --- a/src/field.cxx +++ b/src/field.cxx @@ -38,31 +38,31 @@ bool PQXX_COLD pqxx::field::operator==(field const &rhs) const noexcept } -char const *pqxx::field::name() const & +char const *pqxx::field::name(sl loc) const & { - return home().column_name(col()); + return home().column_name(col(), loc); } -pqxx::oid pqxx::field::type() const +pqxx::oid pqxx::field::type(sl loc) const { - return home().column_type(col()); + return home().column_type(col(), loc); } -pqxx::oid pqxx::field::table() const +pqxx::oid pqxx::field::table(sl loc) const { - return home().column_table(col()); + return home().column_table(col(), loc); } -pqxx::row::size_type pqxx::field::table_column() const +pqxx::row::size_type pqxx::field::table_column(sl loc) const { - return home().table_column(col()); + return home().table_column(col(), loc); } -char const *pqxx::field::c_str() const & +char const *pqxx::field::c_str() const & noexcept { return home().get_value(idx(), col()); } diff --git a/src/notification.cxx b/src/notification.cxx index 415bc8f7f..f9e4a8beb 100644 --- a/src/notification.cxx +++ b/src/notification.cxx @@ -31,6 +31,8 @@ pqxx::notification_receiver::notification_receiver( pqxx::notification_receiver::~notification_receiver() { + // TODO: How can we pass std::source_location here? + auto loc{sl::current()}; pqxx::internal::gate::connection_notification_receiver{this->conn()} - .remove_receiver(this); + .remove_receiver(this, loc); } diff --git a/src/params.cxx b/src/params.cxx index 603c405d5..4bfb2d217 100644 --- a/src/params.cxx +++ b/src/params.cxx @@ -80,13 +80,13 @@ void pqxx::params::append(params &&value) & } -pqxx::internal::c_params pqxx::params::make_c_params() const +pqxx::internal::c_params pqxx::params::make_c_params(sl loc) const { pqxx::internal::c_params p; p.reserve(std::size(m_params)); for (auto const ¶m : m_params) std::visit( - [&p](auto const &value) { + [&p, loc](auto const &value) { using T = std::remove_cvref_t; if constexpr (std::is_same_v) @@ -97,7 +97,8 @@ pqxx::internal::c_params pqxx::params::make_c_params() const else { p.values.push_back(reinterpret_cast(std::data(value))); - p.lengths.push_back(check_cast(std::ssize(value), s_overflow)); + p.lengths.push_back( + check_cast(std::ssize(value), s_overflow, loc)); } p.formats.push_back(param_format(value)); diff --git a/src/pipeline.cxx b/src/pipeline.cxx index 7eb7c38aa..4014fbab7 100644 --- a/src/pipeline.cxx +++ b/src/pipeline.cxx @@ -35,9 +35,9 @@ constexpr std::string_view theSeparator{"; "sv}, theDummyValue{"1"sv}, } // namespace -void pqxx::pipeline::init() +void pqxx::pipeline::init(sl loc) { - m_encoding = internal::enc_group(m_trans->conn().encoding_id()); + m_encoding = internal::enc_group(m_trans->conn().encoding_id(loc), loc); m_issuedrange = make_pair(std::end(m_queries), std::end(m_queries)); attach(); } @@ -45,9 +45,11 @@ void pqxx::pipeline::init() pqxx::pipeline::~pipeline() noexcept { + // TODO: How can we pass std::source_location here? + sl const loc{sl::current()}; try { - cancel(); + cancel(loc); } catch (std::exception const &) {} @@ -69,7 +71,7 @@ void pqxx::pipeline::detach() } -pqxx::pipeline::query_id pqxx::pipeline::insert(std::string_view q) & +pqxx::pipeline::query_id pqxx::pipeline::insert(std::string_view q, sl loc) & { attach(); query_id const qid{generate_id()}; @@ -86,34 +88,34 @@ pqxx::pipeline::query_id pqxx::pipeline::insert(std::string_view q) & if (m_num_waiting > m_retain) { if (have_pending()) - receive_if_available(); + receive_if_available(loc); if (not have_pending()) - issue(); + issue(loc); } return qid; } -void pqxx::pipeline::complete() +void pqxx::pipeline::complete(sl loc) { if (have_pending()) - receive(m_issuedrange.second); + receive(m_issuedrange.second, loc); if (m_num_waiting and (m_error == qid_limit())) { - issue(); - receive(std::end(m_queries)); + issue(loc); + receive(std::end(m_queries), loc); } detach(); } -void pqxx::pipeline::flush() +void pqxx::pipeline::flush(sl loc) { if (not std::empty(m_queries)) { if (have_pending()) - receive(m_issuedrange.second); + receive(m_issuedrange.second, loc); m_issuedrange.first = m_issuedrange.second = std::end(m_queries); m_num_waiting = 0; m_dummy_pending = false; @@ -123,11 +125,12 @@ void pqxx::pipeline::flush() } -void PQXX_COLD pqxx::pipeline::cancel() +void PQXX_COLD pqxx::pipeline::cancel(sl loc) { while (have_pending()) { - pqxx::internal::gate::connection_pipeline(m_trans->conn()).cancel_query(); + pqxx::internal::gate::connection_pipeline(m_trans->conn()) + .cancel_query(loc); auto canceled_query{m_issuedrange.first}; ++m_issuedrange.first; m_queries.erase(canceled_query); @@ -146,11 +149,12 @@ bool pqxx::pipeline::is_finished(pipeline::query_id q) const } -std::pair pqxx::pipeline::retrieve() +std::pair +pqxx::pipeline::retrieve(sl loc) { if (std::empty(m_queries)) throw std::logic_error{"Attempt to retrieve result from empty pipeline."}; - return retrieve(std::begin(m_queries)); + return retrieve(std::begin(m_queries), loc); } @@ -170,14 +174,14 @@ int pqxx::pipeline::retain(int retain_max) & } -void pqxx::pipeline::resume() & +void pqxx::pipeline::resume(sl loc) & { if (have_pending()) - receive_if_available(); + receive_if_available(loc); if (not have_pending() and m_num_waiting) { - issue(); - receive_if_available(); + issue(loc); + receive_if_available(loc); } } @@ -191,10 +195,10 @@ pqxx::pipeline::query_id pqxx::pipeline::generate_id() } -void pqxx::pipeline::issue() +void pqxx::pipeline::issue(sl loc) { // Retrieve that null result for the last query, if needed. - obtain_result(); + obtain_result(false, loc); // Don't issue anything if we've encountered an error. if (m_error < qid_limit()) @@ -220,18 +224,18 @@ void pqxx::pipeline::issue() m_dummy_pending = prepend_dummy; m_issuedrange.first = oldest; m_issuedrange.second = std::end(m_queries); - m_num_waiting -= check_cast(num_issued, "pipeline issue()"sv); + m_num_waiting -= check_cast(num_issued, "pipeline issue()"sv, loc); } -void PQXX_COLD pqxx::pipeline::internal_error(std::string const &err) +void PQXX_COLD pqxx::pipeline::internal_error(std::string const &err, sl loc) { set_error_at(0); - throw pqxx::internal_error{err}; + throw pqxx::internal_error{err, loc}; } -bool pqxx::pipeline::obtain_result(bool expect_none) +bool pqxx::pipeline::obtain_result(bool expect_none, sl loc) { pqxx::internal::gate::connection_pipeline gate{m_trans->conn()}; std::shared_ptr const r{ @@ -260,7 +264,7 @@ bool pqxx::pipeline::obtain_result(bool expect_none) // Must be the result for the oldest pending query. if (not std::empty(m_issuedrange.first->second.res)) [[unlikely]] - internal_error("Multiple results for one query."); + internal_error("Multiple results for one query.", loc); m_issuedrange.first->second.res = res; ++m_issuedrange.first; @@ -269,7 +273,7 @@ bool pqxx::pipeline::obtain_result(bool expect_none) } -void pqxx::pipeline::obtain_dummy() +void pqxx::pipeline::obtain_dummy(sl loc) { // Allocate once, re-use across invocations. static auto const text{ @@ -282,7 +286,7 @@ void pqxx::pipeline::obtain_dummy() if (not r) [[unlikely]] internal_error( - "Pipeline got no result from backend when it expected one."); + "Pipeline got no result from backend when it expected one.", loc); pqxx::internal::gate::connection_pipeline const pgate{m_trans->conn()}; auto handler{pgate.get_notice_waiters()}; @@ -292,7 +296,7 @@ void pqxx::pipeline::obtain_dummy() bool OK{false}; try { - pqxx::internal::gate::result_creation{R}.check_status(); + pqxx::internal::gate::result_creation{R}.check_status(loc); OK = true; } catch (sql_error const &) @@ -300,10 +304,11 @@ void pqxx::pipeline::obtain_dummy() if (OK) [[likely]] { if (std::size(R) > 1) [[unlikely]] - internal_error("Unexpected result for dummy query in pipeline."); + internal_error("Unexpected result for dummy query in pipeline.", loc); - if (R.at(0).at(0).as() != theDummyValue) [[unlikely]] - internal_error("Dummy query in pipeline returned unexpected value."); + if (R.at(0).at(0).as(loc) != theDummyValue) [[unlikely]] + internal_error( + "Dummy query in pipeline returned unexpected value.", loc); return; } @@ -327,11 +332,12 @@ void pqxx::pipeline::obtain_dummy() auto const stop{m_issuedrange.second}; // Retrieve that null result for the last query, if needed - obtain_result(true); + obtain_result(true, loc); // Reset internal state to forget botched batch attempt m_num_waiting += check_cast( - std::distance(m_issuedrange.first, stop), "pipeline obtain_dummy()"sv); + std::distance(m_issuedrange.first, stop), "pipeline obtain_dummy()"sv, + loc); m_issuedrange.second = m_issuedrange.first; // Issue queries in failed batch one at a time. @@ -342,8 +348,8 @@ void pqxx::pipeline::obtain_dummy() m_num_waiting--; auto const query{*m_issuedrange.first->second.query}; auto &holder{m_issuedrange.first->second}; - holder.res = m_trans->exec(query); - pqxx::internal::gate::result_creation{holder.res}.check_status(); + holder.res = m_trans->exec(query, loc); + pqxx::internal::gate::result_creation{holder.res}.check_status(loc); ++m_issuedrange.first; } while (m_issuedrange.first != stop); } @@ -359,7 +365,7 @@ void pqxx::pipeline::obtain_dummy() std::pair -pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q) +pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q, sl loc) { if (q == std::end(m_queries)) throw std::logic_error{"Attempt to retrieve result for unknown query."}; @@ -374,9 +380,9 @@ pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q) (q->first >= m_issuedrange.second->first)) { if (have_pending()) - receive(m_issuedrange.second); + receive(m_issuedrange.second, loc); if (m_error == qid_limit()) - issue(); + issue(loc); } // If result not in yet, get it; else get at least whatever's convenient. @@ -386,11 +392,11 @@ pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q) { auto suc{q}; ++suc; - receive(suc); + receive(suc, loc); } else { - receive_if_available(); + receive_if_available(loc); } } @@ -400,51 +406,51 @@ pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q) // Don't leave the backend idle if there are queries waiting to be issued. if (m_num_waiting and not have_pending() and (m_error == qid_limit())) - issue(); + issue(loc); result const R{q->second.res}; auto P{std::make_pair(q->first, R)}; m_queries.erase(q); - pqxx::internal::gate::result_creation{R}.check_status(); + pqxx::internal::gate::result_creation{R}.check_status(loc); return P; } -void pqxx::pipeline::get_further_available_results() +void pqxx::pipeline::get_further_available_results(sl loc) { pqxx::internal::gate::connection_pipeline gate{m_trans->conn()}; - while (not gate.is_busy() and obtain_result()) + while (not gate.is_busy() and obtain_result(false, loc)) if (not gate.consume_input()) - throw broken_connection{}; + throw broken_connection{loc}; } -void pqxx::pipeline::receive_if_available() +void pqxx::pipeline::receive_if_available(sl loc) { pqxx::internal::gate::connection_pipeline gate{m_trans->conn()}; if (not gate.consume_input()) - throw broken_connection{}; + throw broken_connection{loc}; if (gate.is_busy()) return; if (m_dummy_pending) - obtain_dummy(); + obtain_dummy(loc); if (have_pending()) - get_further_available_results(); + get_further_available_results(loc); } -void pqxx::pipeline::receive(pipeline::QueryMap::const_iterator stop) +void pqxx::pipeline::receive(pipeline::QueryMap::const_iterator stop, sl loc) { if (m_dummy_pending) - obtain_dummy(); + obtain_dummy(loc); - while (obtain_result() and + while (obtain_result(false, loc) and QueryMap::const_iterator{m_issuedrange.first} != stop); // Also haul in any remaining "targets of opportunity". if (QueryMap::const_iterator{m_issuedrange.first} == stop) - get_further_available_results(); + get_further_available_results(loc); } diff --git a/src/result.cxx b/src/result.cxx index ad13f207f..0e7c03d81 100644 --- a/src/result.cxx +++ b/src/result.cxx @@ -75,25 +75,25 @@ bool pqxx::result::operator==(result const &rhs) const noexcept } -pqxx::result::const_reverse_iterator pqxx::result::rbegin() const +pqxx::result::const_reverse_iterator pqxx::result::rbegin() const noexcept { return const_reverse_iterator{end()}; } -pqxx::result::const_reverse_iterator pqxx::result::crbegin() const +pqxx::result::const_reverse_iterator pqxx::result::crbegin() const noexcept { return rbegin(); } -pqxx::result::const_reverse_iterator pqxx::result::rend() const +pqxx::result::const_reverse_iterator pqxx::result::rend() const noexcept { return const_reverse_iterator{begin()}; } -pqxx::result::const_reverse_iterator pqxx::result::crend() const +pqxx::result::const_reverse_iterator pqxx::result::crend() const noexcept { return rend(); } @@ -159,21 +159,21 @@ pqxx::field pqxx::result::operator[]( #endif // PQXX_HAVE_MULTIDIM -pqxx::row pqxx::result::at(pqxx::result::size_type i) const +pqxx::row pqxx::result::at(pqxx::result::size_type i, sl loc) const { if (i >= size()) - throw range_error{"Row number out of range."}; + throw range_error{"Row number out of range.", loc}; return operator[](i); } pqxx::field pqxx::result::at( - pqxx::result_size_type row_num, pqxx::row_size_type col_num) const + pqxx::result_size_type row_num, pqxx::row_size_type col_num, sl loc) const { if (row_num >= size()) - throw range_error{"Row number out of range."}; + throw range_error{"Row number out of range.", loc}; if (col_num >= columns()) - throw range_error{"Column out of range."}; + throw range_error{"Column out of range.", loc}; return {*this, row_num, col_num}; } @@ -188,7 +188,7 @@ inline bool equal(char const lhs[], char const rhs[]) } // namespace void PQXX_COLD pqxx::result::throw_sql_error( - std::string const &Err, std::string const &Query) const + std::string const &Err, std::string const &Query, sl loc) const { // Try to establish more precise error type, and throw corresponding // type of exception. @@ -197,7 +197,7 @@ void PQXX_COLD pqxx::result::throw_sql_error( { // No SQLSTATE at all. Can this even happen? // Let's assume the connection is no longer usable. - throw broken_connection{Err}; + throw broken_connection{Err, loc}; } switch (code[0]) @@ -209,44 +209,44 @@ void PQXX_COLD pqxx::result::throw_sql_error( // connection was just fine, so we had no real way of detecting the // problem. (Trying to continue to use the connection does break // though, so I feel justified in panicking.) - throw broken_connection{Err}; + throw broken_connection{Err, loc}; case '0': switch (code[1]) { - case 'A': throw feature_not_supported{Err, Query, code}; + case 'A': throw feature_not_supported{Err, Query, code, loc}; case '8': if (equal(code, "08P01")) - throw protocol_violation{Err}; - throw broken_connection{Err}; + throw protocol_violation{Err, loc}; + throw broken_connection{Err, loc}; case 'L': - case 'P': throw insufficient_privilege{Err, Query, code}; + case 'P': throw insufficient_privilege{Err, Query, code, loc}; } break; case '2': switch (code[1]) { - case '2': throw data_exception{Err, Query, code}; + case '2': throw data_exception{Err, Query, code, loc}; case '3': if (equal(code, "23001")) - throw restrict_violation{Err, Query, code}; + throw restrict_violation{Err, Query, code, loc}; if (equal(code, "23502")) - throw not_null_violation{Err, Query, code}; + throw not_null_violation{Err, Query, code, loc}; if (equal(code, "23503")) - throw foreign_key_violation{Err, Query, code}; + throw foreign_key_violation{Err, Query, code, loc}; if (equal(code, "23505")) - throw unique_violation{Err, Query, code}; + throw unique_violation{Err, Query, code, loc}; if (equal(code, "23514")) - throw check_violation{Err, Query, code}; - throw integrity_constraint_violation{Err, Query, code}; - case '4': throw invalid_cursor_state{Err, Query, code}; - case '6': throw invalid_sql_statement_name{Err, Query, code}; + throw check_violation{Err, Query, code, loc}; + throw integrity_constraint_violation{Err, Query, code, loc}; + case '4': throw invalid_cursor_state{Err, Query, code, loc}; + case '6': throw invalid_sql_statement_name{Err, Query, code, loc}; } break; case '3': switch (code[1]) { - case '4': throw invalid_cursor_name{Err, Query, code}; + case '4': throw invalid_cursor_name{Err, Query, code, loc}; } break; case '4': @@ -254,25 +254,25 @@ void PQXX_COLD pqxx::result::throw_sql_error( { case '0': if (equal(code, "40000")) - throw transaction_rollback{Err, Query, code}; + throw transaction_rollback{Err, Query, code, loc}; if (equal(code, "40001")) - throw serialization_failure{Err, Query, code}; + throw serialization_failure{Err, Query, code, loc}; if (equal(code, "40003")) - throw statement_completion_unknown{Err, Query, code}; + throw statement_completion_unknown{Err, Query, code, loc}; if (equal(code, "40P01")) - throw deadlock_detected{Err, Query, code}; + throw deadlock_detected{Err, Query, code, loc}; break; case '2': if (equal(code, "42501")) - throw insufficient_privilege{Err, Query}; + throw insufficient_privilege{Err, Query, nullptr, loc}; if (equal(code, "42601")) - throw syntax_error{Err, Query, code, errorposition()}; + throw syntax_error{Err, Query, code, errorposition(), loc}; if (equal(code, "42703")) - throw undefined_column{Err, Query, code}; + throw undefined_column{Err, Query, code, loc}; if (equal(code, "42883")) - throw undefined_function{Err, Query, code}; + throw undefined_function{Err, Query, code, loc}; if (equal(code, "42P01")) - throw undefined_table{Err, Query, code}; + throw undefined_table{Err, Query, code, loc}; } break; case '5': @@ -280,44 +280,44 @@ void PQXX_COLD pqxx::result::throw_sql_error( { case '3': if (equal(code, "53100")) - throw disk_full{Err, Query, code}; + throw disk_full{Err, Query, code, loc}; if (equal(code, "53200")) - throw out_of_memory{Err, Query, code}; + throw out_of_memory{Err, Query, code, loc}; if (equal(code, "53300")) - throw too_many_connections{Err}; - throw insufficient_resources{Err, Query, code}; + throw too_many_connections{Err, loc}; + throw insufficient_resources{Err, Query, code, loc}; } break; case 'P': if (equal(code, "P0001")) - throw plpgsql_raise{Err, Query, code}; + throw plpgsql_raise{Err, Query, code, loc}; if (equal(code, "P0002")) - throw plpgsql_no_data_found{Err, Query, code}; + throw plpgsql_no_data_found{Err, Query, code, loc}; if (equal(code, "P0003")) - throw plpgsql_too_many_rows{Err, Query, code}; + throw plpgsql_too_many_rows{Err, Query, code, loc}; throw plpgsql_error{Err, Query, code}; } // Unknown error code. - throw sql_error{Err, Query, code}; + throw sql_error{Err, Query, code, loc}; } -void pqxx::result::check_status(std::string_view desc) const +void pqxx::result::check_status(std::string_view desc, sl loc) const { - if (auto err{status_error()}; not std::empty(err)) [[unlikely]] + if (auto err{status_error(loc)}; not std::empty(err)) [[unlikely]] { if (not std::empty(desc)) err = pqxx::internal::concat("Failure during '", desc, "': ", err); - throw_sql_error(err, query()); + throw_sql_error(err, query(), loc); } } -std::string pqxx::result::status_error() const +std::string pqxx::result::status_error(sl loc) const { if (m_data.get() == nullptr) - throw failure{"No result set given."}; + throw failure{"No result set given.", loc}; std::string err; @@ -334,7 +334,8 @@ std::string pqxx::result::status_error() const #if defined(LIBPQ_HAS_PIPELINING) case PGRES_PIPELINE_SYNC: // Pipeline mode synchronisation point. case PGRES_PIPELINE_ABORTED: // Previous command in pipeline failed. - throw feature_not_supported{"Not supported yet: libpq pipelines."}; + throw feature_not_supported{ + "Not supported yet: libpq pipelines.", "", nullptr, loc}; #endif case PGRES_BAD_RESPONSE: // The server's response was not understood. @@ -344,12 +345,15 @@ std::string pqxx::result::status_error() const break; case PGRES_SINGLE_TUPLE: - throw feature_not_supported{"Not supported: single-row mode."}; + throw feature_not_supported{ + "Not supported: single-row mode.", "", nullptr, loc}; default: - throw internal_error{internal::concat( - "pqxx::result: Unrecognized result status code ", - PQresultStatus(m_data.get()))}; + throw internal_error{ + internal::concat( + "pqxx::result: Unrecognized result status code ", + PQresultStatus(m_data.get())), + loc}; } return err; } @@ -371,11 +375,11 @@ std::string const &pqxx::result::query() const & noexcept } -pqxx::oid pqxx::result::inserted_oid() const +pqxx::oid pqxx::result::inserted_oid(sl loc) const { if (m_data.get() == nullptr) throw usage_error{ - "Attempt to read oid of inserted row without an INSERT result"}; + "Attempt to read oid of inserted row without an INSERT result", loc}; return PQoidValue(m_data.get()); } @@ -413,29 +417,31 @@ pqxx::field::size_type pqxx::result::get_length( } -pqxx::oid pqxx::result::column_type(row::size_type col_num) const +pqxx::oid pqxx::result::column_type(row::size_type col_num, sl loc) const { oid const t{PQftype(m_data.get(), col_num)}; if (t == oid_none) - throw argument_error{internal::concat( - "Attempt to retrieve type of nonexistent column ", col_num, - " of query result.")}; + throw argument_error{ + internal::concat( + "Attempt to retrieve type of nonexistent column ", col_num, + " of query result."), + loc}; return t; } -pqxx::row::size_type pqxx::result::column_number(zview col_name) const +pqxx::row::size_type pqxx::result::column_number(zview col_name, sl loc) const { auto const n{PQfnumber(m_data.get(), col_name.c_str())}; if (n == -1) throw argument_error{ - internal::concat("Unknown column name: '", col_name, "'.")}; + internal::concat("Unknown column name: '", col_name, "'."), loc}; return static_cast(n); } -pqxx::oid pqxx::result::column_table(row::size_type col_num) const +pqxx::oid pqxx::result::column_table(row::size_type col_num, sl loc) const { oid const t{PQftable(m_data.get(), col_num)}; @@ -443,15 +449,18 @@ pqxx::oid pqxx::result::column_table(row::size_type col_num) const * we got an invalid row number. */ if (t == oid_none and col_num >= columns()) - throw argument_error{internal::concat( - "Attempt to retrieve table ID for column ", col_num, " out of ", - columns())}; + throw argument_error{ + internal::concat( + "Attempt to retrieve table ID for column ", col_num, " out of ", + columns()), + loc}; return t; } -pqxx::row::size_type pqxx::result::table_column(row::size_type col_num) const +pqxx::row::size_type +pqxx::result::table_column(row::size_type col_num, sl loc) const { auto const n{row::size_type(PQftablecol(m_data.get(), col_num))}; if (n != 0) [[likely]] @@ -461,16 +470,21 @@ pqxx::row::size_type pqxx::result::table_column(row::size_type col_num) const auto const col_str{to_string(col_num)}; if (col_num > columns()) throw range_error{ - internal::concat("Invalid column index in table_column(): ", col_str)}; + internal::concat("Invalid column index in table_column(): ", col_str), + loc}; if (m_data.get() == nullptr) - throw usage_error{internal::concat( - "Can't query origin of column ", col_str, - ": result is not initialized.")}; + throw usage_error{ + internal::concat( + "Can't query origin of column ", col_str, + ": result is not initialized."), + loc}; - throw usage_error{internal::concat( - "Can't query origin of column ", col_str, - ": not derived from table column.")}; + throw usage_error{ + internal::concat( + "Can't query origin of column ", col_str, + ": not derived from table column."), + loc}; } @@ -487,16 +501,19 @@ int pqxx::result::errorposition() const } -char const *pqxx::result::column_name(pqxx::row::size_type number) const & +char const * +pqxx::result::column_name(pqxx::row::size_type number, sl loc) const & { auto const n{PQfname(m_data.get(), number)}; if (n == nullptr) [[unlikely]] { if (m_data.get() == nullptr) - throw usage_error{"Queried column name on null result."}; - throw range_error{internal::concat( - "Invalid column number: ", number, " (maximum is ", (columns() - 1), - ").")}; + throw usage_error{"Queried column name on null result.", loc}; + throw range_error{ + internal::concat( + "Invalid column number: ", number, " (maximum is ", (columns() - 1), + ")."), + loc}; } return n; } @@ -508,17 +525,21 @@ pqxx::row::size_type pqxx::result::columns() const noexcept } -int pqxx::result::column_storage(pqxx::row::size_type number) const +int pqxx::result::column_storage(pqxx::row::size_type number, sl loc) const { int const out{PQfsize(m_data.get(), number)}; if (out == 0) { auto const sz{this->size()}; if ((number < 0) or (number >= sz)) - throw argument_error{pqxx::internal::concat( - "Column number out of range: ", number, " (have 0 - ", sz, ")")}; - throw failure{pqxx::internal::concat( - "Error getting column_storage for column ", number)}; + throw argument_error{ + pqxx::internal::concat( + "Column number out of range: ", number, " (have 0 - ", sz, ")"), + loc}; + throw failure{ + pqxx::internal::concat( + "Error getting column_storage for column ", number), + loc}; } return out; } @@ -531,7 +552,7 @@ int pqxx::result::column_type_modifier( } -pqxx::row pqxx::result::one_row() const +pqxx::row pqxx::result::one_row(sl loc) const { auto const sz{size()}; if (sz != 1) @@ -539,34 +560,41 @@ pqxx::row pqxx::result::one_row() const // TODO: See whether result contains a generated statement. if (not m_query or m_query->empty()) throw unexpected_rows{ - pqxx::internal::concat("Expected 1 row from query, got ", sz, ".")}; + pqxx::internal::concat("Expected 1 row from query, got ", sz, "."), + loc}; else - throw unexpected_rows{pqxx::internal::concat( - "Expected 1 row from query '", *m_query, "', got ", sz, ".")}; + throw unexpected_rows{ + pqxx::internal::concat( + "Expected 1 row from query '", *m_query, "', got ", sz, "."), + loc}; } return front(); } -pqxx::field pqxx::result::one_field() const +pqxx::field pqxx::result::one_field(sl loc) const { - expect_columns(1); - return one_row()[0]; + expect_columns(1, loc); + return one_row(loc)[0]; } -std::optional pqxx::result::opt_row() const +std::optional pqxx::result::opt_row(sl loc) const { auto const sz{size()}; if (sz > 1) { // TODO: See whether result contains a generated statement. if (not m_query or m_query->empty()) - throw unexpected_rows{pqxx::internal::concat( - "Expected at most 1 row from query, got ", sz, ".")}; + throw unexpected_rows{ + pqxx::internal::concat( + "Expected at most 1 row from query, got ", sz, "."), + loc}; else - throw unexpected_rows{pqxx::internal::concat( - "Expected at most 1 row from query '", *m_query, "', got ", sz, ".")}; + throw unexpected_rows{ + pqxx::internal::concat( + "Expected at most 1 row from query '", *m_query, "', got ", sz, "."), + loc}; } else if (sz == 1) { diff --git a/src/robusttransaction.cxx b/src/robusttransaction.cxx index f2726e33b..7c05d7bf2 100644 --- a/src/robusttransaction.cxx +++ b/src/robusttransaction.cxx @@ -72,55 +72,59 @@ constexpr tx_stat parse_status(std::string_view text) noexcept } -tx_stat query_status(std::string const &xid, std::string const &conn_str) +tx_stat query_status( + std::string const &xid, std::string const &conn_str, + pqxx::sl loc = pqxx::sl::current()) { static std::string const name{"robusttxck"sv}; auto const query{pqxx::internal::concat("SELECT txid_status(", xid, ")")}; - pqxx::connection cx{conn_str}; + pqxx::connection cx{conn_str, loc}; pqxx::nontransaction tx{cx, name}; - auto const status_row{tx.exec(query).one_row()}; + auto const status_row{tx.exec(query, loc).one_row(loc)}; auto const status_field{status_row[0]}; if (std::size(status_field) == 0) - throw pqxx::internal_error{"Transaction status string is empty."}; - auto const status{parse_status(status_field.as())}; + throw pqxx::internal_error{"Transaction status string is empty.", loc}; + auto const status{parse_status(status_field.as(loc))}; if (status == tx_unknown) - throw pqxx::internal_error{pqxx::internal::concat( - "Unknown transaction status string: ", status_field.view())}; + throw pqxx::internal_error{ + pqxx::internal::concat( + "Unknown transaction status string: ", status_field.view()), + loc}; return status; } } // namespace -void pqxx::internal::basic_robusttransaction::init(zview begin_command) +void pqxx::internal::basic_robusttransaction::init(zview begin_command, sl loc) { static auto const txid_q{ std::make_shared("SELECT txid_current()"sv)}; m_backendpid = conn().backendpid(); - direct_exec(begin_command); - direct_exec(txid_q).one_field().to(m_xid); + direct_exec(begin_command, loc); + direct_exec(txid_q, loc).one_field(loc).to(m_xid); } pqxx::internal::basic_robusttransaction::basic_robusttransaction( - connection &cx, zview begin_command, std::string_view tname) : + connection &cx, zview begin_command, std::string_view tname, sl loc) : dbtransaction(cx, tname), m_conn_string{cx.connection_string()} { - init(begin_command); + init(begin_command, loc); } pqxx::internal::basic_robusttransaction::basic_robusttransaction( - connection &cx, zview begin_command) : + connection &cx, zview begin_command, sl loc) : dbtransaction(cx), m_conn_string{cx.connection_string()} { - init(begin_command); + init(begin_command, loc); } pqxx::internal::basic_robusttransaction::~basic_robusttransaction() = default; -void pqxx::internal::basic_robusttransaction::do_commit() +void pqxx::internal::basic_robusttransaction::do_commit(sl loc) { static auto const check_constraints_q{ std::make_shared("SET CONSTRAINTS ALL IMMEDIATE"sv)}, @@ -129,11 +133,11 @@ void pqxx::internal::basic_robusttransaction::do_commit() // minimise our in-doubt window. try { - direct_exec(check_constraints_q); + direct_exec(check_constraints_q, loc); } catch (std::exception const &) { - do_abort(); + do_abort(loc); throw; } @@ -148,7 +152,7 @@ void pqxx::internal::basic_robusttransaction::do_commit() // robusttransaction what it is. try { - direct_exec(commit_q); + direct_exec(commit_q, loc); // If we make it here, great. Normal, successful commit. return; @@ -163,7 +167,7 @@ void pqxx::internal::basic_robusttransaction::do_commit() if (conn().is_open()) { // Commit failed, for some other reason. - do_abort(); + do_abort(loc); throw; } // Otherwise, fall through to in-doubt handling. @@ -180,7 +184,7 @@ void pqxx::internal::basic_robusttransaction::do_commit() { try { - switch (query_status(m_xid, m_conn_string)) + switch (query_status(m_xid, m_conn_string, loc)) { case tx_unknown: // We were unable to reconnect and query transaction status. @@ -191,7 +195,7 @@ void pqxx::internal::basic_robusttransaction::do_commit() return; case tx_aborted: // Aborted. We're done. - do_abort(); + do_abort(loc); return; case tx_in_progress: // The transaction is still running. Stick around until we know what @@ -208,14 +212,16 @@ void pqxx::internal::basic_robusttransaction::do_commit() } // Okay, this has taken too long. Give up, report in-doubt state. - throw in_doubt_error{internal::concat( - "Transaction ", name(), " (with transaction ID ", m_xid, - ") " - "lost connection while committing. It's impossible to tell whether " - "it committed, or aborted, or is still running. " - "Attempts to find out its outcome have failed. " - "The backend process on the server had process ID ", - m_backendpid, - ". " - "You may be able to check what happened to that process.")}; + throw in_doubt_error{ + internal::concat( + "Transaction ", name(), " (with transaction ID ", m_xid, + ") " + "lost connection while committing. It's impossible to tell whether " + "it committed, or aborted, or is still running. " + "Attempts to find out its outcome have failed. " + "The backend process on the server had process ID ", + m_backendpid, + ". " + "You may be able to check what happened to that process."), + loc}; } diff --git a/src/row.cxx b/src/row.cxx index 63972314c..78ea7fa1a 100644 --- a/src/row.cxx +++ b/src/row.cxx @@ -114,7 +114,8 @@ pqxx::row::reference pqxx::row::operator[](size_type i) const noexcept pqxx::row::reference pqxx::row::operator[](zview col_name) const { - return at(col_name); + sl const loc{sl::current()}; + return at(col_name, loc); } @@ -130,42 +131,42 @@ void pqxx::row::swap(row &rhs) noexcept } -pqxx::field pqxx::row::at(zview col_name) const +pqxx::field pqxx::row::at(zview col_name, sl loc) const { - return {m_result, m_index, column_number(col_name)}; + return {m_result, m_index, column_number(col_name, loc)}; } -pqxx::field pqxx::row::at(pqxx::row::size_type i) const +pqxx::field pqxx::row::at(pqxx::row::size_type i, sl loc) const { if (i >= size()) - throw range_error{"Invalid field number."}; + throw range_error{"Invalid field number.", loc}; return operator[](i); } -pqxx::oid pqxx::row::column_type(size_type col_num) const +pqxx::oid pqxx::row::column_type(size_type col_num, sl loc) const { - return m_result.column_type(col_num); + return m_result.column_type(col_num, loc); } -pqxx::oid pqxx::row::column_table(size_type col_num) const +pqxx::oid pqxx::row::column_table(size_type col_num, sl loc) const { - return m_result.column_table(col_num); + return m_result.column_table(col_num, loc); } -pqxx::row::size_type pqxx::row::table_column(size_type col_num) const +pqxx::row::size_type pqxx::row::table_column(size_type col_num, sl loc) const { - return m_result.table_column(col_num); + return m_result.table_column(col_num, loc); } -pqxx::row::size_type pqxx::row::column_number(zview col_name) const +pqxx::row::size_type pqxx::row::column_number(zview col_name, sl loc) const { - return m_result.column_number(col_name); + return m_result.column_number(col_name, loc); } diff --git a/src/sql_cursor.cxx b/src/sql_cursor.cxx index 4a7c2537c..e3bc6225f 100644 --- a/src/sql_cursor.cxx +++ b/src/sql_cursor.cxx @@ -57,8 +57,8 @@ inline bool useless_trail(char c) * * The query must be nonempty. */ -std::string::size_type -find_query_end(std::string_view query, pqxx::internal::encoding_group enc) +std::string::size_type find_query_end( + std::string_view query, pqxx::internal::encoding_group enc, pqxx::sl loc) { auto const text{std::data(query)}; auto const size{std::size(query)}; @@ -81,7 +81,7 @@ find_query_end(std::string_view query, pqxx::internal::encoding_group enc) if (gend - gbegin > 1 or not useless_trail(*gbegin)) end = std::string::size_type(gend - text); }, - text, size); + text, size, 0u, loc); } return end; @@ -92,18 +92,18 @@ find_query_end(std::string_view query, pqxx::internal::encoding_group enc) pqxx::internal::sql_cursor::sql_cursor( transaction_base &t, std::string_view query, std::string_view cname, cursor_base::access_policy ap, cursor_base::update_policy up, - cursor_base::ownership_policy op, bool hold) : + cursor_base::ownership_policy op, bool hold, sl loc) : cursor_base{t.conn(), cname}, m_home{t.conn()}, m_at_end{-1}, m_pos{0} { if (&t.conn() != &m_home) - throw internal_error{"Cursor in wrong connection"}; + throw internal_error{"Cursor in wrong connection", loc}; if (std::empty(query)) - throw usage_error{"Cursor has empty query."}; - auto const enc{enc_group(t.conn().encoding_id())}; - auto const qend{find_query_end(query, enc)}; + throw usage_error{"Cursor has empty query.", loc}; + auto const enc{enc_group(t.conn().encoding_id(loc), loc)}; + auto const qend{find_query_end(query, enc, loc)}; if (qend == 0) - throw usage_error{"Cursor has effectively empty query."}; + throw usage_error{"Cursor has effectively empty query.", loc}; query.remove_suffix(std::size(query) - qend); std::string const cq{internal::concat( @@ -112,13 +112,13 @@ pqxx::internal::sql_cursor::sql_cursor( (hold ? "WITH HOLD "sv : ""sv), "FOR "sv, query, " "sv, ((up == cursor_base::update) ? "FOR UPDATE "sv : "FOR READ ONLY "sv))}; - t.exec(cq); + t.exec(cq, loc); // Now that we're here in the starting position, keep a copy of an empty // result. That may come in handy later, because we may not be able to // construct an empty result with all the right metadata due to the weird // meaning of "FETCH 0." - init_empty_result(t); + init_empty_result(t, loc); m_ownership = op; } @@ -135,14 +135,14 @@ pqxx::internal::sql_cursor::sql_cursor( {} -void pqxx::internal::sql_cursor::close() noexcept +void pqxx::internal::sql_cursor::close(sl loc) noexcept { if (m_ownership == cursor_base::owned) { try { gate::connection_sql_cursor{m_home}.exec( - internal::concat("CLOSE "sv, m_home.quote_name(name())).c_str()); + internal::concat("CLOSE "sv, m_home.quote_name(name())).c_str(), loc); } catch (std::exception const &) {} @@ -151,12 +151,12 @@ void pqxx::internal::sql_cursor::close() noexcept } -void pqxx::internal::sql_cursor::init_empty_result(transaction_base &t) +void pqxx::internal::sql_cursor::init_empty_result(transaction_base &t, sl loc) { if (pos() != 0) - throw internal_error{"init_empty_result() from bad pos()."}; + throw internal_error{"init_empty_result() from bad pos().", loc}; m_empty_result = - t.exec(internal::concat("FETCH 0 IN "sv, m_home.quote_name(name()))); + t.exec(internal::concat("FETCH 0 IN "sv, m_home.quote_name(name())), loc); } @@ -217,7 +217,7 @@ pqxx::internal::sql_cursor::difference_type pqxx::internal::sql_cursor::adjust( pqxx::result pqxx::internal::sql_cursor::fetch( - difference_type rows, difference_type &displacement) + difference_type rows, difference_type &displacement, sl loc) { if (rows == 0) { @@ -226,14 +226,14 @@ pqxx::result pqxx::internal::sql_cursor::fetch( } auto const query{pqxx::internal::concat( "FETCH "sv, stridestring(rows), " IN "sv, m_home.quote_name(name()))}; - auto r{gate::connection_sql_cursor{m_home}.exec(query.c_str())}; + auto r{gate::connection_sql_cursor{m_home}.exec(query.c_str(), loc)}; displacement = adjust(rows, difference_type(std::size(r))); return r; } pqxx::cursor_base::difference_type pqxx::internal::sql_cursor::move( - difference_type rows, difference_type &displacement) + difference_type rows, difference_type &displacement, sl loc) { if (rows == 0) { @@ -243,7 +243,7 @@ pqxx::cursor_base::difference_type pqxx::internal::sql_cursor::move( auto const query{pqxx::internal::concat( "MOVE "sv, stridestring(rows), " IN "sv, m_home.quote_name(name()))}; - auto const r{gate::connection_sql_cursor{m_home}.exec(query.c_str())}; + auto const r{gate::connection_sql_cursor{m_home}.exec(query.c_str(), loc)}; auto d{static_cast(r.affected_rows())}; displacement = adjust(rows, d); return d; diff --git a/src/strconv.cxx b/src/strconv.cxx index d12eb3a32..43d901b76 100644 --- a/src/strconv.cxx +++ b/src/strconv.cxx @@ -234,15 +234,17 @@ std::string demangle_type_name(char const raw[]) // TODO: Equivalents for converting a null in the other direction. -void PQXX_COLD throw_null_conversion(std::string const &type) +void PQXX_COLD throw_null_conversion(std::string const &type, sl loc) { - throw conversion_error{concat("Attempt to convert SQL null to ", type, ".")}; + throw conversion_error{ + concat("Attempt to convert SQL null to ", type, "."), loc}; } -void PQXX_COLD throw_null_conversion(std::string_view type) +void PQXX_COLD throw_null_conversion(std::string_view type, sl loc) { - throw conversion_error{concat("Attempt to convert SQL null to ", type, ".")}; + throw conversion_error{ + concat("Attempt to convert SQL null to ", type, "."), loc}; } diff --git a/src/stream_from.cxx b/src/stream_from.cxx index 98d7ace7b..b51a610b2 100644 --- a/src/stream_from.cxx +++ b/src/stream_from.cxx @@ -28,8 +28,9 @@ namespace { pqxx::internal::char_finder_func *get_finder(pqxx::transaction_base const &tx) { - auto const group{pqxx::internal::enc_group(tx.conn().encoding_id())}; - return pqxx::internal::get_char_finder<'\t', '\\'>(group); + pqxx::sl const loc{pqxx::sl::current()}; + auto const group{pqxx::internal::enc_group(tx.conn().encoding_id(loc), loc)}; + return pqxx::internal::get_char_finder<'\t', '\\'>(group, loc); } @@ -41,7 +42,9 @@ pqxx::stream_from::stream_from( transaction_base &tx, from_query_t, std::string_view query) : transaction_focus{tx, class_name}, m_char_finder{get_finder(tx)} { - tx.exec(internal::concat("COPY ("sv, query, ") TO STDOUT"sv)).no_rows(); + sl const loc{sl::current()}; + tx.exec(internal::concat("COPY ("sv, query, ") TO STDOUT"sv), loc) + .no_rows(loc); register_me(); } @@ -50,8 +53,10 @@ pqxx::stream_from::stream_from( transaction_base &tx, from_table_t, std::string_view table) : transaction_focus{tx, class_name, table}, m_char_finder{get_finder(tx)} { - tx.exec(internal::concat("COPY "sv, tx.quote_name(table), " TO STDOUT"sv)) - .no_rows(); + sl const loc{sl::current()}; + tx.exec( + internal::concat("COPY "sv, tx.quote_name(table), " TO STDOUT"sv), loc) + .no_rows(loc); register_me(); } @@ -61,12 +66,15 @@ pqxx::stream_from::stream_from( from_table_t) : transaction_focus{tx, class_name, table}, m_char_finder{get_finder(tx)} { + sl const loc{sl::current()}; if (std::empty(columns)) [[unlikely]] - tx.exec(internal::concat("COPY "sv, table, " TO STDOUT"sv)).no_rows(); + tx.exec(internal::concat("COPY "sv, table, " TO STDOUT"sv), loc) + .no_rows(loc); else [[likely]] tx.exec( - internal::concat("COPY "sv, table, "("sv, columns, ") TO STDOUT"sv)) - .no_rows(); + internal::concat("COPY "sv, table, "("sv, columns, ") TO STDOUT"sv), + loc) + .no_rows(loc); register_me(); } @@ -175,7 +183,7 @@ void pqxx::stream_from::complete() } -void pqxx::stream_from::parse_line() +void pqxx::stream_from::parse_line(sl loc) { if (m_finished) [[unlikely]] return; @@ -191,7 +199,7 @@ void pqxx::stream_from::parse_line() } if (line_size >= (std::numeric_limits::max() / 2)) - throw range_error{"Stream produced a ridiculously long line."}; + throw range_error{"Stream produced a ridiculously long line.", loc}; // Make room for unescaping the line. It's a pessimistic size. // Unusually, we're storing terminating zeroes *inside* the string. @@ -222,7 +230,7 @@ void pqxx::stream_from::parse_line() std::size_t offset{0}; while (offset < line_size) { - auto const stop_char{m_char_finder(line_view, offset)}; + auto const stop_char{m_char_finder(line_view, offset, loc)}; // Copy the text we have so far. It's got no special characters in it. std::memcpy(write, &line_begin[offset], stop_char - offset); write += (stop_char - offset); @@ -256,7 +264,7 @@ void pqxx::stream_from::parse_line() // Escape sequence. assert(special == '\\'); if ((offset) >= line_size) - throw failure{"Row ends in backslash"}; + throw failure{"Row ends in backslash", loc}; // The database will only escape ASCII characters, so no need to use // the glyph scanner. @@ -265,7 +273,7 @@ void pqxx::stream_from::parse_line() { // Null value. if (write != field_begin) - throw failure{"Null sequence found in nonempty field"}; + throw failure{"Null sequence found in nonempty field", loc}; field_begin = nullptr; // (If there's any characters _after_ the null we'll just crash.) } @@ -289,8 +297,8 @@ void pqxx::stream_from::parse_line() } -std::vector const *pqxx::stream_from::read_row() & +std::vector const *pqxx::stream_from::read_row(sl loc) & { - parse_line(); + parse_line(loc); return m_finished ? nullptr : &m_fields; } diff --git a/src/stream_to.cxx b/src/stream_to.cxx index 2af257bc7..8913168e1 100644 --- a/src/stream_to.cxx +++ b/src/stream_to.cxx @@ -25,19 +25,21 @@ namespace using namespace std::literals; void begin_copy( - pqxx::transaction_base &tx, std::string_view table, std::string_view columns) + pqxx::transaction_base &tx, std::string_view table, std::string_view columns, + pqxx::sl loc) { tx.exec( std::empty(columns) ? pqxx::internal::concat("COPY "sv, table, " FROM STDIN"sv) : pqxx::internal::concat( - "COPY "sv, table, "("sv, columns, ") FROM STDIN"sv)) - .no_rows(); + "COPY "sv, table, "("sv, columns, ") FROM STDIN"sv), + loc) + .no_rows(loc); } /// Return the escape character for escaping the given special character. -char escape_char(char special) +char escape_char(char special, pqxx::sl loc) { switch (special) { @@ -50,9 +52,11 @@ char escape_char(char special) case '\\': return '\\'; default: break; } - throw pqxx::internal_error{pqxx::internal::concat( - "Stream escaping unexpectedly stopped at '", - static_cast(static_cast(special)), "'.")}; + throw pqxx::internal_error{ + pqxx::internal::concat( + "Stream escaping unexpectedly stopped at '", + static_cast(static_cast(special)), "'."), + loc}; } } // namespace @@ -70,13 +74,14 @@ pqxx::stream_to::~stream_to() noexcept } -void pqxx::stream_to::write_raw_line(std::string_view text) +void pqxx::stream_to::write_raw_line(std::string_view text, sl loc) { - internal::gate::connection_stream_to{m_trans->conn()}.write_copy_line(text); + internal::gate::connection_stream_to{m_trans->conn()}.write_copy_line( + text, loc); } -void pqxx::stream_to::write_buffer() +void pqxx::stream_to::write_buffer(sl loc) { if (not std::empty(m_buffer)) { @@ -85,7 +90,7 @@ void pqxx::stream_to::write_buffer() assert(m_buffer[std::size(m_buffer) - 1] == '\t'); m_buffer.resize(std::size(m_buffer) - 1); } - write_raw_line(m_buffer); + write_raw_line(m_buffer, loc); m_buffer.clear(); } @@ -97,20 +102,21 @@ pqxx::stream_to &pqxx::stream_to::operator<<(stream_from &tr) const auto [line, size] = tr.get_raw_line(); if (line.get() == nullptr) break; - write_raw_line(std::string_view{line.get(), size}); + write_raw_line(std::string_view{line.get(), size}, sl::current()); } return *this; } pqxx::stream_to::stream_to( - transaction_base &tx, std::string_view path, std::string_view columns) : + transaction_base &tx, std::string_view path, std::string_view columns, + sl loc) : transaction_focus{tx, s_classname, path}, m_finder{pqxx::internal::get_char_finder< '\b', '\f', '\n', '\r', '\t', '\v', '\\'>( - pqxx::internal::enc_group(tx.conn().encoding_id()))} + pqxx::internal::enc_group(tx.conn().encoding_id(loc), loc), loc)} { - begin_copy(tx, path, columns); + begin_copy(tx, path, columns, loc); register_me(); } @@ -126,19 +132,19 @@ void pqxx::stream_to::complete() } -void pqxx::stream_to::escape_field_to_buffer(std::string_view data) +void pqxx::stream_to::escape_field_to_buffer(std::string_view data, sl loc) { std::size_t const end{std::size(data)}; std::size_t here{0}; while (here < end) { - auto const stop_char{m_finder(data, here)}; + auto const stop_char{m_finder(data, here, loc)}; // Append any unremarkable we just skipped over. m_buffer.append(std::data(data) + here, stop_char - here); if (stop_char < end) { m_buffer.push_back('\\'); - m_buffer.push_back(escape_char(data[stop_char])); + m_buffer.push_back(escape_char(data[stop_char], loc)); } here = stop_char + 1; } diff --git a/src/subtransaction.cxx b/src/subtransaction.cxx index 727167774..e335cc0e0 100644 --- a/src/subtransaction.cxx +++ b/src/subtransaction.cxx @@ -30,7 +30,7 @@ constexpr std::string_view class_name{"subtransaction"sv}; pqxx::subtransaction::subtransaction( - dbtransaction &t, std::string_view tname) : + dbtransaction &t, std::string_view tname, sl loc) : transaction_focus{t, class_name, t.conn().adorn_name(tname)}, // We can't initialise the rollback command here, because we don't yet // have a full object to implement quoted_name(). @@ -38,8 +38,10 @@ pqxx::subtransaction::subtransaction( { set_rollback_cmd(std::make_shared( internal::concat("ROLLBACK TO SAVEPOINT ", quoted_name()))); - direct_exec(std::make_shared( - internal::concat("SAVEPOINT ", quoted_name()))); + direct_exec( + std::make_shared( + internal::concat("SAVEPOINT ", quoted_name())), + loc); } @@ -50,19 +52,22 @@ using dbtransaction_ref = pqxx::dbtransaction &; pqxx::subtransaction::subtransaction( - subtransaction &t, std::string_view tname) : - subtransaction(dbtransaction_ref(t), tname) + subtransaction &t, std::string_view tname, sl loc) : + subtransaction(dbtransaction_ref(t), tname, loc) {} pqxx::subtransaction::~subtransaction() noexcept { - close(); + sl const loc{sl::current()}; + close(loc); } -void pqxx::subtransaction::do_commit() +void pqxx::subtransaction::do_commit(sl loc) { - direct_exec(std::make_shared( - internal::concat("RELEASE SAVEPOINT ", quoted_name()))); + direct_exec( + std::make_shared( + internal::concat("RELEASE SAVEPOINT ", quoted_name())), + loc); } diff --git a/src/time.cxx b/src/time.cxx index e216c1ade..f68ebd006 100644 --- a/src/time.cxx +++ b/src/time.cxx @@ -75,17 +75,17 @@ year_into_buf(char *begin, char *end, std::chrono::year const &value) /// Parse the numeric part of a year value. -inline int year_from_buf(std::string_view text) +inline int year_from_buf(std::string_view text, pqxx::sl loc) { if (std::size(text) < 4) throw pqxx::conversion_error{ - pqxx::internal::concat("Year field is too small: '", text, "'.")}; + pqxx::internal::concat("Year field is too small: '", text, "'."), loc}; // Parse as int, so we can accommodate 32768 BC which won't fit in a short // as-is, but equates to 32767 BCE which will. int const year{pqxx::string_traits::from_string(text)}; if (year <= 0) throw pqxx::conversion_error{ - pqxx::internal::concat("Bad year: '", text, "'.")}; + pqxx::internal::concat("Bad year: '", text, "'."), loc}; return year; } @@ -110,13 +110,14 @@ inline char *month_into_buf(char *begin, std::chrono::month const &value) /// Parse a 1-based month value. -inline std::chrono::month month_from_string(std::string_view text) +inline std::chrono::month +month_from_string(std::string_view text, pqxx::sl loc) { if ( not pqxx::internal::is_digit(text[0]) or not pqxx::internal::is_digit(text[1])) throw pqxx::conversion_error{ - pqxx::internal::concat("Invalid month: '", text, "'.")}; + pqxx::internal::concat("Invalid month: '", text, "'."), loc}; return std::chrono::month{unsigned( (ten * pqxx::internal::digit_to_number(text[0])) + pqxx::internal::digit_to_number(text[1]))}; @@ -134,19 +135,19 @@ inline char *day_into_buf(char *begin, std::chrono::day const &value) /// Parse a 1-based day-of-month value. -inline std::chrono::day day_from_string(std::string_view text) +inline std::chrono::day day_from_string(std::string_view text, pqxx::sl loc) { if ( not pqxx::internal::is_digit(text[0]) or not pqxx::internal::is_digit(text[1])) throw pqxx::conversion_error{ - pqxx::internal::concat("Bad day in date: '", text, "'.")}; + pqxx::internal::concat("Bad day in date: '", text, "'."), loc}; std::chrono::day const d{unsigned( (ten * pqxx::internal::digit_to_number(text[0])) + pqxx::internal::digit_to_number(text[1]))}; if (not d.ok()) throw pqxx::conversion_error{ - pqxx::internal::concat("Bad day in date: '", text, "'.")}; + pqxx::internal::concat("Bad day in date: '", text, "'."), loc}; return d; } @@ -177,10 +178,10 @@ std::string make_parse_error(std::string_view text) namespace pqxx { char *string_traits::into_buf( - char *begin, char *end, std::chrono::year_month_day const &value) + char *begin, char *end, std::chrono::year_month_day const &value, sl loc) { if (std::size_t(end - begin) < size_buffer(value)) - throw conversion_overrun{"Not enough room in buffer for date."}; + throw conversion_overrun{"Not enough room in buffer for date.", loc}; begin = year_into_buf(begin, end, value.year()); *begin++ = '-'; begin = month_into_buf(begin, value.month()); @@ -194,30 +195,31 @@ char *string_traits::into_buf( std::chrono::year_month_day -string_traits::from_string(std::string_view text) +string_traits::from_string( + std::string_view text, sl loc) { // We can't just re-use the std::chrono::year conversions, because the "BC" // suffix comes at the very end. if (std::size(text) < 9) - throw conversion_error{make_parse_error(text)}; + throw conversion_error{make_parse_error(text), loc}; bool const is_bc{text.ends_with(s_bc)}; if (is_bc) [[unlikely]] text = text.substr(0, std::size(text) - std::size(s_bc)); auto const ymsep{find_year_month_separator(text)}; if ((std::size(text) - ymsep) != 6) - throw conversion_error{make_parse_error(text)}; + throw conversion_error{make_parse_error(text), loc}; auto const base_year{ - year_from_buf(std::string_view{std::data(text), ymsep})}; + year_from_buf(std::string_view{std::data(text), ymsep}, loc)}; if (base_year == 0) - throw conversion_error{"Year zero conversion."}; + throw conversion_error{"Year zero conversion.", loc}; std::chrono::year const y{is_bc ? (-base_year + 1) : base_year}; - auto const m{month_from_string(text.substr(ymsep + 1, 2))}; + auto const m{month_from_string(text.substr(ymsep + 1, 2), loc)}; if (text[ymsep + 3] != '-') - throw conversion_error{make_parse_error(text)}; - auto const d{day_from_string(text.substr(ymsep + 4, 2))}; + throw conversion_error{make_parse_error(text), loc}; + auto const d{day_from_string(text.substr(ymsep + 4, 2), loc)}; std::chrono::year_month_day const date{y, m, d}; if (not date.ok()) - throw conversion_error{make_parse_error(text)}; + throw conversion_error{make_parse_error(text), loc}; return date; } } // namespace pqxx diff --git a/src/transaction.cxx b/src/transaction.cxx index a20852afd..b5701e2b5 100644 --- a/src/transaction.cxx +++ b/src/transaction.cxx @@ -22,29 +22,29 @@ pqxx::internal::basic_transaction::basic_transaction( - connection &cx, zview begin_command, std::string_view tname) : + connection &cx, zview begin_command, std::string_view tname, sl loc) : dbtransaction(cx, tname) { register_transaction(); - direct_exec(begin_command); + direct_exec(begin_command, loc); } pqxx::internal::basic_transaction::basic_transaction( - connection &cx, zview begin_command, std::string &&tname) : + connection &cx, zview begin_command, std::string &&tname, sl loc) : dbtransaction(cx, std::move(tname)) { register_transaction(); - direct_exec(begin_command); + direct_exec(begin_command, loc); } pqxx::internal::basic_transaction::basic_transaction( - connection &cx, zview begin_command) : + connection &cx, zview begin_command, sl loc) : dbtransaction(cx) { register_transaction(); - direct_exec(begin_command); + direct_exec(begin_command, loc); } @@ -57,12 +57,12 @@ pqxx::internal::basic_transaction::basic_transaction( pqxx::internal::basic_transaction::~basic_transaction() noexcept = default; -void pqxx::internal::basic_transaction::do_commit() +void pqxx::internal::basic_transaction::do_commit(sl loc) { static auto const commit_q{std::make_shared("COMMIT"sv)}; try { - direct_exec(commit_q); + direct_exec(commit_q, loc); } catch (statement_completion_unknown const &e) { @@ -70,6 +70,7 @@ void pqxx::internal::basic_transaction::do_commit() // resulting state of the database. process_notice(internal::concat(e.what(), "\n")); + // XXX: Log source location? std::string msg{internal::concat( "WARNING: Commit status of transaction '", name(), "' is unknown. " @@ -95,7 +96,7 @@ void pqxx::internal::basic_transaction::do_commit() process_notice(msg); // Strip newline. It was only needed for process_notice(). msg.pop_back(); - throw in_doubt_error{msg}; + throw in_doubt_error{msg, loc}; } else { diff --git a/src/transaction_base.cxx b/src/transaction_base.cxx index c82c8162a..e54563910 100644 --- a/src/transaction_base.cxx +++ b/src/transaction_base.cxx @@ -95,7 +95,7 @@ void pqxx::transaction_base::register_transaction() } -void pqxx::transaction_base::commit() +void pqxx::transaction_base::commit(sl loc) { check_pending_error(); @@ -107,8 +107,9 @@ void pqxx::transaction_base::commit() break; case status::aborted: - throw usage_error{internal::concat( - "Attempt to commit previously aborted ", description())}; + throw usage_error{ + internal::concat("Attempt to commit previously aborted ", description()), + loc}; case status::committed: // Transaction has been committed already. This is not exactly proper @@ -134,9 +135,11 @@ void pqxx::transaction_base::commit() // commit is premature. Punish this swiftly and without fail to discourage // the habit from forming. if (m_focus != nullptr) - throw failure{internal::concat( - "Attempt to commit ", description(), " with ", m_focus->description(), - " still open.")}; + throw failure{ + internal::concat( + "Attempt to commit ", description(), " with ", m_focus->description(), + " still open."), + loc}; // Check that we're still connected (as far as we know--this is not an // absolute thing!) before trying to commit. If the connection was broken @@ -144,11 +147,11 @@ void pqxx::transaction_base::commit() // remain in-doubt as to whether the backend got the commit order at all. if (not m_conn.is_open()) throw broken_connection{ - "Broken connection to backend; cannot complete transaction."}; + "Broken connection to backend; cannot complete transaction.", loc}; try { - do_commit(); + do_commit(loc); m_status = status::committed; } catch (in_doubt_error const &) @@ -162,18 +165,18 @@ void pqxx::transaction_base::commit() throw; } - close(); + close(loc); } -void pqxx::transaction_base::do_abort() +void pqxx::transaction_base::do_abort(sl loc) { if (m_rollback_cmd) - direct_exec(m_rollback_cmd); + direct_exec(m_rollback_cmd, loc); } -void pqxx::transaction_base::abort() +void pqxx::transaction_base::abort(sl loc) { // Check previous status code. Quietly accept multiple aborts to // simplify emergency bailout code. @@ -182,7 +185,7 @@ void pqxx::transaction_base::abort() case status::active: try { - do_abort(); + do_abort(loc); } catch (std::exception const &e) { @@ -193,8 +196,10 @@ void pqxx::transaction_base::abort() case status::aborted: return; case status::committed: - throw usage_error{internal::concat( - "Attempt to abort previously committed ", description())}; + throw usage_error{ + internal::concat( + "Attempt to abort previously committed ", description()), + loc}; case status::in_doubt: // Aborting an in-doubt transaction is probably a reasonably sane response @@ -209,7 +214,7 @@ void pqxx::transaction_base::abort() } m_status = status::aborted; - close(); + close(loc); } @@ -238,7 +243,7 @@ class PQXX_PRIVATE command : pqxx::transaction_focus }; } // namespace -pqxx::result pqxx::transaction_base::exec(std::string_view query) +pqxx::result pqxx::transaction_base::exec(std::string_view query, sl loc) { check_pending_error(); @@ -253,17 +258,17 @@ pqxx::result pqxx::transaction_base::exec(std::string_view query) case status::in_doubt: // TODO: Pass query. throw usage_error{ - "Could not execute command: transaction is already closed."}; + "Could not execute command: transaction is already closed.", loc}; default: PQXX_UNREACHABLE; } - return direct_exec(query); + return direct_exec(query, loc); } -pqxx::result -pqxx::transaction_base::exec(std::string_view query, std::string_view desc) +pqxx::result pqxx::transaction_base::exec( + std::string_view query, std::string_view desc, sl loc) { check_pending_error(); @@ -279,14 +284,16 @@ pqxx::transaction_base::exec(std::string_view query, std::string_view desc) std::string const n{ std::empty(desc) ? "" : internal::concat("'", desc, "' ")}; - throw usage_error{internal::concat( - "Could not execute command ", n, ": transaction is already closed.")}; + throw usage_error{ + internal::concat( + "Could not execute command ", n, ": transaction is already closed."), + loc}; } default: PQXX_UNREACHABLE; } - return direct_exec(query, desc); + return direct_exec(query, desc, loc); } @@ -302,30 +309,30 @@ pqxx::result pqxx::transaction_base::exec_n( pqxx::result pqxx::transaction_base::internal_exec_prepared( - std::string_view statement, internal::c_params const &args) + std::string_view statement, internal::c_params const &args, sl loc) { command const cmd{*this, statement}; return pqxx::internal::gate::connection_transaction{conn()}.exec_prepared( - statement, args); + statement, args, loc); } pqxx::result pqxx::transaction_base::internal_exec_params( - std::string_view query, internal::c_params const &args) + std::string_view query, internal::c_params const &args, sl loc) { command const cmd{*this, query}; return pqxx::internal::gate::connection_transaction{conn()}.exec_params( - query, args); + query, args, loc); } void pqxx::transaction_base::notify( - std::string_view channel, std::string_view payload) + std::string_view channel, std::string_view payload, sl loc) { // For some reason, NOTIFY does not work as a parameterised statement, // even just for the payload (which is supposed to be a normal string). // Luckily, pg_notify() does. - exec("SELECT pg_notify($1, $2)", params{channel, payload}).one_row(); + exec("SELECT pg_notify($1, $2)", params{channel, payload}, loc).one_row(loc); } @@ -346,7 +353,7 @@ std::string pqxx::transaction_base::get_variable(std::string_view var) } -void pqxx::transaction_base::close() noexcept +void pqxx::transaction_base::close(sl loc) noexcept { try { @@ -377,7 +384,7 @@ void pqxx::transaction_base::close() noexcept try { - abort(); + abort(loc); } catch (std::exception const &e) { @@ -443,19 +450,20 @@ void pqxx::transaction_base::unregister_focus( pqxx::result pqxx::transaction_base::direct_exec( - std::string_view cmd, std::string_view desc) + std::string_view cmd, std::string_view desc, sl loc) { check_pending_error(); - return pqxx::internal::gate::connection_transaction{conn()}.exec(cmd, desc); + return pqxx::internal::gate::connection_transaction{conn()}.exec( + cmd, desc, loc); } pqxx::result pqxx::transaction_base::direct_exec( - std::shared_ptr cmd, std::string_view desc) + std::shared_ptr cmd, std::string_view desc, sl loc) { check_pending_error(); return pqxx::internal::gate::connection_transaction{conn()}.exec( - std::move(cmd), desc); + std::move(cmd), desc, loc); } @@ -471,6 +479,7 @@ void pqxx::transaction_base::register_pending_error(zview err) noexcept { try { + // XXX: Log source location? [[unlikely]] process_notice("UNABLE TO PROCESS ERROR\n"); // TODO: Make at least an attempt to append a newline. process_notice(e.what()); @@ -513,6 +522,7 @@ void pqxx::transaction_base::check_pending_error() { if (not std::empty(m_pending_error)) { + // TODO: Store exceptions, or message + source_location. std::string err; err.swap(m_pending_error); throw failure{err}; diff --git a/src/util.cxx b/src/util.cxx index 57283c52f..75db2593f 100644 --- a/src/util.cxx +++ b/src/util.cxx @@ -110,7 +110,6 @@ constexpr std::array hex_digits{ constexpr char hex_digit(int c) noexcept { PQXX_ASSUME(c >= 0); - PQXX_ASSUME(c < std::ssize(hex_digits)); return hex_digits.at(static_cast(c)); } @@ -166,39 +165,40 @@ std::string pqxx::internal::esc_bin(bytes_view binary_data) void pqxx::internal::unesc_bin( - std::string_view escaped_data, std::byte buffer[]) + std::string_view escaped_data, std::byte buffer[], sl loc) { auto const in_size{std::size(escaped_data)}; if (in_size < 2) - throw pqxx::failure{"Binary data appears truncated."}; + throw pqxx::failure{"Binary data appears truncated.", loc}; if ((in_size % 2) != 0) - throw pqxx::failure{"Invalid escaped binary length."}; + throw pqxx::failure{"Invalid escaped binary length.", loc}; char const *in{escaped_data.data()}; char const *const end{in + in_size}; if (*in++ != '\\' or *in++ != 'x') throw pqxx::failure( "Escaped binary data did not start with '\\x'`. Is the server or libpq " - "too old?"); + "too old?", + loc); auto out{buffer}; while (in != end) { int const hi{nibble(*in++)}; if (hi < 0) - throw pqxx::failure{"Invalid hex-escaped data."}; + throw pqxx::failure{"Invalid hex-escaped data.", loc}; int const lo{nibble(*in++)}; if (lo < 0) - throw pqxx::failure{"Invalid hex-escaped data."}; + throw pqxx::failure{"Invalid hex-escaped data.", loc}; *out++ = static_cast((hi << 4) | lo); } } -pqxx::bytes pqxx::internal::unesc_bin(std::string_view escaped_data) +pqxx::bytes pqxx::internal::unesc_bin(std::string_view escaped_data, sl loc) { auto const bytes{size_unesc_bin(std::size(escaped_data))}; pqxx::bytes buf; buf.resize(bytes); - unesc_bin(escaped_data, buf.data()); + unesc_bin(escaped_data, buf.data(), loc); return buf; } diff --git a/src/wait.cxx b/src/wait.cxx index 6c087a9c1..d8acdff29 100644 --- a/src/wait.cxx +++ b/src/wait.cxx @@ -60,11 +60,12 @@ namespace { -template T to_milli(unsigned seconds, unsigned microseconds) +template +T to_milli(unsigned seconds, unsigned microseconds, pqxx::sl loc) { return pqxx::check_cast( (seconds * 1000) + (microseconds / 1000), - "Wait timeout value out of bounds."); + "Wait timeout value out of bounds.", loc); } @@ -90,7 +91,7 @@ template T to_milli(unsigned seconds, unsigned microseconds) void pqxx::internal::wait_fd( int fd, bool for_read, bool for_write, unsigned seconds, - unsigned microseconds) + unsigned microseconds, sl loc) { // WSAPoll is available in winsock2.h only for versions of Windows >= 0x0600 #if defined(_WIN32) && (_WIN32_WINNT >= 0x0600) @@ -99,13 +100,13 @@ void pqxx::internal::wait_fd( (for_read ? POLLRDNORM : 0) | (for_write ? POLLWRNORM : 0))}; WSAPOLLFD fdarray{SOCKET(fd), events, 0}; int const code{ - WSAPoll(&fdarray, 1u, to_milli(seconds, microseconds))}; + WSAPoll(&fdarray, 1u, to_milli(seconds, microseconds, loc))}; #elif defined(PQXX_HAVE_POLL) auto const events{static_cast( POLLERR | POLLHUP | POLLNVAL | (for_read ? POLLIN : 0) | (for_write ? POLLOUT : 0))}; pollfd pfd{fd, events, 0}; - int const code{poll(&pfd, 1, to_milli(seconds, microseconds))}; + int const code{poll(&pfd, 1, to_milli(seconds, microseconds, loc))}; #else // No poll()? Our last option is select(). fd_set read_fds; diff --git a/test/runner.cxx b/test/runner.cxx index e66609755..5f8aeef3c 100644 --- a/test/runner.cxx +++ b/test/runner.cxx @@ -14,7 +14,7 @@ namespace pqxx::test { -test_failure::test_failure(std::string const &desc, std::source_location loc) : +test_failure::test_failure(std::string const &desc, sl loc) : std::logic_error{desc}, m_loc{loc} {} @@ -22,21 +22,19 @@ test_failure::~test_failure() noexcept = default; /// Drop table, if it exists. -inline void drop_table(transaction_base &t, std::string const &table) +inline void drop_table(transaction_base &t, std::string const &table, sl loc) { - t.exec("DROP TABLE IF EXISTS " + table); + t.exec("DROP TABLE IF EXISTS " + table, loc); } -[[noreturn]] void check_notreached(std::string desc, std::source_location loc) +[[noreturn]] void check_notreached(std::string desc, sl loc) { throw test_failure{desc, loc}; } -void check( - bool condition, char const text[], std::string const &desc, - std::source_location loc) +void check(bool condition, char const text[], std::string const &desc, sl loc) { if (not condition) throw test_failure{desc + " (failed expression: " + text + ")", loc}; diff --git a/test/test07.cxx b/test/test07.cxx index cfb1b58cf..eead0b966 100644 --- a/test/test07.cxx +++ b/test/test07.cxx @@ -30,7 +30,7 @@ int To4Digits(int Y) else if (Y < 100) Result += 1900; else if (Y < 1970) - PQXX_CHECK_NOTREACHED("Unexpected year: " + to_string(Y)); + pqxx::test::check_notreached("Unexpected year: " + to_string(Y)); return Result; } @@ -81,9 +81,11 @@ void test_007() { int Y{0}; +#include "pqxx/internal/ignore-deprecated-pre.hxx" // Read year, and if it is non-null, note its converted value if (r[0] >> Y) conversions[Y] = To4Digits(Y); +#include "pqxx/internal/ignore-deprecated-post.hxx" // See if type identifiers are consistent oid const tctype{r.column_type(0)}; diff --git a/test/test_array.cxx b/test/test_array.cxx index e7da2ef6c..1ab2124ca 100644 --- a/test/test_array.cxx +++ b/test/test_array.cxx @@ -15,6 +15,7 @@ struct nullness : no_null inline std::string to_string(pqxx::array_parser::juncture const &j) { +#include "pqxx/internal/ignore-deprecated-pre.hxx" using junc = pqxx::array_parser::juncture; switch (j) { @@ -25,6 +26,7 @@ inline std::string to_string(pqxx::array_parser::juncture const &j) case junc::done: return "done"; default: return "UNKNOWN JUNCTURE: " + to_string(static_cast(j)); } +#include "pqxx/internal/ignore-deprecated-post.hxx" } } // namespace pqxx @@ -33,6 +35,7 @@ namespace { void test_empty_arrays() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; // Parsing a null pointer just immediately returns "done". @@ -68,11 +71,13 @@ void test_empty_arrays() output.first, pqxx::array_parser::juncture::done, "Empty array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_null_value() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser containing_null("{NULL}"); @@ -99,11 +104,13 @@ void test_array_null_value() output.first, pqxx::array_parser::juncture::done, "Array containing null did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_double_quoted_string() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser("{\"item\"}"); @@ -129,11 +136,13 @@ void test_array_double_quoted_string() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_double_quoted_escaping() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser(R"--({"don''t\\ care"})--"); @@ -159,12 +168,14 @@ void test_array_double_quoted_escaping() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } // A pair of double quotes in a double-quoted string is an escaped quote. void test_array_double_double_quoted_string() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser{R"--({"3"" steel"})--"}; @@ -179,11 +190,13 @@ void test_array_double_double_quoted_string() "Array did not return string_value."); PQXX_CHECK_EQUAL(output.second, "3\" steel", "Unexpected string value."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_unquoted_string() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser("{item}"); @@ -209,11 +222,13 @@ void test_array_unquoted_string() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_multiple_values() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser("{1,2}"); @@ -245,11 +260,13 @@ void test_array_multiple_values() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_nested_array() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser("{{item}}"); @@ -286,11 +303,13 @@ void test_nested_array() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_nested_array_with_multiple_entries() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::pair output; pqxx::array_parser parser("{{1,2},{3,4}}"); @@ -356,6 +375,7 @@ void test_nested_array_with_multiple_entries() output.first, pqxx::array_parser::juncture::done, "Array did not conclude with done."); PQXX_CHECK_EQUAL(output.second, "", "Unexpected nonempty output."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } @@ -493,6 +513,7 @@ void test_array_generate() void test_array_roundtrip() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" pqxx::connection cx; pqxx::work tx{cx}; @@ -526,11 +547,13 @@ void test_array_roundtrip() PQXX_CHECK_EQUAL( item.first, pqxx::array_parser::juncture::done, "Array did not end in done."); +#include "pqxx/internal/ignore-deprecated-post.hxx" } void test_array_strings() { +#include "pqxx/internal/ignore-deprecated-pre.hxx" std::vector inputs{ "", "null", "NULL", "\\N", "'", "''", "\\", "\n\t", "\\n", "\"", "\"\"", "a b", "a<>b", "{", "}", "{}", @@ -552,6 +575,7 @@ void test_array_strings() "Bad value juncture."); PQXX_CHECK_EQUAL(value, input, "Bad array value roundtrip."); } +#include "pqxx/internal/ignore-deprecated-post.hxx" } diff --git a/test/test_encodings.cxx b/test/test_encodings.cxx index 7965357a5..bb5a32485 100644 --- a/test/test_encodings.cxx +++ b/test/test_encodings.cxx @@ -8,30 +8,30 @@ namespace void test_scan_ascii() { auto const scan{pqxx::internal::get_glyph_scanner( - pqxx::internal::encoding_group::MONOBYTE)}; + pqxx::internal::encoding_group::MONOBYTE, pqxx::sl::current())}; std::string const text{"hello"}; PQXX_CHECK_EQUAL( - scan(text.c_str(), std::size(text), 0), 1ul, + scan(text.c_str(), std::size(text), 0, pqxx::sl::current()), 1ul, "Monobyte scanner acting up."); PQXX_CHECK_EQUAL( - scan(text.c_str(), std::size(text), 1), 2ul, + scan(text.c_str(), std::size(text), 1, pqxx::sl::current()), 2ul, "Monobyte scanner is inconsistent."); } void test_scan_utf8() { - auto const scan{ - pqxx::internal::get_glyph_scanner(pqxx::internal::encoding_group::UTF8)}; + auto const scan{pqxx::internal::get_glyph_scanner( + pqxx::internal::encoding_group::UTF8, pqxx::sl::current())}; // Thai: "Khrab". std::string const text{"\xe0\xb8\x95\xe0\xb8\xa3\xe0\xb8\xb1\xe0\xb8\x9a"}; PQXX_CHECK_EQUAL( - scan(text.c_str(), std::size(text), 0), 3ul, + scan(text.c_str(), std::size(text), 0, pqxx::sl::current()), 3ul, "UTF-8 scanner mis-scanned Thai khor khwai."); PQXX_CHECK_EQUAL( - scan(text.c_str(), std::size(text), 3), 6ul, + scan(text.c_str(), std::size(text), 3, pqxx::sl::current()), 6ul, "UTF-8 scanner mis-scanned Thai ror reua."); } @@ -41,7 +41,8 @@ void test_for_glyphs_empty() bool iterated{false}; pqxx::internal::for_glyphs( pqxx::internal::encoding_group::MONOBYTE, - [&iterated](char const *, char const *) { iterated = true; }, "", 0); + [&iterated](char const *, char const *) { iterated = true; }, "", 0, 0, + pqxx::sl::current()); PQXX_CHECK(!iterated, "Empty string went through an iteration."); } @@ -56,7 +57,7 @@ void test_for_glyphs_ascii() [&points](char const *gbegin, char const *gend) { points.push_back(gend - gbegin); }, - text.c_str(), std::size(text)); + text.c_str(), std::size(text), 0, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(points), 2u, "Wrong number of ASCII iterations."); PQXX_CHECK_EQUAL(points[0], 1u, "ASCII iteration started off wrong."); @@ -75,7 +76,7 @@ void test_for_glyphs_utf8() [&points](char const *gbegin, char const *gend) { points.push_back(gend - gbegin); }, - text.c_str(), std::size(text)); + text.c_str(), std::size(text), 0, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(points), 2u, "Wrong number of UTF-8 iterations."); PQXX_CHECK_EQUAL(points[0], 2u, "UTF-8 iteration started off wrong."); @@ -90,7 +91,7 @@ void test_for_glyphs_utf8() [&points](char const *gbegin, char const *gend) { points.push_back(gend - gbegin); }, - mix.c_str(), std::size(mix)); + mix.c_str(), std::size(mix), 0, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(points), 3u, "Mixed UTF-8 iteration is broken."); PQXX_CHECK_EQUAL(points[0], 2u, "Mixed UTF-8 iteration started off wrong."); diff --git a/test/test_helpers.hxx b/test/test_helpers.hxx index 6c1e59de3..268aff561 100644 --- a/test/test_helpers.hxx +++ b/test/test_helpers.hxx @@ -11,9 +11,7 @@ namespace test class test_failure : public std::logic_error { public: - test_failure( - std::string const &desc, - std::source_location loc = std::source_location::current()); + test_failure(std::string const &desc, sl loc = sl::current()); ~test_failure() noexcept override; @@ -21,12 +19,13 @@ public: constexpr auto line() const noexcept { return m_loc.line(); } private: - std::source_location m_loc; + sl m_loc; }; /// Drop a table, if it exists. -void drop_table(transaction_base &, std::string const &table); +void drop_table( + transaction_base &, std::string const &table, sl loc = sl::current()); using testfunc = void (*)(); @@ -54,17 +53,16 @@ struct registrar // Unconditional test failure. -#define PQXX_CHECK_NOTREACHED(desc) pqxx::test::check_notreached((desc)) [[noreturn]] void check_notreached( - std::string desc, - std::source_location loc = std::source_location::current()); + std::string desc = "Execution was never supposed to reach this point.", + sl loc = sl::current()); // Verify that a condition is met, similar to assert() #define PQXX_CHECK(condition, desc) \ pqxx::test::check((condition), #condition, (desc)) void check( bool condition, char const text[], std::string const &desc, - std::source_location loc = std::source_location::current()); + sl loc = sl::current()); // Verify that variable has the expected value. #define PQXX_CHECK_EQUAL(actual, expected, desc) \ @@ -72,8 +70,7 @@ void check( template inline void check_equal( ACTUAL actual, char const actual_text[], EXPECTED expected, - char const expected_text[], std::string const &desc, - std::source_location loc = std::source_location::current()) + char const expected_text[], std::string const &desc, sl loc = sl::current()) { if (expected == actual) return; @@ -94,8 +91,7 @@ inline void check_equal( template inline void check_not_equal( VALUE1 value1, char const text1[], VALUE2 value2, char const text2[], - std::string const &desc, - std::source_location loc = std::source_location::current()) + std::string const &desc, sl loc = sl::current()) { if (value1 != value2) return; @@ -116,8 +112,7 @@ inline void check_not_equal( template inline void check_less( VALUE1 value1, char const text1[], VALUE2 value2, char const text2[], - std::string const &desc, - std::source_location loc = std::source_location::current()) + std::string const &desc, sl loc = sl::current()) { if (value1 < value2) return; @@ -141,22 +136,18 @@ inline void check_less( template inline void check_less_equal( VALUE1 value1, char const text1[], VALUE2 value2, char const text2[], - std::string const &desc, - std::source_location loc = std::source_location::current()) + std::string const &desc, sl loc = sl::current()) { if (value1 <= value2) return; - std::string const fulldesc = desc + " (" + text1 + " > " + text2 + - ": " - "\"lower\"=" + - to_string(value1) + - ", " - "\"upper\"=" + - to_string(value2) + ")"; + std::string const fulldesc = pqxx::internal::concat( + desc, " (", text1, " > ", text2, ": \"lower\"=", value1, + ", \"upper\"=", value2, ")"); throw test_failure{fulldesc, loc}; } +/// A special exception type not derived from `std::exception`. struct failure_to_fail {}; @@ -170,80 +161,103 @@ inline void end_of_statement() {} // Verify that "action" does not throw an exception. #define PQXX_CHECK_SUCCEEDS(action, desc) \ - { \ - try \ - { \ - action; \ - } \ - catch (std::exception const &e) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " - \"" + \ - #action "\" threw exception: " + e.what()); \ - } \ - catch (...) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " - \"" + #action "\" threw a non-exception!"); \ - } \ - } \ - pqxx::test::internal::end_of_statement() + pqxx::test::check_succeeds(([&]() { action; }), #action, (desc)) + +template +inline void check_succeeds( + F &&f, char const text[], std::string desc = "Expected this to succeed.", + sl loc = sl::current()) +{ + try + { + f(); + } + catch (std::exception const &e) + { + pqxx::test::check_notreached( + pqxx::internal::concat( + desc, " - \"", text, "\" threw exception: ", e.what()), + loc); + } + catch (...) + { + pqxx::test::check_notreached( + pqxx::internal::concat(desc, " - \"", text, "\" threw a non-exception!"), + loc); + } +} + + +template +inline void check_throws( + F &&f, char const text[], + std::string desc = "This code did not thow the expected exception.", + pqxx::sl loc = sl::current()) +{ + try + { + f(); + throw failure_to_fail{}; + } + catch (failure_to_fail const &) + { + check_notreached( + pqxx::internal::concat(desc, " (\"", text, "\" did not throw)"), loc); + } + catch (EXC const &) + {} + catch (std::exception const &e) + { + check_notreached(pqxx::internal::concat( + desc, " (\"", text, "\" threw the wrong exception type: ", e.what(), + ")")); + } + catch (...) + { + check_notreached( + pqxx::internal::concat( + desc, " (\"", text, "\" threw a non-exception type!)"), + loc); + } +} + + +template +inline void check_throws_exception( + F &&f, char const text[], + std::string desc = "This code did not thow a std::exception.", + pqxx::sl loc = sl::current()) +{ + try + { + f(); + throw failure_to_fail{}; + } + catch (failure_to_fail const &) + { + check_notreached( + pqxx::internal::concat(desc, " (\"", text, "\" did not throw)"), loc); + } + catch (std::exception const &) + {} + catch (...) + { + check_notreached( + pqxx::internal::concat( + desc, " (\"", text, "\" threw a non-exception type!)"), + loc); + } +} + // Verify that "action" throws an exception, of any std::exception-based type. #define PQXX_CHECK_THROWS_EXCEPTION(action, desc) \ - { \ - try \ - { \ - action; \ - throw pqxx::test::failure_to_fail(); \ - } \ - catch (pqxx::test::failure_to_fail const &) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " (\"" #action "\" did not throw)"); \ - } \ - catch (std::exception const &) \ - {} \ - catch (...) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " (\"" #action "\" threw non-exception type)"); \ - } \ - } \ - pqxx::test::internal::end_of_statement() + pqxx::test::check_throws_exception(([&]() { action; }), #action, desc) // Verify that "action" throws "exception_type" (which is not std::exception). #define PQXX_CHECK_THROWS(action, exception_type, desc) \ - { \ - try \ - { \ - action; \ - throw pqxx::test::failure_to_fail(); \ - } \ - catch (pqxx::test::failure_to_fail const &) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " (\"" #action \ - "\" did not throw " #exception_type ")"); \ - } \ - catch (exception_type const &) \ - {} \ - catch (std::exception const &e) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + \ - " (\"" #action \ - "\" " \ - "threw exception other than " #exception_type ": " + \ - e.what() + ")"); \ - } \ - catch (...) \ - { \ - PQXX_CHECK_NOTREACHED( \ - std::string{desc} + " (\"" #action "\" threw non-exception type)"); \ - } \ - } \ - pqxx::test::internal::end_of_statement() + pqxx::test::check_throws(([&] { action; }), #action, desc) + #define PQXX_CHECK_BOUNDS(value, lower, upper, desc) \ pqxx::test::check_bounds( \ @@ -252,7 +266,7 @@ template inline void check_bounds( VALUE value, char const text[], LOWER lower, char const lower_text[], UPPER upper, char const upper_text[], std::string const &desc, - std::source_location loc = std::source_location::current()) + sl loc = sl::current()) { std::string const range_check = std::string{lower_text} + " < " + upper_text, lower_check = diff --git a/test/test_sql_cursor.cxx b/test/test_sql_cursor.cxx index bc37b4030..0e1d6e73f 100644 --- a/test/test_sql_cursor.cxx +++ b/test/test_sql_cursor.cxx @@ -23,45 +23,45 @@ void test_forward_sql_cursor() PQXX_CHECK_EQUAL(std::size(empty_result), 0, "Empty result not empty"); auto displacement{0}; - auto one{forward.fetch(1, displacement)}; + auto one{forward.fetch(1, displacement, pqxx::sl::current())}; PQXX_CHECK_EQUAL(std::size(one), 1, "Fetched wrong number of rows"); PQXX_CHECK_EQUAL(one[0][0].as(), "1", "Unexpected result"); PQXX_CHECK_EQUAL(displacement, 1, "Wrong displacement"); PQXX_CHECK_EQUAL(forward.pos(), 1, "In wrong position"); - auto offset{forward.move(1, displacement)}; + auto offset{forward.move(1, displacement, pqxx::sl::current())}; PQXX_CHECK_EQUAL(offset, 1, "Unexpected offset from move()"); PQXX_CHECK_EQUAL(displacement, 1, "Unexpected displacement after move()"); PQXX_CHECK_EQUAL(forward.pos(), 2, "Wrong position after move()"); PQXX_CHECK_EQUAL(forward.endpos(), -1, "endpos() unexpectedly set"); - auto row{forward.fetch(0, displacement)}; + auto row{forward.fetch(0, displacement, pqxx::sl::current())}; PQXX_CHECK_EQUAL(std::size(row), 0, "fetch(0, displacement) returns rows"); PQXX_CHECK_EQUAL(displacement, 0, "Unexpected displacement after fetch(0)"); PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0, displacement) affected pos()"); - row = forward.fetch(0); + row = forward.fetch(0, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(row), 0, "fetch(0) fetched wrong number of rows"); PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0) moved cursor"); PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0) affected pos()"); - offset = forward.move(1); + offset = forward.move(1, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 1, "move(1) returned unexpected value"); PQXX_CHECK_EQUAL(forward.pos(), 3, "move(1) after fetch(0) broke"); - row = forward.fetch(1); + row = forward.fetch(1, pqxx::sl::current()); PQXX_CHECK_EQUAL( std::size(row), 1, "fetch(1) returned wrong number of rows"); PQXX_CHECK_EQUAL(forward.pos(), 4, "fetch(1) results in bad pos()"); PQXX_CHECK_EQUAL(row[0][0].as(), "4", "pos() is lying"); - empty_result = forward.fetch(1, displacement); + empty_result = forward.fetch(1, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(empty_result), 0, "Got rows at end of cursor"); PQXX_CHECK_EQUAL(forward.pos(), 5, "Not at one-past-end position"); PQXX_CHECK_EQUAL(forward.endpos(), 5, "Failed to notice end position"); PQXX_CHECK_EQUAL(displacement, 1, "Wrong displacement at end position"); - offset = forward.move(5, displacement); + offset = forward.move(5, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 0, "move() lied at end of result set"); PQXX_CHECK_EQUAL(forward.pos(), 5, "pos() is beyond end"); PQXX_CHECK_EQUAL(forward.endpos(), 5, "endpos() changed after end position"); @@ -74,7 +74,8 @@ void test_forward_sql_cursor() pqxx::cursor_base::owned, false); // Move through entire result set at once. - offset = forward2.move(pqxx::cursor_base::all(), displacement); + offset = + forward2.move(pqxx::cursor_base::all(), displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 4, "Unexpected number of rows in result set"); PQXX_CHECK_EQUAL(displacement, 5, "displacement != rows+1"); PQXX_CHECK_EQUAL(forward2.pos(), 5, "Bad pos() after skipping all rows"); @@ -86,7 +87,8 @@ void test_forward_sql_cursor() pqxx::cursor_base::owned, false); // Fetch entire result set at once. - auto rows{forward3.fetch(pqxx::cursor_base::all(), displacement)}; + auto rows{forward3.fetch( + pqxx::cursor_base::all(), displacement, pqxx::sl::current())}; PQXX_CHECK_EQUAL( std::size(rows), 4, "Unexpected number of rows in result set"); PQXX_CHECK_EQUAL(displacement, 5, "displacement != rows+1"); @@ -98,7 +100,7 @@ void test_forward_sql_cursor() pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only, pqxx::cursor_base::owned, false); - offset = forward_empty.move(3, displacement); + offset = forward_empty.move(3, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(forward_empty.pos(), 1, "Bad pos() at end of result"); PQXX_CHECK_EQUAL(forward_empty.endpos(), 1, "Bad endpos() in empty result"); PQXX_CHECK_EQUAL(displacement, 1, "Bad displacement in empty result"); @@ -117,7 +119,7 @@ void test_scroll_sql_cursor() PQXX_CHECK_EQUAL(scroll.pos(), 0, "Scroll cursor's initial pos() is wrong"); PQXX_CHECK_EQUAL(scroll.endpos(), -1, "New scroll cursor has endpos() set"); - auto rows{scroll.fetch(pqxx::cursor_base::next())}; + auto rows{scroll.fetch(pqxx::cursor_base::next(), pqxx::sl::current())}; PQXX_CHECK_EQUAL(std::size(rows), 1, "Scroll cursor is broken"); PQXX_CHECK_EQUAL(scroll.pos(), 1, "Scroll cursor's pos() is broken"); PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set prematurely"); @@ -125,7 +127,7 @@ void test_scroll_sql_cursor() // Turn cursor around. This is where we begin to feel SQL cursors' // semantics: we pre-decrement, ending up on the position in front of the // first row and returning no rows. - rows = scroll.fetch(pqxx::cursor_base::prior()); + rows = scroll.fetch(pqxx::cursor_base::prior(), pqxx::sl::current()); PQXX_CHECK_EQUAL(std::empty(rows), true, "Turning around on fetch() broke"); PQXX_CHECK_EQUAL(scroll.pos(), 0, "pos() is not back at zero"); PQXX_CHECK_EQUAL( @@ -134,7 +136,7 @@ void test_scroll_sql_cursor() // Bounce off the left-hand side of the result set. Can't move before the // starting position. auto offset{0}, displacement{0}; - offset = scroll.move(-3, displacement); + offset = scroll.move(-3, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 0, "Rows found before beginning"); PQXX_CHECK_EQUAL(displacement, 0, "Failed to bounce off beginning"); PQXX_CHECK_EQUAL(scroll.pos(), 0, "pos() moved back from zero"); @@ -142,27 +144,27 @@ void test_scroll_sql_cursor() // Try bouncing off the left-hand side a little harder. Take 4 paces away // from the boundary and run into it. - offset = scroll.move(4, displacement); + offset = scroll.move(4, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 4, "Offset mismatch"); PQXX_CHECK_EQUAL(displacement, 4, "Displacement mismatch"); PQXX_CHECK_EQUAL(scroll.pos(), 4, "Position mismatch"); PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set at weird time"); - offset = scroll.move(-10, displacement); + offset = scroll.move(-10, displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(offset, 3, "Offset mismatch"); PQXX_CHECK_EQUAL(displacement, -4, "Displacement mismatch"); PQXX_CHECK_EQUAL(scroll.pos(), 0, "Hard bounce failed"); PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set during hard bounce"); - rows = scroll.fetch(3); + rows = scroll.fetch(3, pqxx::sl::current()); PQXX_CHECK_EQUAL(scroll.pos(), 3, "Bad pos()"); PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows"); PQXX_CHECK_EQUAL(rows[2][0].as(), 3, "pos() does not match data"); - rows = scroll.fetch(-1); + rows = scroll.fetch(-1, pqxx::sl::current()); PQXX_CHECK_EQUAL(scroll.pos(), 2, "Bad pos()"); PQXX_CHECK_EQUAL(rows[0][0].as(), 2, "pos() does not match data"); - rows = scroll.fetch(1); + rows = scroll.fetch(1, pqxx::sl::current()); PQXX_CHECK_EQUAL(scroll.pos(), 3, "Bad pos() after inverse turnaround"); PQXX_CHECK_EQUAL(rows[0][0].as(), 3, "Data position mismatch"); } @@ -182,7 +184,8 @@ void test_adopted_sql_cursor() PQXX_CHECK_EQUAL(adopted.endpos(), -1, "Adopted cursor has known endpos()"); auto displacement{0}; - auto rows{adopted.fetch(pqxx::cursor_base::all(), displacement)}; + auto rows{adopted.fetch( + pqxx::cursor_base::all(), displacement, pqxx::sl::current())}; PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows in result"); PQXX_CHECK_EQUAL(rows[0][0].as(), 1, "Wrong result data"); PQXX_CHECK_EQUAL(rows[2][0].as(), 3, "Wrong result data"); @@ -191,7 +194,8 @@ void test_adopted_sql_cursor() adopted.pos(), -1, "End-of-result set pos() on adopted cur"); PQXX_CHECK_EQUAL(adopted.endpos(), -1, "endpos() set too early"); - rows = adopted.fetch(pqxx::cursor_base::backward_all(), displacement); + rows = adopted.fetch( + pqxx::cursor_base::backward_all(), displacement, pqxx::sl::current()); PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows in result"); PQXX_CHECK_EQUAL(rows[0][0].as(), 3, "Wrong result data"); PQXX_CHECK_EQUAL(rows[2][0].as(), 1, "Wrong result data"); @@ -199,7 +203,7 @@ void test_adopted_sql_cursor() PQXX_CHECK_EQUAL(adopted.pos(), 0, "Failed to recognize starting position"); PQXX_CHECK_EQUAL(adopted.endpos(), -1, "endpos() set too early"); - auto offset{adopted.move(pqxx::cursor_base::all())}; + auto offset{adopted.move(pqxx::cursor_base::all(), pqxx::sl::current())}; PQXX_CHECK_EQUAL(offset, 3, "Unexpected move() offset"); PQXX_CHECK_EQUAL(adopted.pos(), 4, "Bad position on adopted cursor"); PQXX_CHECK_EQUAL(adopted.endpos(), 4, "endpos() not set properly"); @@ -244,7 +248,7 @@ void test_hold_cursor() pqxx::cursor_base::owned, true); tx.commit(); pqxx::work tx2(cx, "tx2"); - auto rows{with_hold.fetch(1)}; + auto rows{with_hold.fetch(1, pqxx::sl::current())}; PQXX_CHECK_EQUAL( std::size(rows), 1, "Did not get 1 row from with-hold cursor"); @@ -256,7 +260,8 @@ void test_hold_cursor() tx2.commit(); pqxx::work tx3(cx, "tx3"); PQXX_CHECK_THROWS( - no_hold.fetch(1), pqxx::sql_error, "Cursor not closed on commit"); + no_hold.fetch(1, pqxx::sl::current()), pqxx::sql_error, + "Cursor not closed on commit"); } diff --git a/test/test_stream_from.cxx b/test/test_stream_from.cxx index 25c173186..2e216a6f7 100644 --- a/test/test_stream_from.cxx +++ b/test/test_stream_from.cxx @@ -31,7 +31,7 @@ void test_nonoptionals(pqxx::connection &connection) // We can't read the "910" row -- it contains nulls, which our tuple does // not accept. extractor >> got_tuple; - PQXX_CHECK_NOTREACHED( + pqxx::test::check_notreached( "Failed to fail to stream null values into null-less fields."); } catch (pqxx::conversion_error const &e) @@ -86,7 +86,7 @@ void test_nonoptionals(pqxx::connection &connection) try { ex2 >> null_tup; - PQXX_CHECK_NOTREACHED( + pqxx::test::check_notreached( "stream_from should have refused to convert non-null value to " "nullptr_t."); } @@ -116,7 +116,7 @@ void test_bad_tuples(pqxx::connection &cx) try { extractor >> got_tuple_too_short; - PQXX_CHECK_NOTREACHED("stream_from improperly read first row"); + pqxx::test::check_notreached("stream_from improperly read first row"); } catch (pqxx::usage_error const &e) { @@ -133,7 +133,7 @@ void test_bad_tuples(pqxx::connection &cx) try { extractor >> got_tuple_too_long; - PQXX_CHECK_NOTREACHED("stream_from improperly read first row"); + pqxx::test::check_notreached("stream_from improperly read first row"); } catch (pqxx::usage_error const &e) { diff --git a/test/test_stream_to.cxx b/test/test_stream_to.cxx index 634179039..2aae7616f 100644 --- a/test/test_stream_to.cxx +++ b/test/test_stream_to.cxx @@ -148,7 +148,7 @@ void test_too_few_fields(pqxx::connection &connection) inserter << std::make_tuple(1234, "now", 4321, ipv4{8, 8, 8, 8}); inserter.complete(); tx.commit(); - PQXX_CHECK_NOTREACHED("stream_from improperly inserted row"); + pqxx::test::check_notreached("stream_from improperly inserted row"); } catch (pqxx::sql_error const &e) { @@ -171,7 +171,7 @@ void test_too_few_fields_fold(pqxx::connection &connection) inserter.write_values(1234, "now", 4321, ipv4{8, 8, 8, 8}); inserter.complete(); tx.commit(); - PQXX_CHECK_NOTREACHED("stream_from_fold improperly inserted row"); + pqxx::test::check_notreached("stream_from_fold improperly inserted row"); } catch (pqxx::sql_error const &e) { @@ -197,7 +197,7 @@ void test_too_many_fields(pqxx::connection &connection) bytea{'\x00', '\x01', '\x02'}, 5678); inserter.complete(); tx.commit(); - PQXX_CHECK_NOTREACHED("stream_from improperly inserted row"); + pqxx::test::check_notreached("stream_from improperly inserted row"); } catch (pqxx::sql_error const &e) { @@ -222,7 +222,7 @@ void test_too_many_fields_fold(pqxx::connection &connection) bytea{'\x00', '\x01', '\x02'}, 5678); inserter.complete(); tx.commit(); - PQXX_CHECK_NOTREACHED("stream_from_fold improperly inserted row"); + pqxx::test::check_notreached("stream_from_fold improperly inserted row"); } catch (pqxx::sql_error const &e) { diff --git a/test/test_test_helpers.cxx b/test/test_test_helpers.cxx index 7acac6138..e653d20b5 100644 --- a/test/test_test_helpers.cxx +++ b/test/test_test_helpers.cxx @@ -7,11 +7,11 @@ void empty() {} void test_check_notreached() { - // At a minimum, PQXX_CHECK_NOTREACHED must work. + // At a minimum, check_notreached() must work. bool failed{true}; try { - PQXX_CHECK_NOTREACHED("(expected)"); + pqxx::test::check_notreached("(expected)"); failed = false; } catch (pqxx::test::test_failure const &) @@ -19,7 +19,7 @@ void test_check_notreached() // This is what we expect. } if (not failed) - throw pqxx::test::test_failure{"PQXX_CHECK_NOTREACHED is broken."}; + throw pqxx::test::test_failure{"check_notreached is broken."}; } @@ -37,7 +37,7 @@ void test_check() catch (pqxx::test::test_failure const &) {} if (not failed) - PQXX_CHECK_NOTREACHED("PQXX_CHECK failed to notice failure."); + pqxx::test::check_notreached("PQXX_CHECK failed to notice failure."); } @@ -156,8 +156,8 @@ void test_test_helpers() // Test other helpers against PQXX_CHECK_THROWS. PQXX_CHECK_THROWS( - PQXX_CHECK_NOTREACHED("(expected)"), pqxx::test::test_failure, - "PQXX_CHECK_THROWS did not catch PQXX_CHECK_NOTREACHED."); + pqxx::test::check_notreached("(expected)"), pqxx::test::test_failure, + "PQXX_CHECK_THROWS did not catch check_notreached()."); PQXX_CHECK_THROWS( PQXX_CHECK(false, "(expected)"), pqxx::test::test_failure, diff --git a/test/test_types.hxx b/test/test_types.hxx index ebf53c94e..9b513fb32 100644 --- a/test/test_types.hxx +++ b/test/test_types.hxx @@ -87,11 +87,11 @@ template<> struct string_traits static constexpr bool converts_to_string{true}; static constexpr bool converts_from_string{true}; - static ipv4 from_string(std::string_view text) + static ipv4 from_string(std::string_view text, sl loc = sl::current()) { ipv4 ts; if (std::data(text) == nullptr) - internal::throw_null_conversion(type_name); + internal::throw_null_conversion(type_name, loc); std::vector ends; for (std::size_t i{0}; i < std::size(text); ++i) if (text[i] == '.')