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 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 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 3d66102d9..86940d634 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,10 +9,16 @@ build: tools: python: "3.12" apt_packages: + - "autoconf" + - "automake" + - "build-essential" - "graphviz" + - "libpq-dev" + - "make" jobs: pre_build: - - "./configure --enable-documentation CXXFLAGS='-O0 -std=c++23'" + - "lsb_release -a && g++ --version" + - "./configure CXXFLAGS=\"-O0 -std=c++23\" --enable-documentation" - "mkdir -p doc/doxygen-html" - "cd doc && doxygen" - "mkdir -p -- $READTHEDOCS_OUTPUT/html" 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/NEWS b/NEWS index b61940d63..aeb56dce7 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,27 @@ +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) + - 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`. + - 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. + - 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. + - 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. 7.10.1 - Fix string conversion buffer budget for arrays containing nulls. (#921) - Remove `-fanalyzer` option again; gcc is still broken. 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/VERSION b/VERSION index d6e2f7b0a..ae9a76b92 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.10.1 +8.0.0 diff --git a/cmake/pqxx_cxx_feature_checks.cmake b/cmake/pqxx_cxx_feature_checks.cmake index ac51729d3..bb2f6253a 100644 --- a/cmake/pqxx_cxx_feature_checks.cmake +++ b/cmake/pqxx_cxx_feature_checks.cmake @@ -7,18 +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 -) -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 @@ -31,18 +19,10 @@ 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 ) -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 @@ -55,14 +35,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 -) 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_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/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/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-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/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/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/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 da97c35db..58af65a35 100644 --- a/configitems +++ b/configitems @@ -5,21 +5,14 @@ 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 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 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 PQXX_HAVE_THREAD_LOCAL internal compiler diff --git a/configure b/configure index 866092d59..21ef77e63 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 @@ -17060,8 +17060,8 @@ 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. +# 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. { 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 @@ -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 @@ -17324,132 +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 -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 -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 @@ -17586,52 +17460,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 @@ -17672,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 @@ -17870,86 +17662,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 -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 @@ -19328,7 +19040,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 +19108,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/configure.ac b/configure.ac index 064ab2243..654ce3b6f 100644 --- a/configure.ac +++ b/configure.ac @@ -91,16 +91,16 @@ 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. +# 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. 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, @@ -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 diff --git a/cxx_features.txt b/cxx_features.txt index c9986486a..3b722f97e 100644 --- a/cxx_features.txt +++ b/cxx_features.txt @@ -12,8 +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 -PQXX_HAVE_SPAN __cpp_lib_span -PQXX_HAVE_SSIZE __cpp_lib_ssize 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 ab3ebe8f9..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 (internal::cmp_less(end - begin, budget)) - throw conversion_overrun{ - "Not enough buffer space to escape binary data."}; - std::string_view text{value.view()}; - internal::esc_bin(binary_cast(text), begin); - return begin + budget; - } - - static binarystring from_string(std::string_view text) - { - auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; - std::shared_ptr 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/blob.hxx b/include/pqxx/blob.hxx index 9a8621dee..dcd504934 100644 --- a/include/pqxx/blob.hxx +++ b/include/pqxx/blob.hxx @@ -19,19 +19,8 @@ #include -#if defined(PQXX_HAVE_PATH) -# 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" @@ -95,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 +96,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. @@ -114,44 +103,22 @@ 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))); } -#endif // PQXX_HAVE_SPAN -#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) /// Read up to `std::size(buf)` bytes from the object. /** Retrieves bytes from the blob, at the current position, until `buf` is * full or there are no more bytes to read, whichever comes first. * * Returns the filled portion of `buf`. This may be empty. */ - template std::span read(DATA &buf) - { - return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; - } -#else // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN - /// Read up to `std::size(buf)` bytes from the object. - /** @deprecated As libpqxx moves to C++20 as its baseline language version, - * this will take and return `std::span`. - * - * 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) + template writable_bytes_view read(DATA &buf) { return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; } -#endif // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN -#if defined(PQXX_HAVE_CONCEPTS) /// Write `data` to large object, at the current position. /** If the writing position is at the end of the object, this will append * `data` to the object's contents and move the writing position so that @@ -173,33 +140,8 @@ public: */ template void write(DATA const &data) { - raw_write(std::data(data), std::size(data)); + return raw_write(binary_cast(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 @@ -235,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. @@ -294,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 @@ -337,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 fdca38cf9..0e9834a5b 100644 --- a/include/pqxx/config.h.in +++ b/include/pqxx/config.h.in @@ -63,15 +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 - -/* Define if this feature is available. */ -#undef PQXX_HAVE_CONCEPTS - /* Define if this feature is available. */ #undef PQXX_HAVE_CXA_DEMANGLE @@ -81,15 +72,9 @@ /* 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 -/* Define if this feature is available. */ -#undef PQXX_HAVE_PATH - /* Define if this feature is available. */ #undef PQXX_HAVE_POLL @@ -99,12 +84,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 - /* Define if this feature is available. */ #undef PQXX_HAVE_STRERROR_R diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index 78b1ebc73..a24f63015 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -24,19 +24,16 @@ #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" #include "pqxx/params.hxx" +#include "pqxx/result.hxx" #include "pqxx/separated_list.hxx" #include "pqxx/strconv.hxx" #include "pqxx/types.hxx" @@ -80,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) { @@ -88,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. @@ -301,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: @@ -320,7 +311,6 @@ public: */ template inline connection(MAPPING const ¶ms); -#endif // PQXX_HAVE_CONCEPTS ~connection() { @@ -823,7 +813,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. @@ -841,7 +830,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 +854,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 @@ -874,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) && defined(PQXX_HAVE_SPAN) /// 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 @@ -913,23 +897,15 @@ public: internal::esc_bin(view, out); return zview{out, needed - 1}; } -#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; -#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. /** You can also just use @ref esc with a binary string. */ template @@ -937,16 +913,13 @@ public: { return esc_raw(bytes_view{std::data(data), std::size(data)}); } -#endif -#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) /// 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. @@ -968,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 @@ -976,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. @@ -1010,7 +981,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. @@ -1021,13 +992,18 @@ 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; + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape and quote binary data for use as a BYTEA value in SQL statement. + template + [[nodiscard]] std::string quote(DATA data) const + { + return esc_raw(binary_cast(data)); + } + // 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 @@ -1057,40 +1033,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. @@ -1124,8 +1066,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 @@ -1352,10 +1292,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 @@ -1455,14 +1391,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 @@ -1481,7 +1416,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( @@ -1490,7 +1425,6 @@ inline std::string connection::quote_columns(STRINGS const &columns) const } -#if defined(PQXX_HAVE_CONCEPTS) template inline connection::connection(MAPPING const ¶ms) { @@ -1512,23 +1446,5 @@ inline connection::connection(MAPPING const ¶ms) values.push_back(nullptr); 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/include/pqxx/doc/binary-data.md b/include/pqxx/doc/binary-data.md index 7c85cf4b8..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 if you're building in C++20 or -better, anything 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/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/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/field.hxx b/include/pqxx/field.hxx index 22475cc8f..16fe5a93c 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; } @@ -349,45 +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()) - PQXX_UNLIKELY - 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()}; @@ -412,8 +375,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/conversions.hxx b/include/pqxx/internal/conversions.hxx index 462c2d5ce..3e80f9d95 100644 --- a/include/pqxx/internal/conversions.hxx +++ b/include/pqxx/internal/conversions.hxx @@ -1,14 +1,11 @@ #include +#include #include #include #include #include #include - -#if defined(PQXX_HAVE_SPAN) && __has_include() -# include -#endif - +#include #include #include #include @@ -73,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}; @@ -98,7 +96,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)}; @@ -107,35 +105,24 @@ inline char *generic_into_buf(char *begin, char *end, T const &value) } -// C++20: Guard with concept? -/// 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); -// C++20: Guard with concept? -/// 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. @@ -203,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 @@ -338,7 +323,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); } @@ -361,7 +346,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); } @@ -369,7 +355,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); } @@ -491,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) { @@ -539,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}; @@ -560,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; }; @@ -586,12 +590,13 @@ 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); return begin + N; } + static constexpr std::size_t size_buffer(char const (&)[N]) noexcept { return N; @@ -618,7 +623,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. @@ -648,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 @@ -661,7 +671,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)); @@ -676,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; } }; @@ -700,7 +709,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'; @@ -713,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; }; @@ -895,21 +908,10 @@ inline constexpr bool is_unquoted_safe>{ is_unquoted_safe}; -template<> struct nullness : no_null -{}; - - -#if defined(PQXX_HAVE_CONCEPTS) 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}; @@ -928,7 +930,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); @@ -940,91 +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; } }; -#endif // PQXX_HAVE_CONCEPTS - - -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 (internal::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 (internal::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 @@ -1035,7 +957,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"}; @@ -1052,7 +974,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."}; @@ -1151,101 +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}; - - -#if defined(PQXX_HAVE_SPAN) && __has_include() -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}; -#endif - - -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)) @@ -1253,29 +1106,40 @@ 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; + } } 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/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..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)) @@ -170,16 +163,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/result_iterator.hxx b/include/pqxx/internal/result_iterator.hxx index 874f3b2a3..7b911b5ae 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 @@ -69,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" //@} /** @@ -93,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; } diff --git a/include/pqxx/internal/statement_parameters.hxx b/include/pqxx/internal/statement_parameters.hxx index d002b6b17..1e1b2319f 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" @@ -30,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/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/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/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/params.hxx b/include/pqxx/params.hxx index e11075fbf..52d3f8340 100644 --- a/include/pqxx/params.hxx +++ b/include/pqxx/params.hxx @@ -50,18 +50,19 @@ public: */ void reserve(std::size_t n) &; - // C++20: constexpr. /// Get the number of parameters currently in this `params`. - [[nodiscard]] auto size() const noexcept { return m_params.size(); } + [[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() &; @@ -81,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. @@ -93,7 +96,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. @@ -102,26 +104,14 @@ public: { append(bytes_view{std::data(data), std::size(data)}); } -#endif // PQXX_HAVE_CONCEPTS /// 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) & - { - for (auto ¶m : value) append(value.access(param)); - } - + /// 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 @@ -129,7 +119,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(); @@ -145,12 +135,10 @@ 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) reserve(std::size(*this) + std::size(range)); -#endif for (auto &value : range) append(value); } @@ -253,9 +241,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]; } } @@ -280,41 +267,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 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/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/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/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/row.hxx b/include/pqxx/row.hxx index 704b8bc2e..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 - m_begin; - } + [[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 @@ -194,24 +191,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,10 +234,7 @@ 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. + /// Number of columns in the row. size_type m_end = 0; private: @@ -297,9 +273,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} {} @@ -571,7 +545,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 fa61b4d30..e0226bfb7 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" @@ -202,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); @@ -514,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); } @@ -525,7 +529,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) + ...); } @@ -594,20 +598,6 @@ inline zview generic_to_buf(char *begin, char *end, TYPE const &value) else return {begin, traits::into_buf(begin, end, value) - begin - 1}; } - - -#if defined(PQXX_HAVE_CONCEPTS) -/// Concept: Binary string, akin to @c std::string for binary data. -/** Any type that satisfies this concept can represent an SQL BYTEA value. - * - * A @c binary has a @c begin(), @c end(), @c size(), and @data(). Each byte - * is a @c std::byte, and they must all be laid out contiguously in memory so - * we can reference them by a pointer. - */ -template -concept binary = std::ranges::contiguous_range and - std::is_same_v>, std::byte>; -#endif //@} } // namespace pqxx diff --git a/include/pqxx/stream_from.hxx b/include/pqxx/stream_from.hxx index 6a0687c38..09f927263 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( @@ -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/stream_to.hxx b/include/pqxx/stream_to.hxx index 6c4685307..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. @@ -134,7 +133,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,13 +150,12 @@ 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) { 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 @@ -238,28 +236,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 +434,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 diff --git a/include/pqxx/transaction_base.hxx b/include/pqxx/transaction_base.hxx index fdda1b4ee..19942e01e 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,24 +235,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 - { - 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). */ template @@ -284,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 @@ -339,7 +296,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.")]] @@ -357,12 +313,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. /** @@ -1042,9 +993,11 @@ 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..8c0fc98c3 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -16,10 +16,8 @@ #include #include #include - -#if defined(PQXX_HAVE_CONCEPTS) && __has_include() -# include -#endif +#include +#include namespace pqxx @@ -45,7 +43,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; @@ -74,89 +71,76 @@ 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>; +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 = strip_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()))>; -#endif // PQXX_HAVE_CONCEPTS +using value_type = + std::remove_cvref_t()))>; + + +/// A type one byte in size. +template +concept char_sized = (sizeof(CHAR) == 1); -#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 - std::same_as>, char>; +concept char_string = + std::ranges::contiguous_range and + char_sized> and + std::same_as>, value_type>; /// 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 concept potential_binary = - std::ranges::contiguous_range and (sizeof(value_type) == 1); -#endif // PQXX_HAVE_CONCEPTS - + std::ranges::contiguous_range and + (sizeof(value_type) == 1) and + not std::is_reference_v>; -// 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. +/// 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. */ -# define PQXX_RANGE_ARG std::ranges::range +template +concept binary = + std::ranges::contiguous_range and + std::same_as>, std::byte>; -/// 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 +/// 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>; -#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 +/// Type alias for a view of bytes. +using bytes_view = std::span; -/// 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 +/// Type alias for a view of writable bytes. +using writable_bytes_view = std::span; -#endif // PQXX_HAVE_CONCEPTS /// Marker for @ref stream_from constructors: "stream from table." /** @deprecated Use @ref stream_from::table() instead. @@ -169,6 +153,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 5ed0496b3..643cd157b 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)}; } } @@ -276,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 @@ -301,8 +233,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. @@ -364,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`. @@ -374,58 +306,30 @@ 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. - * - * 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`. +/// 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`. * - * @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) +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)}); } -#if defined(PQXX_HAVE_CONCEPTS) -template -concept char_sized = (sizeof(CHAR) == 1); -# define PQXX_CHAR_SIZED_ARG char_sized -#else -# define PQXX_CHAR_SIZED_ARG typename -#endif - +// 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. @@ -433,13 +337,10 @@ 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); - return { - reinterpret_cast(data), - check_cast(size, "binary data size")}; + return binary_cast(std::span{data, check_cast(size)}); } @@ -528,7 +429,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 @@ -538,6 +439,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); @@ -551,18 +464,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 @@ -616,15 +517,16 @@ 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())); @@ -635,9 +537,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. @@ -668,7 +570,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/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 { diff --git a/include/pqxx/zview.hxx b/include/pqxx/zview.hxx index a35460e97..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} {} @@ -64,9 +68,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()} {} @@ -92,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 { @@ -115,7 +123,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}; @@ -132,11 +139,11 @@ 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 -#endif // PQXX_HAVE_CONCEPTS namespace pqxx::internal @@ -157,9 +164,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(); } diff --git a/pqxx_cxx_feature_checks.ac b/pqxx_cxx_feature_checks.ac index 325608f29..9f9f74d8e 100644 --- a/pqxx_cxx_feature_checks.ac +++ b/pqxx_cxx_feature_checks.ac @@ -19,36 +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( - [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( - [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( @@ -79,16 +49,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( @@ -99,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( @@ -139,26 +89,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( - [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/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/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/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/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/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 53397dd05..18db0a470 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" @@ -106,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)}, @@ -162,7 +152,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 +467,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 +518,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 +594,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; @@ -653,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(); } @@ -790,17 +780,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 +886,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}; @@ -914,13 +903,11 @@ 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()}; + 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]] + throw failure{err_prefix + err_msg()}; } @@ -948,9 +935,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 +951,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; } @@ -982,62 +967,18 @@ 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"); } -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"); @@ -1049,9 +990,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 +1020,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 +1035,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 +1048,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 +1080,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 +1104,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 +1172,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..2e8d80b4c 100644 --- a/src/encodings.cxx +++ b/src/encodings.cxx @@ -63,11 +63,9 @@ 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)) + if (encoding_name.starts_with("EUC_"sv)) { auto const subtype{encoding_name.substr(4)}; static constexpr std::array subtypes{ @@ -82,40 +80,33 @@ 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(). - 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')) 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(). - 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) @@ -129,13 +120,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 +132,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) + else if (encoding_name == "UTF8"sv) [[likely]] return pqxx::internal::encoding_group::UTF8; - PQXX_UNLIKELY - break; + [[unlikely]] break; case 'W': if (encoding_name.substr(0, 3) == "WIN"sv) { @@ -164,12 +151,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 +179,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 +191,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/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/src/params.cxx b/src/params.cxx index ad1e38d4d..aa8350967 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)); @@ -99,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) { @@ -109,8 +103,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(internal::ssize(value), s_overflow)); + p.lengths.push_back(check_cast(std::ssize(value), s_overflow)); } p.formats.push_back(param_format(value)); 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/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/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 b460445ee..c3293b6bf 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. @@ -118,41 +118,39 @@ 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()) - 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'; return res.ptr; } -#endif } // 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}, @@ -178,48 +176,24 @@ 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) { -#if defined(PQXX_HAVE_CHARCONV_INT) // This is exactly what to_chars is good at. Trust standard library // implementers to optimise better than we can. return wrap_to_chars(begin, end, value); -#else - return generic_into_buf(begin, end, value); -#endif } -template char *integral_traits::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 @@ -289,9 +263,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)}; @@ -303,9 +275,8 @@ template 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()) @@ -332,168 +303,9 @@ 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 #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 @@ -569,9 +381,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; @@ -598,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 @@ -671,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; } @@ -704,48 +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_INT) +#if defined(PQXX_HAVE_CHARCONV_FLOAT) return from_string_arithmetic(text); #else - return from_string_integer(text); + 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) + +template +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/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..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 { @@ -38,7 +36,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 +44,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 +60,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 +186,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 +201,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..c82c8162a 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) { @@ -214,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. @@ -245,6 +238,30 @@ 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) { @@ -353,11 +370,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 +471,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 +496,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..57283c52f 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), @@ -110,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)); } @@ -120,12 +121,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 [[unlikely]] if (c >= 'A' and c <= 'F') + return ten + (c - 'A'); + else [[unlikely]] + return -1; } } // namespace diff --git a/test/Makefile.am b/test/Makefile.am index c6a150aa4..67280a457 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 \ @@ -70,16 +69,12 @@ runner_SOURCES = \ test75.cxx \ test76.cxx \ test77.cxx \ - test78.cxx \ - test79.cxx \ test82.cxx \ test84.cxx \ - test87.cxx \ test88.cxx \ 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 \ @@ -102,7 +97,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 c73c756c2..9a3c5521d 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -126,23 +126,22 @@ 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) \ - 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) \ + 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) 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) \ @@ -152,8 +151,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) \ @@ -191,28 +189,24 @@ 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)/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)/test82.Po \ + ./$(DEPDIR)/test84.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 \ @@ -231,7 +225,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 \ @@ -472,7 +465,6 @@ runner_SOURCES = \ test00.cxx \ test01.cxx \ test02.cxx \ - test04.cxx \ test07.cxx \ test10.cxx \ test11.cxx \ @@ -502,16 +494,12 @@ runner_SOURCES = \ test75.cxx \ test76.cxx \ test77.cxx \ - test78.cxx \ - test79.cxx \ test82.cxx \ test84.cxx \ - test87.cxx \ test88.cxx \ 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 \ @@ -534,7 +522,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 \ @@ -608,8 +595,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) \ @@ -654,8 +639,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) \ @@ -712,7 +695,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 @@ -742,16 +724,12 @@ 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 @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 @@ -774,7 +752,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 @@ -1059,7 +1036,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 @@ -1089,16 +1065,12 @@ 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 -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 @@ -1121,7 +1093,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 @@ -1191,7 +1162,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 @@ -1221,16 +1191,12 @@ 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 -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 @@ -1253,7 +1219,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/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 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); 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_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()."); } 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_blob.cxx b/test/unit/test_blob.cxx index 846ec14ad..e11103d1e 100644 --- a/test/unit/test_blob.cxx +++ b/test/unit/test_blob.cxx @@ -1,4 +1,5 @@ #include +#include #include #include @@ -183,7 +184,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 +230,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 +285,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 +305,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 } @@ -591,10 +588,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'}}; @@ -607,9 +601,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 } 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_errorhandler.cxx b/test/unit/test_errorhandler.cxx index 9dc27a806..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 { @@ -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; diff --git a/test/unit/test_escape.cxx b/test/unit/test_escape.cxx index 956583578..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) && defined(PQXX_HAVE_SPAN) 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) && defined(PQXX_HAVE_SPAN) 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_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."); diff --git a/test/unit/test_prepared_statement.cxx b/test/unit/test_prepared_statement.cxx index 5a307b974..54596a54a 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)}; @@ -226,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. { @@ -238,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_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/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_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" 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."); diff --git a/test/unit/test_stream_to.cxx b/test/unit/test_stream_to.cxx index 12e0eb2aa..20efef55f 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"})}; @@ -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(); 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 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. 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 = ( '',