Repository: taocpp/taopq Branch: main Commit: 0f28919fd7aa Files: 146 Total size: 586.6 KB Directory structure: gitextract_iares_uf/ ├── .clang-format ├── .clang-tidy ├── .github/ │ ├── conan/ │ │ ├── conanfile.py │ │ └── profiles/ │ │ ├── clangcl │ │ └── msvc │ └── workflows/ │ ├── clang-analyze.yml │ ├── clang-format.yml │ ├── clang-tidy.yml │ ├── code-coverage.yml │ ├── linux.yml │ ├── macos.yml │ ├── sanitizer.yml │ └── windows.yml ├── .gitignore ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE_1_0.txt ├── Makefile ├── README.md ├── doc/ │ ├── Aggregate-Support.md │ ├── Binary-Data.md │ ├── Bulk-Transfer.md │ ├── Connection-Pool.md │ ├── Connection.md │ ├── Error-Handling.md │ ├── Getting-Started.md │ ├── Installation.md │ ├── Large-Object.md │ ├── Parameter-Type-Conversion.md │ ├── Performance.md │ ├── Requirements.md │ ├── Result-Type-Conversion.md │ ├── Result.md │ ├── Statement.md │ ├── TOC.md │ └── Transaction.md ├── example/ │ ├── CMakeLists.txt │ └── get_version/ │ ├── CMakeLists.txt │ └── main.cpp ├── include/ │ └── tao/ │ ├── pq/ │ │ ├── access_mode.hpp │ │ ├── binary.hpp │ │ ├── bind.hpp │ │ ├── connection.hpp │ │ ├── connection_pool.hpp │ │ ├── connection_status.hpp │ │ ├── exception.hpp │ │ ├── field.hpp │ │ ├── internal/ │ │ │ ├── aggregate.hpp │ │ │ ├── demangle.hpp │ │ │ ├── exclusive_scan.hpp │ │ │ ├── format_as.hpp │ │ │ ├── from_chars.hpp │ │ │ ├── gen.hpp │ │ │ ├── parameter_traits_helper.hpp │ │ │ ├── poll.hpp │ │ │ ├── pool.hpp │ │ │ ├── resize_uninitialized.hpp │ │ │ ├── strtox.hpp │ │ │ ├── unreachable.hpp │ │ │ └── zsv.hpp │ │ ├── is_aggregate.hpp │ │ ├── is_array.hpp │ │ ├── isolation_level.hpp │ │ ├── large_object.hpp │ │ ├── log.hpp │ │ ├── notification.hpp │ │ ├── null.hpp │ │ ├── oid.hpp │ │ ├── parameter.hpp │ │ ├── parameter_traits.hpp │ │ ├── parameter_traits_aggregate.hpp │ │ ├── parameter_traits_array.hpp │ │ ├── parameter_traits_optional.hpp │ │ ├── parameter_traits_pair.hpp │ │ ├── parameter_traits_tuple.hpp │ │ ├── pipeline.hpp │ │ ├── pipeline_status.hpp │ │ ├── poll.hpp │ │ ├── result.hpp │ │ ├── result_status.hpp │ │ ├── result_traits.hpp │ │ ├── result_traits_aggregate.hpp │ │ ├── result_traits_array.hpp │ │ ├── result_traits_optional.hpp │ │ ├── result_traits_pair.hpp │ │ ├── result_traits_tuple.hpp │ │ ├── row.hpp │ │ ├── table_field.hpp │ │ ├── table_reader.hpp │ │ ├── table_row.hpp │ │ ├── table_writer.hpp │ │ ├── transaction.hpp │ │ ├── transaction_base.hpp │ │ ├── transaction_status.hpp │ │ └── version.hpp │ └── pq.hpp ├── src/ │ └── lib/ │ └── pq/ │ ├── connection.cpp │ ├── connection_pool.cpp │ ├── exception.cpp │ ├── field.cpp │ ├── internal/ │ │ ├── demangle.cpp │ │ ├── poll.cpp │ │ └── strtox.cpp │ ├── large_object.cpp │ ├── parameter_traits.cpp │ ├── pipeline.cpp │ ├── result.cpp │ ├── result_traits.cpp │ ├── result_traits_array.cpp │ ├── row.cpp │ ├── table_field.cpp │ ├── table_reader.cpp │ ├── table_row.cpp │ ├── table_writer.cpp │ ├── transaction.cpp │ └── transaction_base.cpp └── test/ ├── CMakeLists.txt ├── integration/ │ ├── aggregate.cpp │ ├── array.cpp │ ├── basic_datatypes.cpp │ ├── chunk_mode.cpp │ ├── connection.cpp │ ├── connection_pool.cpp │ ├── example.cpp │ ├── exception.cpp │ ├── large_object.cpp │ ├── log.cpp │ ├── notifications.cpp │ ├── parameter.cpp │ ├── password.cpp │ ├── pipeline_mode.cpp │ ├── result.cpp │ ├── row.cpp │ ├── single_row_mode.cpp │ ├── table_reader.cpp │ ├── table_writer.cpp │ ├── traits.cpp │ └── transaction.cpp ├── unit/ │ ├── getenv.cpp │ ├── parameter_type.cpp │ ├── resize_uninitialized.cpp │ ├── result_type.cpp │ └── strtox.cpp └── utils/ ├── compare.hpp ├── getenv.hpp └── macros.hpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ # The Art of C++ # https://github.com/taocpp # Copyright (c) 2016-2026 Dr. Colin Hirsch and Daniel Frey # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) # This is our official .clang-format style for https://github.com/taocpp # # clang-format -i -style=file $(find . -name '[^.]*.[hc]pp') Language: Cpp Standard: Latest AccessModifierOffset: -3 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: false AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: true AfterControlStatement: false AfterEnum : true AfterFunction : true AfterNamespace : true AfterStruct : true AfterUnion : true AfterExternBlock: true BeforeCatch : true BeforeElse : true IndentBraces : false SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false BreakBeforeBinaryOperators: All BreakBeforeBraces: Custom BreakBeforeTernaryOperators: false BreakConstructorInitializers: BeforeColon BreakInheritanceList: BeforeColon BreakStringLiterals: false ColumnLimit: 0 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 3 ContinuationIndentWidth: 3 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true IncludeBlocks: Preserve IndentCaseLabels: true IndentPPDirectives: None IndentWidth: 3 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MaxEmptyLinesToKeep: 1 NamespaceIndentation: All PointerAlignment: Left ReflowComments: false SortIncludes: true SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: Never SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: true SpacesInCStyleCastParentheses: false SpacesInParentheses: true SpacesInSquareBrackets: true TabWidth: 8 UseTab: Never ================================================ FILE: .clang-tidy ================================================ # The Art of C++ # https://github.com/taocpp # Copyright (c) 2016-2026 Dr. Colin Hirsch and Daniel Frey # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) # Note: The misc-include-cleaner is generally useful, # but produces false positives with Oid/InvalidOid and libpq-fe.h. # For that reason it is disabled, but it should be enabled # manually from time to time. Checks: >- bugprone-*, -bugprone-easily-swappable-parameters, cppcoreguidelines-slicing, cppcoreguidelines-special-member-functions, google-build-explicit-make-pair, google-build-namespaces, google-default-arguments, google-global-names-in-headers, google-readability-casting, llvm-*, misc-*, -misc-include-cleaner, -misc-non-private-member-variables-in-classes, -misc-unused-alias-decls, modernize-*, -modernize-avoid-c-arrays, -modernize-concat-nested-namespaces, -modernize-raw-string-literal, performance-*, readability-*, -readability-avoid-const-params-in-decls, -readability-function-cognitive-complexity, -readability-identifier-length, -readability-magic-numbers, -readability-non-const-parameter, CheckOptions: - { key: readability-identifier-naming.ClassCase, value: lower_case } - { key: readability-identifier-naming.FunctionCase, value: lower_case } - { key: readability-identifier-naming.ParameterCase, value: lower_case } - { key: readability-identifier-naming.StructCase, value: lower_case } - { key: readability-identifier-naming.VariableCase, value: lower_case } WarningsAsErrors: '*' ================================================ FILE: .github/conan/conanfile.py ================================================ from conan import ConanFile from conan.tools.cmake import CMakeDeps, CMakeToolchain, cmake_layout class TaopqRequirements(ConanFile): settings = "os", "compiler", "build_type", "arch" default_options = { "libpq/*:with_openssl": False, "libpq/*:with_icu": False, "libpq/*:with_zlib": False, "libpq/*:with_zstd": False, "libpq/*:with_libxml2": False, "libpq/*:with_lz4": False, "libpq/*:with_xslt": False, "libpq/*:with_readline": False } def layout(self): cmake_layout(self) def requirements(self): self.requires("libpq/[*]") def generate(self): tc = CMakeToolchain(self) tc.generate() deps = CMakeDeps(self) deps.set_property("libpq", "cmake_file_name", "PostgreSQL") deps.set_property("libpq", "cmake_target_name", "PostgreSQL::PostgreSQL") deps.set_property("libpq", "cmake_additional_variables_prefixes", ["PostgreSQL",]) deps.generate() ================================================ FILE: .github/conan/profiles/clangcl ================================================ [settings] os=Windows arch=x86_64 build_type=Release compiler=clang compiler.version=19 compiler.cppstd=20 compiler.runtime=dynamic compiler.runtime_type=Release compiler.runtime_version=v144 libpq/*:compiler=msvc libpq/*:compiler.version=194 libpq/*:compiler.cppstd=20 libpq/*:compiler.runtime=dynamic libpq/*:compiler.runtime_type=Release [conf] tools.cmake.cmaketoolchain:generator=Ninja tools.meson.mesontoolchain:backend=ninja tools.build:compiler_executables={"c": "clang-cl.exe", "cpp": "clang-cl.exe"} libpq/*:tools.build:compiler_executables={"c": "cl.exe", "cpp": "cl.exe"} ================================================ FILE: .github/conan/profiles/msvc ================================================ [settings] arch=x86_64 build_type=Release compiler=msvc compiler.cppstd=20 compiler.runtime=dynamic compiler.runtime_type=Release compiler.version=194 os=Windows [conf] tools.cmake.cmaketoolchain:generator=Ninja tools.meson.mesontoolchain:backend=ninja ================================================ FILE: .github/workflows/clang-analyze.yml ================================================ name: clang-analyze on: push: paths-ignore: - 'README.md' - 'doc/**' pull_request: paths-ignore: - 'README.md' - 'doc/**' jobs: clang-analyze: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: clang-tools libpq-dev version: 1.0 - name: Configure CMake Project run: scan-build cmake --preset unixlike-release-dev - name: Build project files run: scan-build cmake --build --preset unixlike-release-dev ================================================ FILE: .github/workflows/clang-format.yml ================================================ name: clang-format on: push: paths-ignore: - 'README.md' - 'doc/**' pull_request: paths-ignore: - 'README.md' - 'doc/**' jobs: clang-format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: DoozyX/clang-format-lint-action@v0.20 with: extensions: 'hpp,cpp' ================================================ FILE: .github/workflows/clang-tidy.yml ================================================ name: clang-tidy on: push: paths-ignore: - 'README.md' - 'doc/**' pull_request: paths-ignore: - 'README.md' - 'doc/**' jobs: clang-tidy: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: libpq-dev clang-tidy version: 3.0 - name: Configure CMake Project run: cmake --preset unixlike-debug-dev -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF - name: Build project files run: cmake --build --preset unixlike-debug-dev - name: Run clang-tidy run: run-clang-tidy -p build/unixlike-debug-dev/ ================================================ FILE: .github/workflows/code-coverage.yml ================================================ name: code-coverage on: push: paths-ignore: - 'README.md' - 'doc/**' pull_request: paths-ignore: - 'README.md' - 'doc/**' jobs: code-coverage: runs-on: ubuntu-24.04 services: postgres: image: postgres:latest env: POSTGRES_DB: postgres POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres ports: - 5432:5432 # Set health checks to wait until postgres has started options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: TAOPQ_TEST_DATABASE: host=localhost dbname=postgres user=postgres password=postgres steps: - uses: actions/checkout@v5 - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: libpq-dev version: 1.0 - name: Exclude example folder from code coverage run: | printf "ignore:\n - example/**\n" >> .codecov.yml - name: Configure CMake Project run: cmake --preset unixlike-release-dev -DCMAKE_CXX_FLAGS="-coverage" -DBUILD_EXAMPLES=OFF - name: Build project files run: cmake --build --preset unixlike-release-dev - name: Run tests run: cmake --build --preset unixlike-release-dev --target test - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} ================================================ FILE: .github/workflows/linux.yml ================================================ name: Linux on: push: paths-ignore: - 'README.md' - 'doc/**' pull_request: paths-ignore: - 'README.md' - 'doc/**' jobs: linux-next: strategy: matrix: compiler: - g++-13 - g++-14 - g++-15 - g++-16 - clang++-18 runs-on: ubuntu-24.04 services: postgres: image: postgres:latest env: POSTGRES_DB: postgres POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres ports: - 5432:5432 # Set health checks to wait until postgres has started options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: CXX: ${{ matrix.compiler }} TAOPQ_TEST_DATABASE: host=localhost dbname=postgres user=postgres password=postgres steps: - uses: actions/checkout@v5 - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: libpq-dev libc++-dev version: 2.0 - name: Install GCC-15 if: matrix.compiler == 'g++-15' run: | sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt-get update sudo apt-get install -y g++-15 - name: Install GCC-16 if: matrix.compiler == 'g++-16' run: | sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt-get update sudo apt-get install -y g++-16 - name: Configure CMake Project run: cmake --preset unixlike-release-dev -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/taopq-install - name: Build project files run: cmake --build --preset unixlike-release-dev - name: Run tests run: cmake --build --preset unixlike-release-dev --target test - name: Run examples run: cmake --build --preset unixlike-release-dev --target examples env: PGDATABASE: ${{ env.TAOPQ_TEST_DATABASE }} - name: Install Taocpp TaoPQ run: cmake --build --preset unixlike-release-dev --target install linux-gcc-old-abi: runs-on: ubuntu-24.04 services: postgres: image: postgres:latest env: POSTGRES_DB: postgres POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres ports: - 5432:5432 # Set health checks to wait until postgres has started options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: CXX: g++ TAOPQ_TEST_DATABASE: host=localhost dbname=postgres user=postgres password=postgres steps: - uses: actions/checkout@v5 - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: libpq-dev libc++-dev version: 2.0 - name: Configure CMake Project run: cmake --preset unixlike-release-dev -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/taopq-install -DCMAKE_CXX_FLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" - name: Build project files run: cmake --build --preset unixlike-release-dev - name: Run tests run: cmake --build --preset unixlike-release-dev --target test linux-clang-extra: strategy: matrix: flags: ["-stdlib=libc++","-fms-extensions"] runs-on: ubuntu-24.04 services: postgres: image: postgres:latest env: POSTGRES_DB: postgres POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres ports: - 5432:5432 # Set health checks to wait until postgres has started options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: CXX: clang++ TAOPQ_TEST_DATABASE: host=localhost dbname=postgres user=postgres password=postgres steps: - uses: actions/checkout@v5 - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: libpq-dev libc++-dev version: 2.0 - name: Configure CMake Project run: cmake --preset unixlike-release-dev -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/taopq-install -DCMAKE_CXX_FLAGS="${{ matrix.flags }}" - name: Build project files run: cmake --build --preset unixlike-release-dev - name: Run tests run: cmake --build --preset unixlike-release-dev --target test linux-makefile: runs-on: ubuntu-24.04 services: postgres: image: postgres:latest env: POSTGRES_DB: postgres POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres ports: - 5432:5432 # Set health checks to wait until postgres has started options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: TAOPQ_TEST_DATABASE: host=localhost dbname=postgres user=postgres password=postgres CXX: g++-13 steps: - uses: actions/checkout@v5 - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: libpq-dev libc++-dev version: 2.0 - name: Build project files with Makefile run: make -j$(nproc) - name: Run tests with Makefile run: make test env: PGDATABASE: ${{ env.TAOPQ_TEST_DATABASE }} ================================================ FILE: .github/workflows/macos.yml ================================================ name: macOS on: push: paths-ignore: - 'README.md' - 'doc/**' pull_request: paths-ignore: - 'README.md' - 'doc/**' jobs: xcode-macos-15: runs-on: macos-15 env: TAOPQ_TEST_DATABASE: host=localhost dbname=postgres user=postgres password=postgres steps: - uses: actions/checkout@v5 - uses: ikalnytskyi/action-setup-postgres@v8 - uses: conan-io/setup-conan@v1 with: cache_packages: true - name: Install dependencies with Conan run: conan install ${{ github.workspace }}/.github/conan/conanfile.py -s compiler.cppstd=20 --build=missing --lockfile-partial - name: Configure CMake Project run: cmake --preset unixlike-release-dev -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/taopq-install -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/.github/conan/build/Release/generators/conan_toolchain.cmake - name: Build project files run: cmake --build --preset unixlike-release-dev - name: Run tests run: cmake --build --preset unixlike-release-dev --target test - name: Run examples run: cmake --build --preset unixlike-release-dev --target examples env: PGDATABASE: ${{ env.TAOPQ_TEST_DATABASE }} - name: Install Taocpp TaoPQ run: cmake --build --preset unixlike-release-dev --target install ================================================ FILE: .github/workflows/sanitizer.yml ================================================ name: Sanitizer on: push: paths-ignore: - 'README.md' - 'doc/**' pull_request: paths-ignore: - 'README.md' - 'doc/**' jobs: sanitizer: strategy: matrix: sanitizer: [address, undefined, thread, leak] runs-on: ubuntu-24.04 services: postgres: image: postgres:latest env: POSTGRES_DB: postgres POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres ports: - 5432:5432 # Set health checks to wait until postgres has started options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: CXX: clang++ CC: clang TAOPQ_TEST_DATABASE: host=localhost dbname=postgres user=postgres password=postgres steps: - uses: actions/checkout@v5 - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: libpq-dev version: 1.0 - name: Configure CMake Project run: cmake --preset unixlike-debug-dev -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/taopq-install -DCMAKE_CXX_FLAGS="-fsanitize=${{ matrix.sanitizer }}" - name: Build project files run: cmake --build --preset unixlike-debug-dev - name: Run tests run: cmake --build --preset unixlike-debug-dev --target test ================================================ FILE: .github/workflows/windows.yml ================================================ name: Windows on: push: paths-ignore: - 'README.md' - 'doc/**' pull_request: paths-ignore: - 'README.md' - 'doc/**' jobs: vs2022: runs-on: windows-latest strategy: fail-fast: false matrix: cxx: [ "msvc", "clangcl" ] env: TAOPQ_TEST_DATABASE: host=localhost dbname=postgres user=postgres password=postgres steps: - uses: actions/checkout@v5 - name: Set up PostgreSQL uses: ikalnytskyi/action-setup-postgres@v8 - name: Set up MSVC dev cmd (x64) uses: ilammy/msvc-dev-cmd@v1 with: arch: x64 - name: Set up Conan uses: conan-io/setup-conan@v1 with: cache_packages: true config_urls: .github/conan - name: Install dependencies with Conan MSVC run: conan install ${{ github.workspace }}/.github/conan/conanfile.py --build=missing -pr ${{ matrix.cxx }} --lockfile-partial - name: CMake Configure shell: pwsh run: cmake --preset ${{ matrix.cxx }}-release-dev -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/taopq-install -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/.github/conan/build/Release/generators/conan_toolchain.cmake - name: Build project files run: cmake --build --preset ${{ matrix.cxx }}-release-dev - name: Run tests run: cmake --build --preset ${{ matrix.cxx }}-release-dev --target test - name: Run examples run: cmake --build --preset ${{ matrix.cxx }}-release-dev --target examples env: PGDATABASE: ${{ env.TAOPQ_TEST_DATABASE }} - name: Install Taocpp TaoPQ run: cmake --build --preset ${{ matrix.cxx }}-release-dev --target install ================================================ FILE: .gitignore ================================================ *~ build dummy.txt ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) # Read version from version.hpp file(READ "${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/version.hpp" taopq_VERSION_DATA) string(REGEX MATCH "#define TAO_PQ_VERSION \"([^\"]+)\"" _ ${taopq_VERSION_DATA}) set(taopq_VERSION "${CMAKE_MATCH_1}") project(taopq VERSION ${taopq_VERSION} LANGUAGES CXX DESCRIPTION "A lightweight C++ client library for accessing a PostgreSQL database" HOMEPAGE_URL "https://github.com/taocpp/taopq" ) option(BUILD_EXAMPLES "Build taopq examples" ON) find_package(PostgreSQL REQUIRED) add_library(${PROJECT_NAME}) add_library(taocpp::taopq ALIAS ${PROJECT_NAME}) target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/connection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/connection_pool.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/exception.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/field.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/internal/demangle.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/internal/poll.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/internal/strtox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/large_object.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/parameter_traits.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/pipeline.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/result.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/result_traits.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/result_traits_array.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/row.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/table_field.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/table_reader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/table_row.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/table_writer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/transaction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/pq/transaction_base.cpp PUBLIC FILE_SET HEADERS BASE_DIRS include FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/access_mode.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/binary.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/bind.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/connection.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/connection_pool.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/connection_status.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/exception.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/field.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/aggregate.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/demangle.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/exclusive_scan.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/format_as.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/from_chars.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/gen.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/parameter_traits_helper.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/poll.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/pool.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/resize_uninitialized.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/strtox.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/unreachable.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/internal/zsv.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/is_aggregate.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/is_array.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/isolation_level.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/large_object.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/log.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/notification.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/null.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/oid.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/parameter.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/parameter_traits.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/parameter_traits_aggregate.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/parameter_traits_array.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/parameter_traits_optional.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/parameter_traits_pair.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/parameter_traits_tuple.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/pipeline.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/pipeline_status.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/poll.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/result.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/result_status.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/result_traits.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/result_traits_aggregate.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/result_traits_array.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/result_traits_optional.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/result_traits_pair.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/result_traits_tuple.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/row.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/table_field.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/table_reader.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/table_row.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/table_writer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/transaction.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/transaction_base.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/transaction_status.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/tao/pq/version.hpp ) target_include_directories(${PROJECT_NAME} PUBLIC $ $ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ) target_link_libraries(${PROJECT_NAME} PUBLIC PostgreSQL::PostgreSQL) if(WIN32) target_link_libraries(${PROJECT_NAME} PUBLIC ws2_32) endif() target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME taopq VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON EXPORT_NAME taopq ) if(PROJECT_IS_TOP_LEVEL) include(GNUInstallDirs) include(CMakePackageConfigHelpers) install(TARGETS taopq EXPORT taopq-targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} FILE_SET HEADERS ) install(FILES LICENSE_1_0.txt DESTINATION ${CMAKE_INSTALL_DOCDIR}) install(EXPORT taopq-targets FILE ${PROJECT_NAME}Targets.cmake NAMESPACE taocpp:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/taopq ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "include(CMakeFindDependencyMacro) find_dependency(PostgreSQL REQUIRED CONFIG) include(\"\${CMAKE_CURRENT_LIST_DIR}/taopqTargets.cmake\") ") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/taopq ) export(EXPORT taopq-targets FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake" NAMESPACE taocpp:: ) endif() if(BUILD_TESTING AND PROJECT_IS_TOP_LEVEL) enable_testing() add_subdirectory(test) endif() if(BUILD_EXAMPLES AND PROJECT_IS_TOP_LEVEL) add_subdirectory(example) endif() ================================================ FILE: CMakePresets.json ================================================ { "version": 6, "cmakeMinimumRequired": { "major": 3, "minor": 21, "patch": 0 }, "configurePresets": [ { "name": "default", "hidden": true, "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" } }, { "name": "dev-flags-gcc-clang", "hidden": true, "cacheVariables": { "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Werror -Wconversion -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wunused -Woverloaded-virtual -Wnull-dereference -Wno-sign-conversion", "BUILD_TESTING": "ON", "BUILD_EXAMPLES": "ON" } }, { "name": "dev-flags-msvc", "hidden": true, "cacheVariables": { "CMAKE_CXX_FLAGS": "/EHsc /W4 /WX /permissive-", "BUILD_TESTING": "ON", "BUILD_EXAMPLES": "ON" } }, { "name": "debug", "inherits": "default", "displayName": "Debug", "description": "Debug build", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } }, { "name": "release", "inherits": "default", "displayName": "Release", "description": "Release build with optimizations", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } }, { "name": "unixlike-debug", "inherits": "debug", "displayName": "Unix Like Compiler Debug", "description": "Debug build using GCC or Clang" }, { "name": "unixlike-release", "inherits": "release", "displayName": "Unix Like Compiler Release", "description": "Release build using GCC or Clang" }, { "name": "unixlike-debug-dev", "inherits": ["debug", "dev-flags-gcc-clang"], "displayName": "Unix Like Compiler Debug (Dev with -Werror)", "description": "Debug build using Clang or GCC with warnings as errors" }, { "name": "unixlike-release-dev", "inherits": ["release", "dev-flags-gcc-clang"], "displayName": "Unix Like Compiler Release (Dev with -Werror)", "description": "Release build using Clang or GCC with warnings as errors" }, { "name": "clangcl-debug", "inherits": "debug", "displayName": "MSVC Debug", "description": "Debug build using MSVC", "generator": "Ninja", "cacheVariables": { "CMAKE_C_COMPILER": "clang-cl", "CMAKE_CXX_COMPILER": "clang-cl" }, "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" } }, { "name": "clangcl-release", "inherits": "release", "displayName": "MSVC Release", "description": "Ninja", "generator": "Ninja", "cacheVariables": { "CMAKE_C_COMPILER": "clang-cl", "CMAKE_CXX_COMPILER": "clang-cl" }, "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" } }, { "name": "msvc-debug", "inherits": "debug", "displayName": "MSVC Debug", "description": "Ninja", "generator": "Ninja", "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl" }, "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" } }, { "name": "msvc-release", "inherits": "release", "displayName": "MSVC Release", "description": "Release build using MSVC", "generator": "Ninja", "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl" }, "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" } }, { "name": "msvc-debug-dev", "inherits": ["msvc-debug", "dev-flags-msvc"], "displayName": "MSVC Debug (Dev with /WX)", "description": "Debug build using MSVC with warnings as errors" }, { "name": "msvc-release-dev", "inherits": ["msvc-release", "dev-flags-msvc"], "displayName": "MSVC Release (Dev with /WX)", "description": "Release build using MSVC with warnings as errors" }, { "name": "clangcl-debug-dev", "inherits": ["clangcl-debug", "dev-flags-msvc"], "displayName": "ClangCL Debug (Dev with /WX)", "description": "Debug build using ClangCL with warnings as errors" }, { "name": "clangcl-release-dev", "inherits": ["clangcl-release", "dev-flags-msvc"], "displayName": "ClangCL Release (Dev with /WX)" } ], "buildPresets": [ { "name": "debug", "configurePreset": "debug", "displayName": "Debug Build", "description": "Build in Debug mode" }, { "name": "release", "configurePreset": "release", "displayName": "Release Build", "description": "Build in Release mode" }, { "name": "unixlike-debug", "configurePreset": "unixlike-debug", "displayName": "Unix Like Compiler Debug Build" }, { "name": "unixlike-release", "configurePreset": "unixlike-release", "displayName": "Unix Like Compiler Release Build" }, { "name": "unixlike-debug-dev", "configurePreset": "unixlike-debug-dev", "displayName": "Unix Like Compiler Debug Build (Dev)" }, { "name": "unixlike-release-dev", "configurePreset": "unixlike-release-dev", "displayName": "Unix Like Compiler Release Build (Dev)" }, { "name": "msvc-debug", "configurePreset": "msvc-debug", "displayName": "MSVC Debug Build" }, { "name": "msvc-release", "configurePreset": "msvc-release", "displayName": "MSVC Release Build" }, { "name": "msvc-debug-dev", "configurePreset": "msvc-debug-dev", "displayName": "MSVC Debug Build (Dev)" }, { "name": "msvc-release-dev", "configurePreset": "msvc-release-dev", "displayName": "MSVC Release Build (Dev)" }, { "name": "clangcl-debug", "configurePreset": "clangcl-debug", "displayName": "MSVC Clang Debug Build" }, { "name": "clangcl-release", "configurePreset": "clangcl-release", "displayName": "MSVC Clang Release Build" }, { "name": "clangcl-debug-dev", "configurePreset": "clangcl-debug-dev", "displayName": "ClangCL Debug Build (Dev)" }, { "name": "clangcl-release-dev", "configurePreset": "clangcl-release-dev", "displayName": "ClangCL Release Build (Dev)" } ], "testPresets": [ { "name": "debug", "configurePreset": "debug", "displayName": "Test Debug", "output": { "outputOnFailure": true } }, { "name": "release", "configurePreset": "release", "displayName": "Test Release", "output": { "outputOnFailure": true } }, { "name": "unixlike-release", "configurePreset": "unixlike-release", "displayName": "Test Unix Like Compiler Release", "output": { "outputOnFailure": true } }, { "name": "unixlike-debug", "configurePreset": "unixlike-debug", "displayName": "Test Unix Like Compiler Debug", "output": { "outputOnFailure": true } }, { "name": "unixlike-debug-dev", "configurePreset": "unixlike-debug-dev", "displayName": "Test Unix Like Compiler Debug (Dev)", "output": { "outputOnFailure": true } }, { "name": "unixlike-release-dev", "configurePreset": "unixlike-release-dev", "displayName": "Test Unix Like Compiler Release (Dev)", "output": { "outputOnFailure": true } }, { "name": "msvc-debug-dev", "configurePreset": "msvc-debug-dev", "displayName": "Test MSVC Debug (Dev)", "output": { "outputOnFailure": true } }, { "name": "msvc-release-dev", "configurePreset": "msvc-release-dev", "displayName": "Test MSVC Release (Dev)", "output": { "outputOnFailure": true } }, { "name": "clangcl-debug-dev", "configurePreset": "clangcl-debug-dev", "displayName": "Test ClangCL Debug (Dev)", "output": { "outputOnFailure": true } }, { "name": "clangcl-release-dev", "configurePreset": "clangcl-release-dev", "displayName": "Test ClangCL Release (Dev)", "output": { "outputOnFailure": true } } ] } ================================================ FILE: LICENSE_1_0.txt ================================================ Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ # The Art of C++ # https://github.com/taocpp # Copyright (c) 2016-2026 Daniel Frey # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) .SUFFIXES: .SECONDARY: ifeq ($(OS),Windows_NT) UNAME_S := $(OS) ifeq ($(shell gcc -dumpmachine),mingw32) MINGW_CXXFLAGS = -U__STRICT_ANSI__ endif else UNAME_S := $(shell uname -s) endif # For Darwin (Mac OS X / macOS) we assume that the default compiler # clang++ is used; when $(CXX) is some version of g++, then # $(CXXSTD) has to be set to -std=c++20 (or newer) so # that -stdlib=libc++ is not automatically added. ifeq ($(CXXSTD),) CXXSTD := -std=c++20 ifeq ($(UNAME_S),Darwin) CXXSTD += -stdlib=libc++ endif endif # Ensure strict standard compliance and no warnings, can be # changed if desired. BUILDDIR ?= build INCFLAGS ?= -Iinclude -Itest $(patsubst %,-I%,$(shell pg_config --includedir)) CPPFLAGS ?= -pedantic CXXFLAGS ?= -Wall -Wextra -Wshadow -Werror -O3 $(MINGW_CXXFLAGS) LDFLAGS ?= -rdynamic $(patsubst %,-L%,$(shell pg_config --libdir)) LIBS ?= -lpq CLANG_TIDY ?= clang-tidy HEADERS := $(shell find include -name '*.hpp') SOURCES := $(shell find src -name '*.cpp') TESTSOURCES := $(shell find test -name '*.cpp') DEPENDS := $(SOURCES:%.cpp=$(BUILDDIR)/%.d) $(TESTSOURCES:%.cpp=$(BUILDDIR)/%.d) BINARIES := $(SOURCES:%.cpp=$(BUILDDIR)/%) $(TESTSOURCES:%.cpp=$(BUILDDIR)/%) UNIT_TESTS := $(filter $(BUILDDIR)/test/%,$(BINARIES)) LIBSOURCES := $(filter src/lib/%,$(SOURCES)) LIBNAME := taopq .PHONY: all all: check .PHONY: compile compile: $(UNIT_TESTS) .PHONY: check check: $(UNIT_TESTS) @set -e; for T in $(UNIT_TESTS); do echo $$T; $$T; done $(BUILDDIR)/%.clang-tidy: % .clang-tidy $(CLANG_TIDY) -quiet $< -- $(CXXSTD) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) 2>/dev/null @mkdir -p $(@D) @touch $@ .PHONY: clang-tidy clang-tidy: $(HEADERS:%=$(BUILDDIR)/%.clang-tidy) $(SOURCES:%=$(BUILDDIR)/%.clang-tidy) @echo "All $(words $(HEADERS) $(SOURCES)) clang-tidy tests passed." .PHONY: clean clean: @rm -rf $(BUILDDIR)/* @find . -name '*~' -delete $(BUILDDIR)/%.d: %.cpp Makefile @mkdir -p $(@D) $(CXX) $(CXXSTD) $(INCFLAGS) $(CPPFLAGS) -MM -MQ $@ $< -o $@ $(BUILDDIR)/%.o: %.cpp $(BUILDDIR)/%.d $(CXX) $(CXXSTD) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ $(BUILDDIR)/lib/lib$(LIBNAME).a: $(LIBSOURCES:%.cpp=$(BUILDDIR)/%.o) @mkdir -p $(@D) $(AR) -rcs $@ $^ .PHONY: lib lib: $(BUILDDIR)/lib/lib$(LIBNAME).a $(BUILDDIR)/%: $(BUILDDIR)/%.o $(BUILDDIR)/lib/lib$(LIBNAME).a @mkdir -p $(@D) $(CXX) $(CXXSTD) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@ ifeq ($(findstring $(MAKECMDGOALS),clean),) -include $(DEPENDS) endif ================================================ FILE: README.md ================================================ # Welcome to taoPQ [![Windows CI](https://github.com/taocpp/taopq/workflows/Windows/badge.svg)](https://github.com/taocpp/taopq/actions?query=workflow%3AWindows) [![macOS CI](https://github.com/taocpp/taopq/workflows/macOS/badge.svg)](https://github.com/taocpp/taopq/actions?query=workflow%3AmacOS) [![Linux CI](https://github.com/taocpp/taopq/workflows/Linux/badge.svg)](https://github.com/taocpp/taopq/actions?query=workflow%3ALinux)
[![clang-analyze](https://github.com/taocpp/taopq/workflows/clang-analyze/badge.svg)](https://github.com/taocpp/taopq/actions?query=workflow%3Aclang-analyze) [![clang-tidy](https://github.com/taocpp/taopq/workflows/clang-tidy/badge.svg)](https://github.com/taocpp/taopq/actions?query=workflow%3Aclang-tidy) [![Sanitizer](https://github.com/taocpp/taopq/workflows/Sanitizer/badge.svg)](https://github.com/taocpp/taopq/actions?query=workflow%3ASanitizer) [![Codecov](https://codecov.io/gh/taocpp/taopq/graph/badge.svg?token=jrFgbSi3wY)](https://codecov.io/gh/taocpp/taopq) taoPQ is a lightweight C++ client library for accessing a [PostgreSQL➚](https://www.postgresql.org/) database. It has no dependencies beyond [`libpq`➚](https://www.postgresql.org/docs/current/libpq.html), the C application programmer's interface to PostgreSQL. ## Introduction The library provides support for database connections, transactions, nested transactions, prepared statements, large objects, connection pools, pipeline mode, high-speed bulk data transfer, and more. An extensible traits mechanism is used to convert C++ types into SQL statement parameters, and conversely to convert query results into arbitrary C++ types. The following example shows the basic look and feel of the library. ```c++ #include #include int main() { // open a connection to the database const auto conn = tao::pq::connection::create( "dbname=template1" ); // execute statements conn->execute( "DROP TABLE IF EXISTS users" ); conn->execute( "CREATE TABLE users ( name TEXT PRIMARY KEY, age INTEGER NOT NULL )" ); // prepare statements conn->prepare( "insert_user", "INSERT INTO users ( name, age ) VALUES ( $1, $2 )" ); { // begin transaction const auto tr = conn->transaction(); // execute previously prepared statements tr->execute( "insert_user", "Daniel", 42 ); tr->execute( "insert_user", "Tom", 41 ); tr->execute( "insert_user", "Jerry", 29 ); // commit transaction tr->commit(); } // query data const auto users = conn->execute( "SELECT name, age FROM users WHERE age >= $1", 40 ); // iterate and convert results for( const auto& row : users ) { std::cout << row[ "name" ].as< std::string >() << " is " << row[ "age" ].as< unsigned >() << " years old.\n"; } } ``` ## Documentation * [Table of Content](doc/TOC.md) * [Requirements](doc/Requirements.md) * [Installation](doc/Installation.md) * [Getting Started](doc/Getting-Started.md) * [Connection](doc/Connection.md) * [Transaction](doc/Transaction.md) * [Statement](doc/Statement.md) * [Result](doc/Result.md) ## Contact For questions and suggestions regarding taoPQ, success or failure stories, and any other kind of feedback, please feel free to open a [discussion](https://github.com/taocpp/taopq/discussions), an [issue](https://github.com/taocpp/taopq/issues) or a [pull request](https://github.com/taocpp/taopq/pulls) on GitHub or contact the authors at `taocpp(at)icemx.net`. ## The Art of C++ taoPQ is part of [The Art of C++](https://taocpp.github.io/). [colinh](https://github.com/colinh) [d-frey](https://github.com/d-frey) [uilianries](https://github.com/uilianries) ## License Open Source Initiative Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch taoPQ is certified [Open Source➚](http://www.opensource.org/docs/definition.html) software. It is [licensed➚](https://pdimov.github.io/blog/2020/09/06/why-use-the-boost-license/) under the terms of the [Boost Software License, Version 1.0➚](https://www.boost.org/LICENSE_1_0.txt) reproduced here. > Boost Software License - Version 1.0 - August 17th, 2003 > > Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: > > The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [The Art of C++]: https://taocpp.github.io/ ================================================ FILE: doc/Aggregate-Support.md ================================================ # Aggregate Support taoPQ allows the direct use of "simple" aggregates as parameters and result types. ## Requirements An aggregate data type `T` suitable for taoPQ support must meet the following requirements: * [`std::is_aggregate_v< T >`➚](https://en.cppreference.com/w/cpp/types/is_aggregate) must yield `true`. * [`std::is_empty_v< T >`➚](https://en.cppreference.com/w/cpp/types/is_empty) must yield `false`. * [`std::is_union_v< T >`➚](https://en.cppreference.com/w/cpp/types/is_union) must yield `false`. * `T` must not have any base classes. * `T` must not have more than 99 member variables. (this limit can be raised, open an [issue](https://github.com/taocpp/taopq/issues) if necessary) ## Registration We currently require explicit registration of aggregate data types. This is done as a precaution, and to avoid conflicts with other data types. A data type `T` can be registered as both a parameter and a result type by specializing `tao::pq::is_aggregate` as follows: ```c++ template<> inline constexpr bool tao::pq::is_aggregate< T > = true; ``` If necessary, you can control independently whether a data type is suitable as a parameter or a result type by specializing `tao::pq::is_aggregate_parameter` and `tao::pq::is_aggregate_result` as follows: ```c++ template<> inline constexpr bool tao::pq::is_aggregate_parameter< T > = true; template<> inline constexpr bool tao::pq::is_aggregate_result< T > = true; ``` ## Direct Result Conversion An aggregate data type `T` that is registered as a result type enables the direct conversion of a `tao::pq::row` to `T`. This is convenient when retrieving results and iterating over them, it enables you to write: ```c++ const auto result = conn->execute( "SELECT ... FROM ..." ); // some query for( const T t : result ) { // use t } ``` :interrobang: Compilers might complain about extra copies and that you should use `const T&`, but that would not work and there are no extra copies. ## Example The following is a short, but complete example of how to use aggregates with taoPQ: ```c++ #include #include // an aggregate struct user { std::string name; unsigned age; std::string planet; }; template<> inline constexpr bool tao::pq::is_aggregate< user > = true; int main() { // open a connection to the database const auto conn = tao::pq::connection::create( "dbname=template1" ); // execute statements conn->execute( "DROP TABLE IF EXISTS users" ); conn->execute( "CREATE TABLE users ( name TEXT PRIMARY KEY, age INTEGER NOT NULL, planet TEXT NOT NULL )" ); // prepare statements conn->prepare( "insert_user", "INSERT INTO users ( name, age, planet ) VALUES ( $1, $2, $3 )" ); // execute previously prepared statements conn->execute( "insert_user", user{ "R. Daneel Olivaw", 19230, "Earth" } ); conn->execute( "insert_user", user{ "R. Giskard Reventlov", 42, "Aurora" } ); // query and convert data for( const user u : conn->execute( "SELECT name, age, planet FROM users" ) ) { std::cout << u.name << " from " << u.planet << " was " << u.age << " years old.\n"; } } ``` --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Binary-Data.md ================================================ # Binary Data PostgreSQL stores binary data either as a field with the [`BYTEA`➚](https://www.postgresql.org/docs/current/datatype-binary.html) data type or as a [large object➚](https://www.postgresql.org/docs/current/largeobjects.html). Large Objects in taoPQ have their own representation discussed in the [Large Object](Large-Object.md) chapter. ## The [`BYTEA`➚](https://www.postgresql.org/docs/current/datatype-binary.html) Data Type In PostgreSQL strings are a sequence of bytes that is valid in a given encoding. One or more bytes can represent individual characters or code points. This means that not all sequences of bytes are a valid string and therefore binary data can generally not be represented by PostgreSQL as a string. Binary data is different from strings, as binary data is a collection of an arbitrary sequence of bytes. Any byte is treated independently of its surrounding bytes and can have any value, including '\0'. It is therefore crucial to represent binary data with the dedicated [`BYTEA`➚](https://www.postgresql.org/docs/current/datatype-binary.html) data type. ## C++ Binary Data The individual bytes are represented by [`std::byte`➚](https://en.cppreference.com/w/cpp/types/byte). As there is no one-size-fits-all data type to handle binary data in C++, we allow several options. We mostly represent binary data with `tao::pq::binary` and `tao::pq::binary_view`. These are type aliases for [`std::vector`➚](https://en.cppreference.com/w/cpp/container/vector) and [`std::span`➚](https://en.cppreference.com/w/cpp/container/span), respectively. ## Passing Binary Data When you pass binary data to taoPQ, we only require a view to be passed. As a view is a non-owning data type, constructing an instance of it is cheap. If you have other data types, you can create a binary data view by using ```c++ auto tao::pq::to_binary_view( const auto* data, const std::size_t size ) noexcept -> tao::pq::binary_view; auto tao::pq::to_binary_view( const auto& data ) noexcept { return pq::to_binary_view( std::data( data ), std::size( data ) ); } ``` The former function requires (and checks) that `T` has a size of 1 byte. If you want to store larger `T`s as binary data you need to manually convert the pointer and size appropriately. The second method requires the data type `T` to be a suitable candidate for [`std::data()`➚](https://en.cppreference.com/w/cpp/iterator/data) and [`std::size()`➚](https://en.cppreference.com/w/cpp/iterator/size), which requires the data to be stored in a contiguous block of memory. We do not offer any convenience methods to create binary data from distributed data structures, i.e. `std::list` is not supported. ## Receiving Binary Data When receiving binary data, a non-owning view is insufficient, hence we return `tao::pq::binary`. In some cases other alternatives are offered, i.e. you may provide a buffer that the data is written to. --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Bulk-Transfer.md ================================================ # Bulk Transfer **TODO** ## Synopsis Don't be intimidated by the size of the API, as you can see several methods are just single-line convenience forwarders. We will first give the synopsis of everything, afterwards we will break down the API into small logical portions. ```c++ namespace tao::pq { namespace internal { class zsv; // zero-terminated string view } class transaction; class table_writer final { public: template< typename... As > table_writer( const std::shared_ptr< transaction >& transaction, const internal::zsv statement, As&&... as ); ~table_writer(); table_writer( const table_writer& ) = delete; table_writer( table_writer&& ) = delete; void operator=( const table_writer& ) = delete; void operator=( table_writer&& ) = delete; void insert_raw( const std::string_view data ); template< typename... As > void insert( As&&... as ); auto commit() -> std::size_t; }; using null_t = decltype( null ); class table_row; class table_field; class table_reader final { private: class const_iterator; public: template< typename... As > table_reader( const std::shared_ptr< transaction >& transaction, const internal::zsv statement, As&&... as ); ~table_reader() = default; table_reader( const table_reader& ) = delete; table_reader( table_reader&& ) = delete; void operator=( const table_reader& ) = delete; void operator=( table_reader&& ) = delete; auto columns() const noexcept -> std::size_t; auto get_raw_data() -> std::string_view; bool parse_data() noexcept; bool get_row(); bool has_data() const noexcept; auto raw_data() const noexcept -> const std::vector< const char* >&; auto row() noexcept -> table_row; auto begin() -> const_iterator; auto end() noexcept -> const_iterator; auto cbegin() -> const_iterator; auto cend() noexcept -> const_iterator; template< typename T > auto as_container() -> T; // convenience conversions to standard containers template< typename... Ts > auto vector() { return as_container< std::vector< Ts... > >(); } template< typename... Ts > auto list() { return as_container< std::list< Ts... > >(); } template< typename... Ts > auto set() { return as_container< std::set< Ts... > >(); } template< typename... Ts > auto multiset() { return as_container< std::multiset< Ts... > >(); } template< typename... Ts > auto unordered_set() { return as_container< std::unordered_set< Ts... > >(); } template< typename... Ts > auto unordered_multiset() { return as_container< std::unordered_multiset< Ts... > >(); } template< typename... Ts > auto map() { return as_container< std::map< Ts... > >(); } template< typename... Ts > auto multimap() { return as_container< std::multimap< Ts... > >(); } template< typename... Ts > auto unordered_map() { return as_container< std::unordered_map< Ts... > >(); } template< typename... Ts > auto unordered_multimap() { return as_container< std::unordered_multimap< Ts... > >(); } }; class table_row { private: // satisfies LegacyRandomAccessIterator, see // https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator class const_iterator; public: auto slice( const std::size_t offset, const std::size_t in_columns ) const -> table_row; auto columns() const noexcept -> std::size_t; // iteration auto begin() const -> const_iterator; auto end() const -> const_iterator; auto cbegin() const -> const_iterator; auto cend() const -> const_iterator; bool is_null( const std::size_t column ) const; auto get( const std::size_t column ) const -> const char*; template< typename T > auto get( const std::size_t column ) const -> T; template< typename T > auto optional( const std::size_t column ) const { return get< std::optional< T > >( column ); } template< typename T > auto as() const -> T; template< typename T > auto optional() const { return as< std::optional< T > >(); } template< typename T, typename U > auto pair() const { return as< std::pair< T, U > >(); } template< typename... Ts > auto tuple() const { return as< std::tuple< Ts... > >(); } auto at( const std::size_t column ) const -> table_field; auto operator[]( const std::size_t column ) const noexcept -> table_field; friend void swap( table_row& lhs, table_row& rhs ) noexcept; }; class table_field { public: auto index() const -> std::size_t; bool is_null() const; auto get() const -> const char*; template< typename T > auto as() const -> T; template< typename T > auto optional() const { return as< std::optional< T > >(); } }; bool operator==( const table_field& f, null_t ) { return f.is_null(); } bool operator==( null_t, const table_field& f ) { return f.is_null(); } bool operator!=( const table_field& f, null_t ) { return !f.is_null(); } bool operator!=( null_t, const table_field& f ) { return !f.is_null(); } } ``` **TODO** --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Connection-Pool.md ================================================ # Connection Pool Opening a new connection to the database server typically consists of several time-consuming steps. A physical channel such as a socket or a named pipe must be established, the initial handshake with the server must occur, if encryption protocols (SSL) are enabled those need to be established, the connection string information must be parsed, the connection must be authenticated by the server, checks must be run for enlisting in the current transactions, and so on. In practice, most applications use only one or a few different configurations for connections. This means that during application execution, many identical connections will be repeatedly opened and closed. To minimize the cost of opening connections, taoPQ provides connection pools. Connection pools reduces the number of times that new connections must be opened. They manage connections by keeping alive a set of active connections and borrowing them to the application on demand. The connection pool maintains ownership of the connections when they are not used by the application. ## Synopsis ```c++ namespace tao::pq { namespace internal { class zsv; // zero-terminated string view } namespace poll { enum class status { timeout, readable, writable, again }; using callback = status( const int socket, const bool wait_for_write, const int timeout_ms ); } class connection; class connection_pool final : public std::enable_shared_from_this< connection_pool > { public: // create a new connection pool static auto create( const std::string& connection_info, std::function< tao::pq::poll::callback > poll_cb = /*unspecified*/ ) -> std::shared_ptr< connection_pool >; // non-copyable, non-movable connection_pool( const connection_pool& ) = delete; connection_pool( connection_pool&& ) = delete; void operator=( const connection_pool& ) = delete; void operator=( connection_pool&& ) = delete; virtual ~connection_pool() = default; // timeout handling auto timeout() const noexcept -> const std::optional< std::chrono::milliseconds >&; void set_timeout( const std::chrono::milliseconds timeout ); void reset_timeout() noexcept; // customizable poll()-callback auto poll_callback() const noexcept -> const std::function< tao::pq::poll::callback >&; void set_poll_callback( std::function< tao::pq::poll::callback > poll_cb ) noexcept; void reset_poll_callback(); // borrow a connection auto connection() const noexcept -> std::shared_ptr< pq::connection >; // direct statement execution template< typename... As > auto execute( const internal::zsv statement, As&&... as ) { return connection()->execute( statement, std::forward< As >( as )... ); } // checks whether the pool contains idle connections auto empty() const noexcept -> bool; // number of idle connections auto size() const noexcept -> std::size_t; // number of borrowed connections auto attached() const noexcept -> std::size_t; // cleanup void erase_invalid(); }; } ``` :point_up: Note that `tao::pq::internal::zsv` is explained in the [Statement](Statement.md) chapter. ## Creating Connection Pools A connection pool is created by calling `tao::pq::connection_pool`'s static `create()`-method. ```c++ auto tao::pq::connection_pool::create( const std::string& connection_info ) -> std::shared_ptr< tao::pq::connection_pool >; ``` It takes a mandatory parameter, the [connection string➚](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING), that is used when new connections are opened by the pool. The second, optional parameter can be used to specify the default `poll()`-callback for connections, see [Customizable `poll()`-callback](Connection.md#customizable-poll-callback). ## Borrowing Connections When you need a connection, you simply call the `connection()`-method. ```c++ auto tao::pq::connection_pool::connection() -> std::shared_ptr< tao::pq::connection >; ``` This will either open a new connection when the pool is empty, or it will give you a reused connection from the pool. As long as you retain ownership of the returned shared pointer, it is yours to work with. When the last remaining shared pointer is destroyed or assigned another value, the connection is returned to the pool. ## Executing Statements You can [execute statements](Statement.md) on a connection pool directly, which is equivalent to borrowing a temporary connection (as if calling the `connection()`-method) and executing the statement on that [connection](Connection.md). After the statement was executed, the temporary connection is returned to the pool. ## Cleanup The connection pool will implicitly discard connections that are in a failed state when they are returned to the pool or when they are retrieved from the pool. In some environments you might need to periodically clean up the connection pool to get rid of connections that are no longer valid. In order to do so, just call the `erase_invalid()`-method, which will check the status of each pooled connection and discard the invalid ones. ```c++ void tao::pq::connection_pool::erase_invalid(); ``` ## Customizable `poll()`-callback The default implementation for polling uses `poll()` or `WSAPoll()`, depending on your system. This callback can be customized to support other I/O frameworks, e.g. Boost.Asio. To access the current default callback for borrowed connections you can call the `poll_callback()`-method. ```c++ auto poll_callback() const noexcept -> const std::function< tao::pq::poll::callback >&; ``` Setting the default `poll()`-callback for borrowed connections is done by calling the `set_poll_callback()`-method. ```c++ void set_poll_callback( std::function< tao::pq::poll::callback > poll_cb ) noexcept; ``` You can revert the current default `poll()`-callback for borrowed connections to the default by calling the `reset_poll_callback()`-method. ```c++ void reset_poll_callback(); ``` ## Thread Safety The connection pool's borrowing mechanism is thread-safe, i.e. multiple threads can make calls to the `connection()`-method or return connections simultaneously. You can also call the `erase_invalid()`-method at any time. Internally, the connection pool uses a [mutex➚](https://en.cppreference.com/w/cpp/thread/mutex) to serialize the above operations. We minimized the work in the [critical sections➚](https://en.wikipedia.org/wiki/Critical_section) as far as possible. --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Connection.md ================================================ # Connection All communication with a database server is handled through a connection, represented by the `tao::pq::connection` type in taoPQ. A connection object takes care of tracking [transactions](Transaction.md), [error handling](Error-Handling.md), and it has its own set of prepared statements. ## Synopsis ```c++ namespace tao::pq { namespace internal { class zsv; // zero-terminated string view } namespace poll { enum class status { timeout, readable, writable, again }; using callback = status( const int socket, const bool wait_for_write, const int timeout_ms ); } enum class isolation_level { default_isolation_level, serializable, repeatable_read, read_committed, read_uncommitted }; enum class access_mode { default_access_mode, read_write, read_only }; class notification final { public: auto channel() const noexcept -> const char*; auto payload() const noexcept -> const char*; auto underlying_raw_ptr() noexcept -> PGnotify*; auto underlying_raw_ptr() const noexcept -> const PGnotify*; }; class transaction; class connection final : public std::enable_shared_from_this< connection > { public: // create a new connection static auto create( const std::string& connection_info, std::function< tao::pq::poll::callback > poll_cb = /*unspecified*/ ) -> std::shared_ptr< connection >; // non-copyable, non-movable connection( const connection& ) = delete; connection( connection&& ) = delete; void operator=( const connection& ) = delete; void operator=( connection&& ) = delete; ~connection() = default; // query status bool is_open() const noexcept; bool is_idle() const noexcept; // create transactions auto direct() -> std::shared_ptr< pq::transaction >; auto transaction() -> std::shared_ptr< pq::transaction >; auto transaction( const access_mode am, const isolation_level il = isolation_level::default_isolation_level ) -> std::shared_ptr< pq::transaction >; auto transaction( const isolation_level il, const access_mode am = access_mode::default_access_mode ) -> std::shared_ptr< pq::transaction >; // timeout handling auto timeout() const noexcept -> const std::optional< std::chrono::milliseconds >&; void set_timeout( const std::chrono::milliseconds timeout ); void reset_timeout() noexcept; // prepared statements void prepare( const std::string& name, const std::string& statement ); void deallocate( const std::string& name ); // direct statement execution template< typename... As > auto execute( const internal::zsv statement, As&&... as ) { return direct()->execute( statement, std::forward< As >( as )... ); } // listen/notify support void listen( const std::string_view channel ); void listen( const std::string_view channel, const std::function< void( const char* ) >& handler ); void unlisten( const std::string_view channel ); void notify( const std::string_view channel ); void notify( const std::string_view channel, const std::string_view payload ); auto notification_handler() -> std::function< void( const notification& ) >; void set_notification_handler( const std::function< void( const notification& ) >& handler ); void reset_notification_handler() noexcept; auto notification_handler( const std::string_view channel ) -> std::function< void( const char* ) >; void set_notification_handler( const std::string_view channel, const std::function< void( const char* ) >& handler ); void reset_notification_handler( const std::string_view channel ) noexcept; void handle_notifications(); void get_notifications(); // customizable poll()-callback auto poll_callback() const noexcept -> const std::function< tao::pq::poll::callback >&; void set_poll_callback( std::function< tao::pq::poll::callback > poll_cb ) noexcept; void reset_poll_callback(); // access underlying connection pointer from libpq auto underlying_raw_ptr() noexcept -> PGconn*; auto underlying_raw_ptr() const noexcept -> const PGconn*; // access the socket used by libpq auto socket() const -> int; // error message auto error_message() const -> std::string; }; } ``` :point_up: Note that `tao::pq::internal::zsv` is explained in the [Statement](Statement.md) chapter. ## Creating a Connection A connection is created by calling `tao::pq::connection`'s static `create()`-method. ```c++ auto tao::pq::connection::create( const std::string& connection_info, std::function< tao::pq::poll::callback > poll_cb = /*unspecified*/ ) -> std::shared_ptr< tao::pq::connection >; ``` It takes a mandatory parameter, the [connection string➚](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING), that is passed to the underlying `libpq` for opening the database connection. The connection string contains parameters and options, such as the server address or the database name. Connection parameters that are not specified in the connection string might also be set via [environment variables➚](https://www.postgresql.org/docs/current/libpq-envars.html). The second, optional parameter can be used to specify a `poll()`-callback, see [Customizable `poll()`-callback](#customizable-poll-callback). The method returns a `std::shared_ptr` or, in case of an error, throws an exception. When the last reference to a connection is deleted, i.e. the last shared pointer referencing it is deleted or reset, the connection is closed via its destructor which takes care of freeing underlying resources. The shared pointer might also be stored internally in other objects of taoPQ, i.e. a transaction. This ensures, that the connection is kept alive as long as there are dependent objects like an active transaction, see below. ## Creating Transactions You can create [transactions](Transaction.md) by calling either the `direct()`-method or the `transaction()`-method. Both methods register the newly created transaction as the active transaction of a connection. A connection can only have one active transaction at any given time. Further details on how to use transactions are discussed in the [Transaction](Transaction.md) chapter. ### Creating a "Direct" Transaction The `direct()`-method creates an auto-commit transaction proxy, i.e. all statements executed on this transaction are immediately committed to the database. ```c++ auto tao::pq::connection::direct() -> std::shared_ptr< tao::pq::transaction >; ``` :point_up: This is not a real transaction from the database's point of view, therefore calling the `commit()`- or `rollback()`-method on the transaction has no immediate effect on the database. However, calling either the `commit()`- or `rollback()`-method will end the transaction's logical lifetime and it will unregister itself from the connection. ### Creating a Database Transaction The `transaction()`-method begins a real [database transaction➚](https://www.postgresql.org/docs/current/tutorial-transactions.html). ```c++ auto tao::pq::connection::transaction() -> std::shared_ptr< tao::pq::transaction >; auto tao::pq::connection::transaction( const tao::pq::isolation_level il, const tao::pq::access_mode am = tao::pq::access_mode::default_access_mode ) -> std::shared_ptr< tao::pq::transaction >; auto tao::pq::connection::transaction( const tao::pq::access_mode am, const tao::pq::isolation_level il = tao::pq::isolation_level::default_isolation_level ) -> std::shared_ptr< tao::pq::transaction >; ``` You may specify two optional parameters, the [isolation level➚](https://www.postgresql.org/docs/current/transaction-iso.html) and the [access mode➚](https://www.postgresql.org/docs/current/sql-set-transaction.html). When `tao::pq::isolation_level::default_isolation_level` or `tao::pq::access_mode::default_access_mode` are used the transaction inherits its isolation level or access mode from the session, as described in the [PostgreSQL documentation➚](https://www.postgresql.org/docs/current/sql-set-transaction.html). ## Executing Statements You can [execute statements](Statement.md) on a connection object directly, which is equivalent to creating a temporary direct transaction (as if calling the `direct()`-method) and executing the statement on that [transaction](Transaction.md). ## Prepared Statements Prepared statements only last for the duration of a connection, and are bound to a connection, i.e. the set of prepared statements is independent for each connection. You can [prepare➚](https://www.postgresql.org/docs/current/sql-prepare.html) a statement by calling the `prepare()`-method. ```c++ void tao::pq::connection::prepare( const std::string& name, const std::string& statement ); ``` It takes two parameters, the name of the prepared statement and the SQL statement itself. taoPQ limits the name to classic C-style identifiers, i.e. a non-empty sequence of digits, underscores, and lowercase and uppercase Latin letters. A valid identifier must begin with a non-digit character. Identifiers are case-sensitive (lowercase and uppercase letters are distinct). A previously prepared statement can be [deallocated➚](https://www.postgresql.org/docs/current/sql-deallocate.html), although this is rare in pratice. To deallocate a prepared statement, call the `deallocate()`-method. ```c++ void tao::pq::connection::deallocate( const std::string& name ); ``` Using the `prepare()`- and `deallocate()`-methods makes taoPQ's connection object aware of the names of the prepared statements. This allows the [execution](Statement.md) of those prepared statements transparently via an `execute()`-method. ### Manually Prepared Statements You can manually prepare statements by executing [`PREPARE`➚](https://www.postgresql.org/docs/current/sql-prepare.html) statements directly via an `execute()`-method. While those prepared statements live on the same connection, there are some important differences. You can only execute those prepared statements by executing [`EXECUTE`➚](https://www.postgresql.org/docs/current/sql-execute.html) statements directly via an `execute()`-method, and you can only deallocate them by executing [`DEALLOCATE`➚](https://www.postgresql.org/docs/current/sql-deallocate.html) statements directly via an `execute()`-method. :point_up: We advise to use the methods offered by taoPQ's connection type. ## Checking Status You can check a connection's status by calling the `is_open()`- or `is_idle()`-methods. ```c++ bool tao::pq::connection::is_open() const noexcept; bool tao::pq::connection::is_idle() const noexcept; ``` The first method returns `true` when the connection is still open and usable, and `false` otherwise, i.e. if the connection is in a failed state. For further details, check the documentation for the underlying [`PQstatus()`➚](https://www.postgresql.org/docs/current/libpq-status.html)-function provided by `libpq`. The second method returns `true` when the connection is open and is in the idle state, and `false` otherwise. For further details, check the documentation for the underlying [`PQtransactionStatus()`➚](https://www.postgresql.org/docs/current/libpq-status.html)-function provided by `libpq`. ## Notification Framework PostgreSQL provides a simple [interprocess communication mechanism➚](https://www.postgresql.org/docs/current/sql-notify.html) for a collection of applications accessing the same database. ### Sending Messages You can send events with the `notify()`-method, providing a channel name and optionally a payload as the second parameter. ```c++ void tao::pq::connection::notify( const std::string_view channel ); void tao::pq::connection::notify( const std::string_view channel, const std::string_view payload ); ``` :point_up: The channel name is case sensitive when using taoPQ's methods. ### Receiving Messages You can subscribe to channels to receive messages using the `listen()`-method, or unsubscribe by calling the `unlisten()`-method. ```c++ void tao::pq::connection::listen( const std::string_view channel ); void tao::pq::connection::unlisten( const std::string_view channel ); ``` Note that subscriptions are per connection. ### Handling Messages Processing received messages requires you to register a notification handler. Each connection has its own notification handler. The notification handler is managed by a `std::function< void( const tao::pq::notification& >` object. The currently active notification handler is returned by the `notification_handler()`-method. ```c++ auto tao::pq::connection::notification_handler() -> std::function< void( const tao::pq::notification& ) >; ``` If no notification handler is set, the [`std::function`➚](https://en.cppreference.com/w/cpp/utility/functional/function) will be empty. Setting a notification handler is done by calling the `set_notification_handler()`-method. ```c++ void tao::pq::connection::set_notification_handler( const std::function< void( const tao::pq::notification& ) >& handler ); ``` If you want to deregister the current notification handler, you can call the `reset_notification_handler()`-method. ```c++ void tao::pq::connection::reset_notification_handler() noexcept; ``` ### Per Channel Handlers Besides the above general notification handler, there is also the option to register a per channel handler. Per channel handlers only receive the payload as a parameter. ```c++ auto tao::pq::connection::notification_handler( const std::string_view channel ) -> std::function< void( const char* ) >; void tao::pq::connection::set_notification_handler( const std::string_view channel, const std::function< void( const char* ) >& handler ); void tao::pq::connection::reset_notification_handler( const std::string_view channel ) noexcept; ``` When you subscribe to a channel with the `listen()`-method, you can optionally register a channel handler. ```c++ void tao::pq::connection::listen( const std::string_view channel, const std::function< void( const char* ) >& handler ); ``` This registers the handler first by calling `set_notification_handler( channel, handler )`, then calls `listen( channel )`. ### Asynchronous Notifications taoPQ calls the registered notification handler(s) after successful execution by calling the `handle_notifications()`-method. As a user, you rarely need to call the `handle_notifications()`-method manually. ```c++ void tao::pq::connection::handle_notifications(); ``` When you don't have any statement to execute, you can call the `get_notifications()`-method which will actively query the server for new events. ```c++ void tao::pq::connection::get_notifications(); ``` ### Event Loop **TODO** Support event loops? How? ## Customizable `poll()`-callback The default implementation for polling uses `poll()` or `WSAPoll()`, depending on your system. This callback can be customized to support other I/O frameworks, e.g. Boost.Asio. To access the currently active callback you can call the `poll_callback()`-method. ```c++ auto poll_callback() const noexcept -> const std::function< tao::pq::poll::callback >&; ``` Setting the `poll()`-callback is done by calling the `set_poll_callback()`-method. ```c++ void set_poll_callback( std::function< tao::pq::poll::callback > poll_cb ) noexcept; ``` You can revert the current `poll()`-callback to the default by calling the `reset_poll_callback()`-method. ```c++ void reset_poll_callback(); ``` ## Underlying Connection Pointer If you need to access the underlying raw connection pointer from `libpq`, you can call the `underlying_raw_ptr()`-method. ```c++ auto tao::pq::connection::underlying_raw_ptr() noexcept -> PGconn*; auto tao::pq::connection::underlying_raw_ptr() const noexcept -> const PGconn*; ``` ## Error Messages You can retrieve the last error message (if applicable) by calling the `error_message()`-method. ```c++ auto tao::pq::connection::error_message() const -> std::string; ``` When taoPQ throws an exception this is usually done internally and the message is part of the exception's `what()` message. --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Error-Handling.md ================================================ # Error Handling ## SQL errors When an SQL statement is [executed](Statement.md) and the execution fails, an exception is thrown. The base class for the exceptions thrown in this case is `tao::pq::sql_error`, which is derived from `tao::pq::error`, which in turn is derived from [`std::runtime_error`➚](https://en.cppreference.com/w/cpp/error/runtime_error). ```c++ namespace tao::pq { struct error : std::runtime_error { using std::runtime_error::runtime_error; }; struct sql_error : error { std::string sqlstate; // ctor... }; } ``` The exception's `what()`-method will return the error message returned from the server. ## SQLSTATE Depending on the [SQLSTATE➚](https://en.wikipedia.org/wiki/SQLSTATE) returned from the server as documented in [Appendix A➚](https://www.postgresql.org/docs/current/errcodes-appendix.html) of the PostgreSQL documentation, we throw an accordingly named exception class. ### Class of Error The first two characters of the error code denote the class of errors, while the last three characters indicate a specific condition within that class. Thus, an application that does not recognize the specific error code might still be able to infer what to do from the error class. For each class of errors, there is an exception class derived from `tao::pq::sql_error`. As an example, if the class is "02" ("no data"), the exception class that is thrown is either derived from `tao::pq::no_data` if a more specific error condition is recognized, or `tao::pq::no_data` itself will be the exception class that is thrown if only the class itself is recognized by taoPQ. ### Specific Error Conditions If a specific error condition is recognized, an exception named after [Appendix A➚](https://www.postgresql.org/docs/current/errcodes-appendix.html) will be thrown, derived from the exception class of the class of error. There are some cases in which the name can not be simply taken from that table, as they are used multiple times in different classes of errors. In those cases the exception class is a class template and you need to add the class of error as a template parameter. Specifically, this is necessary to distinguish the following exceptions: * `tao::pq::string_data_right_truncation< tao::pq::warning >` (SQLSTATE "01004") * `tao::pq::string_data_right_truncation< tao::pq::data_exception >` (SQLSTATE "22001") and these exceptions from class "sql routine exception": * `tao::pq::modifying_sql_data_not_permitted< tao::pq::sql_routine_exception >` (SQLSTATE "2F002") * `tao::pq::prohibited_sql_statement_attempted< tao::pq::sql_routine_exception >` (SQLSTATE "2F003") * `tao::pq::reading_sql_data_not_permitted< tao::pq::sql_routine_exception >` (SQLSTATE "2F004") vs these exceptions from class "external routine exception": * `tao::pq::modifying_sql_data_not_permitted< tao::pq::external_routine_exception >` (SQLSTATE "38002") * `tao::pq::prohibited_sql_statement_attempted< tao::pq::external_routine_exception >` (SQLSTATE "38003") * `tao::pq::reading_sql_data_not_permitted< tao::pq::external_routine_exception >` (SQLSTATE "38004") ## Connection Errors PostgreSQL only delivers an SQLSTATE when a statement is executed. In other situations, e.g. when opening a connection fails, no SQLSTATE is available. We throw an exception of type `tao::pq::connection_error` in that case, with a dummy SQLSTATE of "08000". The same exception can also be thrown when calling the connection's `get_notifications()`-method when the connection is broken. ## Other Exceptions **TODO** --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Getting-Started.md ================================================ # Getting Started Before we start with taoPQ, we'd like to point you to the excellent [PostgreSQL documentation➚](https://www.postgresql.org/docs/current/index.html). We will assume that you are familiar with PostgreSQL and SQL in general, so we will *not* explain what a certain SQL statement does in the database. Getting started with taoPQ is really simple, a minimalistic program looks as follows: ```c++ #include #include #include int main() { const auto connection = tao::pq::connection::create( "dbname=template1" ); const auto result = connection->execute( "SELECT version()" ); std::cout << result.as< std::string >() << std::endl; return 0; } ``` Let's go through the above code and explain some basic principles, slowly expanding our knowledge of taoPQ. To use taoPQ, you include the top-level header with `#include `. The individual include files in `tao/pq/` are not meant to be included directly. Starting with `tao::pq::connection::create("dbname=template1")`, we can see that the `tao::pq::connection` class has a static `create()`-method. You provide a single parameter, the [connection string➚](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING), and the method then returns a `std::shared_ptr` that holds the connection object it opened. The reason why connections (and several other objects in taoPQ) are handled via smart pointers will be explained later. Note that you can use `auto` as shown above to simplify your code. A connection has an `execute()`-method which can be used to run SQL statements. The `execute()`-method returns a result object directly, meaning `tao::pq::result` objects are not handled via smart pointers. As the statement we just executed was a `SELECT` statement, the result contains a set of rows containing the data returned from the `SELECT` statement. The above shows a simple way of converting a result set that contains only a single row with a single column into a C++ `std::string`. Of course there are other, more sophisticated ways to retrieve and convert result data when you received multiple rows with multiple columns, those will be shown later. Congratulations, you now can write simple programs with taoPQ. ## Next Steps The following chapters are good next steps to get to know taoPQ: * The [Connection](Connection.md) chapter goes into more details on the methods offered by `tao::pq::connection`, introduces transactions, and explains why some objects in taoPQ are handled via smart pointers. * The [Transaction](Transaction.md) chapter explains in more detail how taoPQ handles transactions, makes sure that you don't mess up the transaction ordering and nesting, and which transaction types are supported. * The [Statement](Statement.md) chapter gives more information on how to send statements and parameters. * The [Result](Result.md) chapter explains what types of results exist, how you can access the data they contain, and how to convert results into C++ types, including containers. * The [Installation](Installation.md) chapter explains how to install taoPQ. * The [Requirements](Requirements.md) chapter lists our requirements and assumptions about the used server and protocol versions, encoding support, etc. ## Advanced Topics * The [Error Handling](Error-Handling.md) chapter gives some general hints as to how we manage error scenarios and how those are communicated to the application. * The [Parameter Type Conversion](Parameter-Type-Conversion.md) chapter explains what C++ data types can be used as parameters when executing SQL statements, how NULL values are mapped to C++ data types, and how you can extend the supported types by registering your own types. * The [Result Type Conversion](Result-Type-Conversion.md) chapter explains what C++ data types can be extracted from results, how you can extend the supported types by registering your own types, and how to use `tao::pq::result`'s API elegantly and efficiently. * The [Binary Data](Binary-Data.md) chapter explains the support for PostgreSQL's [`BYTEA`➚](https://www.postgresql.org/docs/current/datatype-binary.html) data type in taoPQ and some design decisions regarding the C++ interface. * The [Connection Pool](Connection-Pool.md) chapter explains how a connection pool might help your application, especially when you use multi-threading. Our connection pool offers some novel features that ease the handling of borrowed connections significantly. * The [Bulk Transfer](Bulk-Transfer.md) chapter explains how we support high-speed [bulk data transfer➚](https://www.postgresql.org/docs/current/sql-copy.html) to or from the server. * The [Large Object](Large-Object.md) chapter provides access to PostgreSQL's [large object➚](https://www.postgresql.org/docs/current/largeobjects.html) facility. * The [Performance](Performance.md) chapter gives hints on how to improve your application's performance, as well as explaining some gotchas you might encounter when using taoPQ. --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Installation.md ================================================ # Installation ## Using CMake Since CMake 3.11, the feature [FetchContent➚](https://cmake.org/cmake/help/latest/module/FetchContent.html) can be used to download and build project dependencies. This mechanism makes our development much easier, but it lacks in terms of reproducibility, so be careful if you are using it for production. Also, we will use `FetchContent_MakeAvailable` which is available since CMake 3.14: ```cmake cmake_minimum_required(VERSION 3.14) project(example CXX) include(FetchContent) find_package(PostgreSQL REQUIRED) FetchContent_Declare( taocpp-taopq GIT_REPOSITORY https://github.com/taocpp/taopq GIT_TAG main ) FetchContent_MakeAvailable(taocpp-taopq) add_library(example main.cpp) target_link_libraries(example taocpp::taopq) set_property(TARGET example PROPERTY CXX_STANDARD 20) ``` Now, we just need to execute CMake as usual: ```sh cmake . cmake --build . ``` The CMake client will download taoPQ source files based on the `main` branch, but is highly recommended using a commit or tag to keep the reproducibility. Besides that, PostgreSQL (libpq) is a pre-requirement. You can extend the `CMakeLists.txt` to download and build libpq too, or just consume from your system. When executing the build step, taoPQ will be built first, as its target is required by our application, after that, the example application will be built and linked to both libpq and taoPQ. --- ## Using Conan You can install pre-built binaries for taoPQ or build it from source using [Conan](https://conan.io/). Use the following command: ```bash conan install --requires="taocpp-taopq/[*]" --build=missing ``` The taoPQ Conan recipe is kept up to date by Conan maintainers and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/conan-io/conan-center-index) on the ConanCenterIndex repository. This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Large-Object.md ================================================ # Large Object PostgreSQL has a [large object➚](https://www.postgresql.org/docs/current/largeobjects.html) facility, which provides stream-style access to user data that is stored in a special large-object structure. Streaming access is useful when working with data values that are too large to manipulate conveniently as a whole. ## Synopsis ```c++ namespace tao::pq { enum class oid : Oid // Oid defined by libpq { invalid = InvalidOid, // InvalidOid defined by libpq // undisclosed additional values }; class transaction; class large_object final { public: static auto create( const std::shared_ptr< transaction >& transaction, const oid desired_id = oid::invalid ) -> oid; static void remove( const std::shared_ptr< transaction >& transaction, const oid id ); static auto import_file( const std::shared_ptr< transaction >& transaction, const char* filename, const oid desired_id = oid::invalid ) -> oid; static void export_file( const std::shared_ptr< transaction >& transaction, const oid id, const char* filename ); large_object( const std::shared_ptr< transaction >& transaction, const oid id, const std::ios_base::openmode m ); large_object( const large_object& ) = delete; large_object( large_object&& other ) noexcept; ~large_object(); void operator=( const large_object& ) = delete; auto operator=( large_object&& rhs ) -> large_object&; void close(); auto read( char* data, const std::size_t size ) -> std::size_t; auto read( std::byte* data, const std::size_t size ) -> std::size_t; void write( const char* data, const std::size_t size ); void write( const std::byte* data, const std::size_t size ); void write( const char* data ); template< typename T = binary > auto read( const std::size_t size ) -> T; template< typename... Ts > void write( Ts&&... ts ); void resize( const std::int64_t size ); auto seek( const std::int64_t offset, const std::ios_base::seekdir whence ) -> std::int64_t; auto tell() const -> std::int64_t; }; } ``` :point_up: All large object manipulation using these functions must take place within an SQL transaction block, since large object file descriptors are only valid for the duration of a transaction. ## Creating a Large Object To [create➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-CREATE) a new large object, call ```c++ static auto tao::pq::large_object::create( const std::shared_ptr& transaction, const tao::pq::oid desired_id = tao::pq::oid::invalid ) -> tao::pq::oid; ``` If no desired oid is given, the server will return the oid that was assigned to the new large object. If you specify a desired oid and that oid is already used, or if any other error occurs, an exception will be thrown. ## Removing a Large Object To [remove➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-UNLINK) a large object from the database, call ```c++ static void tao::pq::large_object::remove( const std::shared_ptr& transaction, const tao::pq::oid id ); ``` ## Importing a Large Object To [import➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-IMPORT) an operating system file as a large object, call ```c++ static auto tao::pq::large_object::import_file( const std::shared_ptr& transaction, const char* filename, const tao::pq::oid desired_id = tao::pq::oid::invalid ) -> tao::pq::oid; ``` If no desired oid is given, the server will return the oid that was assigned to the new large object. If you specify a desired oid and that oid is already used, or if any other error occurs, an exception will be thrown. ## Exporting a Large Object To [export➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-EXPORT) a large object into an operating system file, call ```c++ static void tao::pq::large_object::export_file( const std::shared_ptr& transaction, const tao::pq::oid id, const char* filename ); ``` If an error occurs an exception will be thrown. ## Opening an Existing Large Object To [open➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-OPEN) an existing large object for reading or writing, call ```c++ tao::pq::large_object::large_object( const std::shared_ptr& transaction, const tao::pq::oid id, const std::ios_base::openmode m ); ``` The mode `m` bits control whether the object is opened for reading (`std::ios_base::in`), writing (`std::ios_base::out`), or both. If an error occurs an exception will be thrown. The destructor will take care of [closing➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-CLOSE) the large object descriptor. ## Writing Data to a Large Object To [write➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-WRITE) data to a large object, several methods are available. ```c++ void tao::pq::large_object::write( const char* data, const std::size_t size ); void tao::pq::large_object::write( const std::byte* data, const std::size_t size ); void tao::pq::large_object::write( const char* data ); template< typename... Ts > void tao::pq::large_object::write( Ts&&... ts ); ``` The first three methods write a chunk of data starting at `data` that is `size` bytes long to the large object. The fourth method expects a zero-terminated string, which will be written to the large object. The fifth method template forwards its arguments to a call to [`tao::pq::to_binary_view()`](Binary-Data.md), then writes the binary data to the large object. This allows all data types that are accepted by `tao::pq::to_binary_view()` to be written seamlessly into large objects. If an error occurs an exception will be thrown. ## Reading Data from a Large Object To [read➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-READ) data from a large object, several methods are available. ```c++ auto tao::pq::large_object::read( char* data, const std::size_t size ) -> std::size_t; auto tao::pq::large_object::read( std::byte* data, const std::size_t size ) -> std::size_t; template< typename T = tao::pq::binary > auto tao::pq::large_object::read( const std::size_t size ) -> T; ``` The first three methods read up to `size` bytes from the large object into `data`. The methods will return the number of bytes actually read; this will be less than `size` if the end of the large object is reached first. The fourth method will create a new object of type `T` and read up to `size` bytes from the large object. `T` must be one of the following types: * `std::string` * [`tao::pq::binary`](Binary-Data.md) aka `std::vector` :point_up: Although the `size` parameter of the above methods is declared as `std::size_t`, the methods will reject values larger than `INT_MAX`. In practice, it's best to transfer data in chunks of at most a few megabytes anyway. If an error occurs an exception will be thrown. ## Seeking in a Large Object To change the [current read or write➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-SEEK) location associated with a large object, call ```c++ auto tao::pq::large_object::seek( const std::int64_t offset, const std::ios_base::seekdir whence ) -> std::int64_t; ``` This method moves the current location pointer for the large object to the new location specified by `offset`. The valid values for `whence` are `std::ios_base::beg` (seek from object start), `std::ios_base::cur` (seek from current position), and `std::ios_base::end` (seek from object end). The return value is the new location pointer. If an error occurs an exception will be thrown. ## Obtaining the Seek Position of a Large Object To [obtain➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-TELL) the current read or write location of a large object, call ```c++ auto tao::pq::large_object::tell() const -> std::int64_t; ``` If an error occurs an exception will be thrown. ## Truncating a Large Object To [truncate➚](https://www.postgresql.org/docs/current/lo-interfaces.html#LO-TRUNCATE) a large object to a given size, call ```c++ void tao::pq::large_object::resize( const std::int64_t size ); ``` If `size` is greater than the large object's current length, the large object is extended to the specified length with null bytes ('\0'). If an error occurs an exception will be thrown. --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Parameter-Type-Conversion.md ================================================ # Parameter Type Conversion When [executing statements](Statement.md), you can pass any number of parameters after the statement itself to the `execute()`-method. Each parameter then gets converted through the parameter traits class template into one or more positional parameters for the SQL statement. By default, the following C++ types are available for use as parameters. ## NULL If you want to pass NULL to the database, you pass `tao::pq::null`. ## Fundamental Types * Booleans * `bool` * Character * `char` * Integral Types * `signed char` (8-bit signed integer) * `unsigned char` (8-bit unsigned integer) * `short` * `unsigned short` * `int` * `unsigned int` * `long` * `unsigned long` * `long long` * `unsigned long long` * Floating Point Types * `float` * `double` * `long double` * Strings * `const char*` * `std::string` * `std::string_view` * [Binary](Binary-Data.md) ([`BYTEA`➚](https://www.postgresql.org/docs/current/datatype-binary.html)) * `std::vector< std::byte >` * `std::span< std::byte >` * [`ARRAY`➚](https://www.postgresql.org/docs/current/arrays.html) * `std::array< T, N >` * `std::list< T >` * `std::set< T >` * `std::unordered_set< T >` * `std::vector< T >` ## `std::optional< T >` Represents a [nullable➚](https://en.wikipedia.org/wiki/Nullable_type) type. If the optional is not empty, then the parameters from `T` are generated. If the optional is empty, it is equivalent to one or more `tao::pq::null` parameter(s). The number of NULL values generated depends on the number of parameters that `T` would generate. ## `std::pair< T, U >` Generates all parameters from `T`, then all parameters from `U`, in that order. Note that this generates at least two parameters, possibly more. Pairs can be nested, e.g. `std::pair,int>` would generate three parameters. ## `std::tuple< Ts... >` As a generalisation of pairs, tuples generate all parameters for their individual elements, in order. ## Aggregates Any suitable aggregate data type can be used as a parameter when registered with taoPQ. ```c++ struct my_aggregate { std::string name; unsigned age; std::string address; bool is_pet_owner; }; template<> inline constexpr bool tao::pq::is_aggregate< my_aggregate > = true; ``` See [Aggregate Support](Aggregate.md) for more information. ## Custom Data Types Custom data types can be registered in two different ways, by using a `to_taopq()` function or method, or by specializing the `tao::pq::parameter_traits` class template. ### `to_taopq()` You can use a function or method called `to_taopq()`, any value returned will then be fed into the parameters as outlined above. Usually, that means a simple conversion will return a single known type, more complicated types return a `std::tuple` to return multiple parameters for the SQL statement. There are multiple places where this function or method can be placed. #### Intrusive Placement If you have control over a class type, you can add a method called `to_taopq()` that can be called with no parameters. The method can be marked `const` and/or `noexcept` as applicable. ```c++ class my_int_wrapper { private: int value; public: explicit my_int_wrapper( int v ) : value( v ) {} auto to_taopq() const noexcept { return value; } }; ``` You can now pass values of type `my_int_wrapper` as parameters to call taoPQ's `execute()`-methods. If your class has more members, you can return multiple values: ```c++ class my_coordinates { private: double x,y,z; public: // ctors, etc. auto to_taopq() const noexcept { return std::tie( x, y, z ); } }; ``` The above means that each time you pass a `my_coordinates` instance as a parameter to an `execute()`-method, three positional parameters are added and can be referenced from the SQL statement. #### Non-Intrusive Placement If you can't modify the class you could specialize `tao::pq::bind<...>` and place a static `to_taopq()`-method inside the specialization, or provide a free function called `to_taopq()` instead. Those functions must accept a single parameter of the class you want to register. Example for the specialization of `tao::pq::bind<...>`: ```c++ struct some_coordinates { double x,y,z; }; template<> struct tao::pq::bind< some_coordinates > { static auto to_taopq( const some_coordinates& v ) noexcept { return std::tie( v.x, v.y, v.z ); } }; ``` Example for the free function: ```c++ struct some_coordinates { double x,y,z; }; auto to_taopq( const some_coordinates& v ) noexcept { return std::tie( v.x, v.y, v.z ); } ``` The free function is found either by [ADL➚](https://en.cppreference.com/w/cpp/language/adl) or in namespace `tao::pq`. :point_up: Note that any returned value in the above examples can itself be a registered custom type. taoPQ will simply expand parameters recursively. ### `tao::pq::parameter_traits< T >` If the above custom data type registration via `to_taopq()` is somehow not sufficient, you can specialize the `tao::pq::parameter_traits` class template. For now please consult the source code or ask the developers. TODO: Write proper documentation. --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Performance.md ================================================ # Performance **TODO** --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Requirements.md ================================================ # Requirements ## Operating System Support * We support: * [Windows➚](https://en.wikipedia.org/wiki/Microsoft_Windows). * [macOS➚](https://en.wikipedia.org/wiki/MacOS). * [Linux➚](https://en.wikipedia.org/wiki/Linux). * Other systems might work. ## Compiler Support * We support: * [Visual Studio➚](https://en.wikipedia.org/wiki/Microsoft_Visual_Studio) version 2022 or newer. * [Xcode➚](https://en.wikipedia.org/wiki/Xcode) version 15 or newer. * [GCC➚](https://gcc.gnu.org/) version 13 or newer. * [Clang➚](https://clang.llvm.org/) version 16 or newer. * Other compilers might work. ## Language Requirements * We require [C++20➚](https://en.wikipedia.org/wiki/C%2B%2B20) or newer. * We require exception support. The `-fno-exceptions` option is not supported. * We require RTTI support. The `-fno-rtti` option is not supported. ## Compiler Options/Warnings * We support Clang's [`-fms-extensions`➚](https://clang.llvm.org/docs/MSVCCompatibility.html) option. * We support the `/W4` option on [Visual Studio➚](https://docs.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level). * We support the `-pedantic`, `-Wall`, and `-Wextra` options on [GCC➚](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html) and [Clang➚](https://clang.llvm.org/docs/DiagnosticsReference.html). :point_up: Note that we *support* these options, we don't *require* them to be used. You can decide which options you want to use in your project, we just try to not get in the way by making sure that our code doesn't generate any of those warnings. ## Database Requirements * We expect the database to use UTF-8 encoding. * We expect the database to send `BYTEA` data in [`bytea` hex format➚](https://www.postgresql.org/docs/current/datatype-binary.html). * We expect the database connection to use [protocol version 3➚](https://www.postgresql.org/docs/current/protocol.html). --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Result-Type-Conversion.md ================================================ # Result Type Conversion Depending on what type of [statement](Statement.md) was executed, you receive a [result](Result.md) containing a query result set. Those results then offer conversions to C++ data types through various methods, e.g. `tao::pq::row::as()`. This chapter discusses the data types that are available by default and how to register your own custom data types when needed. ## Fundamental Types By default, the following C++ types are available for use as result types. * Booleans * `bool` * Character * `char` * Integral Types * `signed char` (8-bit signed integer) * `unsigned char` (8-bit unsigned integer) * `short` * `unsigned short` * `int` * `unsigned int` * `long` * `unsigned long` * `long long` * `unsigned long long` * Floating Point Types * `float` * `double` * `long double` * Strings * `const char*` * `std::string` * `std::string_view` * [Binary](Binary-Data.md) ([`BYTEA`➚](https://www.postgresql.org/docs/current/datatype-binary.html)) * `std::vector< std::byte >` * [`ARRAY`➚](https://www.postgresql.org/docs/current/arrays.html) * `std::list< T >` * `std::set< T >` * `std::unordered_set< T >` * `std::vector< T >` ## `std::optional< T >` Represents a [nullable➚](https://en.wikipedia.org/wiki/Nullable_type) type. If the result field is NULL, an empty optional is returned. If the result field is not NULL, the value is converted to `T` and returned in the optional. ## `std::pair< T, U >` Returns a `std::pair< T, U >`, hence convertes two (or more) neighboring fields from the result's row. It can read more than two fields when `T` or `U` themselves read more than one field, i.e. converting to `std::pair>` will read three fields from the result's row. ## `std::tuple< Ts... >` As a generalisation of pairs, tuples read all fields for their individual elements, in order. ## Aggregates Any suitable aggregate data type can be used as a result type when registered with taoPQ. ```c++ struct my_aggregate { std::string name; unsigned age; std::string address; bool is_pet_owner; }; template<> inline constexpr bool tao::pq::is_aggregate< my_aggregate > = true; ``` See [Aggregate Support](Aggregate-Support.md) for more information. ## Custom Data Types Custom data types can be registered in two different ways, by using a `from_taopq()` function or method, or by specializing the `tao::pq::result_traits` class template. ### `from_taopq()` You can use a function or method called `from_taopq()`, which takes one or more suitable parameters and returns a new instance of your type. There are multiple places where this function or method can be placed. #### Intrusive Placement If you have control over a class type, you can add a static method called `from_taopq()`. The method can be marked `noexcept` if applicable. ```c++ class my_int_wrapper { private: int value; public: explicit my_int_wrapper( int v ) : value( v ) {} static auto from_taopq( const int v ) noexcept { return my_int_wrapper( v ); } }; ``` taoPQ will find the class' method, analyze the methods parameters and convert the result's row accordingly. The method must have at least one parameter, each parameter will read the required number of fields. #### Non-Intrusive Placement If you can't modify the class you could specialize `tao::pq::bind<...>` and place a static `from_taopq()`-method inside the specialization. The method behaves identical to the intrusive version. Example for the specialization of `tao::pq::bind<...>`: ```c++ struct some_coordinates { double x,y,z; }; template<> struct tao::pq::bind< some_coordinates > { static auto from_taopq( const double x, const double y, const double z ) noexcept { return some_coordinates{ x, y, z }; } }; ``` :point_up: Note that unlike [`to_taopq()`](Parameter-Type-Conversion.md), there is no free function version for `from_taopq()` available. This is due to the fact that the custom data type is not a parameter, but rather the returned value. The parameter list can therefore be identical for multiple custom data types and this could leads to conflicting overloads. Also, [ADL➚](https://en.cppreference.com/w/cpp/language/adl) would be unavailable as only the parameters' types are considered for ADL. ### `tao::pq::result_traits< T >` If the above custom data type registration via `from_taopq()` is somehow not sufficient, you can specialize the `tao::pq::result_traits` class template. For now please consult the source code or ask the developers. TODO: Write proper documentation. --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Result.md ================================================ # Result When [executing statements](Statement.md) you receive a result object. A result comes in two flavours, depending on what statement was executed. When executing a [query statement➚](https://www.postgresql.org/docs/current/queries-overview.html), the database returns a result set, i.e. any number of rows containing one or more fields of data. When executing non-query statements, you can usually only extract the number of affected rows. Query results can be iterated or conveniently converted into a C++ data structure. Predefined types include most arithmetic C++ data types, STL containers, `std::pair`/`std::tuple`, and `std::optional` for [nullable➚](https://en.wikipedia.org/wiki/Nullable_type) values. Again custom types can be added with custom conversion functions. ## Synopsis Don't be intimidated by the size of the API, as you can see several methods are just single-line convenience forwarders. We will first give the synopsis of everything, afterwards we will break down the API into small logical portions. ```c++ namespace tao::pq { namespace internal { class zsv; // zero-terminated string view } using null_t = decltype( null ); class row; class field; class result final { private: // satisfies LegacyRandomAccessIterator, see // https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator class const_iterator; public: // non-query result access bool has_rows_affected() const noexcept; auto rows_affected() const -> std::size_t; // information about the returned fields auto columns() const noexcept -> std::size_t; auto name( const std::size_t column ) const -> std::string; auto index( const internal::zsv in_name ) const -> std::size_t; // size of the result set bool empty() const; auto size() const -> std::size_t; // iteration auto begin() const -> const_iterator; auto end() const -> const_iterator; auto cbegin() const -> const_iterator; auto cend() const -> const_iterator; // get basic information about a field bool is_null( const std::size_t row, const std::size_t column ) const; auto get( const std::size_t row, const std::size_t column ) const -> const char*; // access rows auto operator[]( const std::size_t row ) const noexcept -> pq::row; auto at( const std::size_t row ) const -> pq::row; // convenience conversions for whole result sets // expects size()==1, converts the only row to T template< typename T > auto as() const -> T; template< typename T > auto optional() const -> std::optional< T >; // convenience conversions to pair/tuple template< typename T, typename U > auto pair() const { return as< std::pair< T, U > >(); } template< typename... Ts > auto tuple() const { return as< std::tuple< Ts... > >(); } // convert each row into T::value_type and add to a container of type T template< typename T > auto as_container() const -> T; // convenience conversions to standard containers template< typename... Ts > auto vector() const { return as_container< std::vector< Ts... > >(); } template< typename... Ts > auto list() const { return as_container< std::list< Ts... > >(); } template< typename... Ts > auto set() const { return as_container< std::set< Ts... > >(); } template< typename... Ts > auto multiset() const { return as_container< std::multiset< Ts... > >(); } template< typename... Ts > auto unordered_set() const { return as_container< std::unordered_set< Ts... > >(); } template< typename... Ts > auto unordered_multiset() const { return as_container< std::unordered_multiset< Ts... > >(); } template< typename... Ts > auto map() const { return as_container< std::map< Ts... > >(); } template< typename... Ts > auto multimap() const { return as_container< std::multimap< Ts... > >(); } template< typename... Ts > auto unordered_map() const { return as_container< std::unordered_map< Ts... > >(); } template< typename... Ts > auto unordered_multimap() const { return as_container< std::unordered_multimap< Ts... > >(); } // access underlying result pointer from libpq auto underlying_raw_ptr() noexcept -> PGresult*; auto underlying_raw_ptr() const noexcept -> const PGresult*; }; class row { private: // satisfies LegacyRandomAccessIterator, see // https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator class const_iterator; public: auto slice( const std::size_t offset, const std::size_t in_columns ) const -> row; auto columns() const noexcept -> std::size_t; auto name( const std::size_t column ) const -> std::string; auto index( const internal::zsv in_name ) const -> std::size_t; // iteration auto begin() const -> const_iterator; auto end() const -> const_iterator; auto cbegin() const -> const_iterator; auto cend() const -> const_iterator; bool is_null( const std::size_t column ) const; auto get( const std::size_t column ) const -> const char*; template< typename T > auto get( const std::size_t column ) const -> T; template< typename T > auto optional( const std::size_t column ) const { return get< std::optional< T > >( column ); } template< typename T > auto as() const -> T; template< typename T > auto optional() const { return as< std::optional< T > >(); } template< typename T, typename U > auto pair() const { return as< std::pair< T, U > >(); } template< typename... Ts > auto tuple() const { return as< std::tuple< Ts... > >(); } auto at( const std::size_t column ) const -> field; auto operator[]( const std::size_t column ) const noexcept -> field; auto at( const internal::zsv in_name ) const -> field; auto operator[]( const internal::zsv in_name ) const -> field; friend void swap( row& lhs, row& rhs ) noexcept; }; class field { public: auto name() const -> std::string; auto index() const -> std::size_t; bool is_null() const; auto get() const -> const char*; template< typename T > auto as() const -> T; template< typename T > auto optional() const { return as< std::optional< T > >(); } }; bool operator==( const field& f, null_t ) { return f.is_null(); } bool operator==( null_t, const field& f ) { return f.is_null(); } bool operator!=( const field& f, null_t ) { return !f.is_null(); } bool operator!=( null_t, const field& f ) { return !f.is_null(); } } ``` ## Non-Query Results For non-query results, i.e. when you called an `INSERT`-, `UPDATE`-, or `DELETE`-statement, you really only need the `rows_affected()`-method. In generic programming, when you might not know what kind of result you have, you can check whether or not a result is a non-query result by calling the `has_rows_affected()`-method. ```c++ bool tao::pq::result::has_rows_affected() const; auto tao::pq::result::rows_affected() const -> std::size_t; ``` ## Query Results [Query results➚](https://www.postgresql.org/docs/current/queries-overview.html) are non-mutable data sets, they are cheap to copy, move, or assign and you can iterate over the data multiple times in random order. Likewise, rows are also non-mutable, as well as fields. This also means iterators will behave as constant iterators. Query results act similar to a random-access container. The don't fully implement the [container requirements➚](https://en.cppreference.com/w/cpp/named_req/Container), but a reasonable subset of those are provided. :point_up: Rows and fields are non-owning, meaning they are only valid as long as the query result instance is still valid. ### Basics You can query the container's size, i.e. the number of rows it contains, by calling the `size()`-method. The `empty()`-method will, of course, return whether the size of the container is zero or not. ```c++ bool tao::pq::result::empty() const; auto tao::pq::result::size() const -> std::size_t; ``` The number of columns, column order, and the column name is the same for all rows of a result set. You can query the number of columns by calling the `columns()`-method. You can retrieve the name of a column using the `name()`-method, or the column index by using the `index()`-method. ```c++ auto tao::pq::result::columns() const -> std::size_t; auto tao::pq::result::name( std::size_t column ) const -> std::string; auto tao::pq::result::index( tao::pq::internal::zsv name ) const -> std::size_t; ``` Direct access to the data is provided by the `is_null()`- and the `get()`-methods. The latter returns the raw string as returned by `libpq`, it is a low level access method that is rarely used directly. ```c++ bool tao::pq::result::is_null( std::size_t row, std::size_t column ) const; auto tao::pq::result::get( std::size_t row, std::size_t column ) const -> const char*; ``` ### Row Access You can iterate over the container's elements, the rows, with the usual methods. This is what the `begin()`- and `end()`-methods are for, also allowing for the convenient use of [range-based for loops➚](https://en.cppreference.com/w/cpp/language/range-for). ```c++ auto tao::pq::result::begin() const -> tao::pq::result::const_iterator; auto tao::pq::result::end() const -> tao::pq::result::const_iterator; ``` The identical `cbegin()`- and `cend()`-methods are provided for completeness. Here's an example of how to iterate all rows: ```c++ const tao::pq::result result = ...; for( const auto& row : result ) { // use row to access your data } ``` or more traditionally: ```c++ const tao::pq::result result = ...; for( auto it = std::begin( result ); it != std::end( result ); ++it ) { // use *it to access your row's data } ``` Alternatively, you can use an index to access the rows. ```c++ const tao::pq::result result = ...; for( std::size_t i = 0; i < result.size(); ++i ) { // use result[ i ] or result.at( i ) to access your row's data } ``` This is enabled by the accessors, the `at()`-method and the `[]`-operator. ```c++ auto tao::pq::result::at( std::size_t index ) const -> tao::pq::row; auto tao::pq::result::operator[]( std::size_t index ) const noexcept -> tao::pq::row; ``` More conversion methods will be discussed later, after we covered the basics for rows and fields. ### Field Access Given a row, you can query information about the fields with the same methods as for the result itself. ```c++ auto tao::pq::row::columns() const -> std::size_t; auto tao::pq::row::name( std::size_t column ) const -> std::string; auto tao::pq::row::index( tao::pq::internal::zsv name ) const -> std::size_t; ``` Direct access to the data is provided by the `is_null()`- and the `get()`-methods. The latter returns the raw string as returned by `libpq`, it is a low level access method that is rarely used directly. ```c++ bool tao::pq::row::is_null( std::size_t column ) const; auto tao::pq::row::get( std::size_t column ) const -> const char*; ``` You can iterate over the row's elements, the fields, with the usual methods. This is what the `begin()`- and `end()`-methods are for, also allowing for the convenient use of [range-based for loops➚](https://en.cppreference.com/w/cpp/language/range-for). ```c++ auto tao::pq::row::begin() const -> tao::pq::row::const_iterator; auto tao::pq::row::end() const -> tao::pq::row::const_iterator; ``` The identical `cbegin()`- and `cend()`-methods are provided for completeness. Here's an example of how to iterate all fields: ```c++ const tao::pq::result result = ...; for( const auto& row : result ) { for( const auto& field : row ) { // use field to access your data } } ``` or more traditionally: ```c++ const tao::pq::result result = ...; for( auto it = std::begin( result ); it != std::end( result ); ++it ) { for( auto jt = std::begin( *it ); jt != std::end( *it ); ++jt ) { // use *jt to access your fields's data } } ``` Alternatively, you can use an index to access the fields. ```c++ const tao::pq::result result = ...; for( std::size_t i = 0; i < result.size(); ++i ) { for( std::size_t j = 0; j < result[ i ].columns(); ++j ) { // use result[ i ][ j ] or result.at( i ).at( j ) to access your field's data } } ``` This is enabled by the accessors, the `at()`-method and the `[]`-operator. ```c++ auto tao::pq::row::at( std::size_t index ) const -> tao::pq::field; auto tao::pq::row::operator[]( std::size_t index ) const noexcept -> tao::pq::field; ``` More conversion methods will be discussed later, after we covered the basics for fields. ### Fields You can query a field's name by calling the `name()`-method. ```c++ auto tao::pq::field::name() const -> std::string; ``` Direct access to the data is provided by the `is_null()`- and the `get()`-methods. The latter returns the raw string as returned by `libpq`, it is a low level access method that is rarely used directly. ```c++ bool tao::pq::field::is_null() const; auto tao::pq::field::get() const -> const char*; ``` Now that we covered the basics, we can retrieve the actual data and convert it to the data types we need. ## Field Data Conversion A field can be converted to any data type `T` that is a single field wide. What we mean by that is, that `tao::pq::result_traits_size< T >` yields 1. This is the case for `const char*`, `std::string`, `int`, etc. In order to convert a field to the data type you want, you can use the `as()`-method. ```c++ template< typename T > auto tao::pq::field::as() const -> T; ``` The conversion is handled by the `tao::pq::result_traits` class template, which is documented in the [result type conversion](Result-Type-Conversion.md) chapter. A field also has a convenience method to convert directly into a `std::optional`. ```c++ template< typename T > auto tao::pq::field::optional() const { return as< std::optional< T > >(); } ``` ## Row Data Conversion **TODO** Finish this up for rows and results... --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/Statement.md ================================================ # Statement :warning: Before showing you how to execute statements with taoPQ, we'd like to take a moment to talk about [SQL injection➚](https://en.wikipedia.org/wiki/SQL_injection). SQL injection is a large family of security issues that has plagued the internet for decades. We designed taoPQ to allow you to *safely* and *conveniently* write code that does not allow SQL injection. With that said, let's start executing statements with taoPQ. ## `execute()` All statements are executed by calling an `execute()`-method, either on a transaction, connection, or connection pool directly. The synopsis of the execute methods of those types (`Type` being `tao::pq::transaction`, `tao::pq::connection`, or `tao::pq::connection_pool`) is: ```c++ template< typename... As > auto Type::execute( const tao::pq::internal::zsv statement, As&&... as ) -> tao::pq::result; ``` For the purpose of this chapter, it makes no difference which `Type` is used. The method takes the statement itself as its first parameter plus additional parameters that will be passed to the statement. It returns a `tao::pq::result` that is documented in the [Result](Result.md) chapter. In case of an error, i.e. if the statement execution failed, an exception is thrown as documented in the [Error Handling](Error-Handling.md) chapter. ### `tao::pq::internal::zsv` The `tao::pq::internal::zsv` type (zero-terminated string view) that is used for the `statement` parameter ensures that you pass a zero-terminated string. It is a non-owning type which has non-explicit constructor overloads for `const char*` and `const std::string&`. For safety reasons, there is another overload for `std::nullptr_t` that is deleted. Note that we do not accept `std::string_view`, as the underlying C-API of `libpq` requires a zero-terminated string. ## Positional Parameters To reference the parameters you supplied in the statement, you use [positional parameters➚](https://www.postgresql.org/docs/current/sql-expressions.html). Positional parameters are of the form `$n` where `n` is a number starting at 1. Here's an example of how you can insert a row with two columns into the database: ```c++ tr->execute( "INSERT INTO user ( name, age ) VALUES ( $1, $2 )", "Daniel", 42 ); ``` The actual data is separated from the statement itself. The use of positional parameters makes passing strings and other types safe, as there is no need for manually escaping the data. Our library now knows what is the actual SQL statement you want to send and what is the data you want to send. This protects you from SQL injections and it is also quiet convenient. The way the data is now transferred between the client and the server is also more efficient. :warning: The only thing you have to remember is to **never** concatenate strings together to create the SQL statement including the data manually. As this is such an important point, we will illustrate how it should **not** be done: ```c++ auto find_user( const std::string& name ) { return connection->execute( "SELECT FROM user WHERE name = '" + name + "'" ); // ~~~~~~~~~~~~~~ WRONG!!! } ``` :no_entry: Never concatenate SQL statements manually! Consider `name` to be an input field coming from untrusted user input. What happens, if a user enters the following "name": `Robert'; DELETE FROM user WHERE name <> 'Little Bobby Tables` Yupp, all users other than [`Little Bobby Tables`➚](https://xkcd.com/327/) have just been deleted from the database. You might have seen other libraries where you should [escape the data explicitly➚](https://www.postgresql.org/docs/current/libpq-exec.html#LIBPQ-EXEC-ESCAPE-STRING), something like: ```c++ auto find_user( const std::string& name ) { return connection->execute( "SELECT FROM user WHERE name = '" + connection->escape( name ) + "'" ); } ``` This is cumbersome and error-prone. It is easy to forget calling the escape method, which the compiler will *not* catch for you, and for non-strings, the code needs to call explicit conversion methods to string. This turns longer SQL statements into a long, ugly mess. Positional parameters solve all of those problems and therefore taoPQ does not even offer any escaping methods. To be safe and to make your life easier, with taoPQ always use positional parameters: ```c++ auto find_user( const std::string& name ) { return connection->execute( "SELECT FROM user WHERE name = $1", name ); } ``` ## Multi-Query Commands Some SQL client libraries allow multi-query commands, i.e. the command string can include multiple SQL statements. In `libpq`, this is supported by the [`PQexec()`➚](https://www.postgresql.org/docs/current/libpq-exec.html)-function. As an extra defense against SQL injection, taoPQ *never* calls the `PQexec()`-function. We allow at most one SQL command in the given statement passed to an `execute()`-method. ## Prepared Statements In the [Connection](Connection.md) chapter we have shown how you can prepare (and deallocate) prepared statements. In order to execute prepared statements, you simply pass the name of the prepared statement to the `execute()`-method instead of the actual SQL statement. This might look like this: ```c++ connection->prepare( "insert_user", "INSERT INTO user ( name, age ) VALUES ( $1, $2 )" ); connection->execute( "insert_user", "Daniel", 42 ); connection->execute( "insert_user", "Tom", 41 ); connection->execute( "insert_user", "Jerry", 29 ); ``` This is both more efficient and also allows you to change the statements in a central place if need be, without touching any of the places where it is actually used. You might want to wrap calls to a (prepared) statement into an application-specific wrapper, that way you add C++'s type safety for the rest of the application calling that method (and also receiving the result). Another idea that we already used in practice is to read the SQL statements from a configuration file. When you then need to change a statement, e.g. to work around a performance issue with the database or because you renamed a column in the database, all you need to do is adapt the configuration. No need to recompile the application. ## Type Conversion The above example also shows that you can use different data types as parameters. Besides basic types like strings or integers, you can also use more complex types like `std::tuple` which will decay into multiple parameters. You can even register your own data types to generate one or more parameters from them. The [Parameter Type Conversion](Parameter-Type-Conversion.md) chapter explains which data types are supported out-of-the-box, and how you can register your own custom data types. --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: doc/TOC.md ================================================ # Table of Content * [Requirements](Requirements.md) * [Operating System Support](Requirements.md#operator-system-support) * [Compiler Support](Requirements.md#compiler-support) * [Language Requirements](Requirements.md#language-requirements) * [Compiler Options/Warnings](Requirements.md#compiler-optionswarnings) * [Database Requirements](Requirements.md#database-requirements) * [Installation](Installation.md) * **TODO** after properly rewriting this chapter * [Getting Started](Getting-Started.md) * [Next Steps](Getting-Started.md#next-steps) * [Advanced Topics](Getting-Started.md#advanced-topics) * [Connection Pool](Connection-Pool.md) * [Synopsis](Connection-Pool.md#synopsis) * [Creating Connection Pools](Connection-Pool.md#creating-connection-pools) * [Borrowing Connections](Connection-Pool.md#borrowing-connections) * [Executing Statements](Connection-Pool.md#executing-statements) * [Cleanup](Connection-Pool.md#cleanup) * [Thread Safety](Connection-Pool.md#thread-safety) * [Connection](Connection.md) * [Synopsis](Connection.md#synopsis) * [Creating a Connection](Connection.md#creating-a-connection) * [Creating Transactions](Connection.md#creating-transactions) * [Creating a "Direct" Transaction](Connection.md#creating-a-direct-transaction) * [Creating a Database Transaction](Connection.md#creating-a-database-transaction) * [Executing Statements](Connection.md#executing-statements) * [Prepared Statements](Connection.md#prepared-statements) * [Manually Prepared Statements](Connection.md#manually-prepared-statements) * [Checking Status](Connection.md#checking-status) * [Notification Framework](Connection.md#notification-framework) * [Sending Messages](Connection.md#sending-messages) * [Receiving Messages](Connection.md#receiving-messages) * [Handling Messages](Connection.md#handling-messages) * [Per Channel Handlers](Connection.md#per-channel-handlers) * [Asynchronous Notifications](Connection.md#asynchronous-notifications) * [Event Loop](Connection.md#event-loop) * [Underlying Connection Pointer](Connection.md#underlying-connection-pointer) * [Error Messages](Connection.md#error-messages) * [Transaction](Transaction.md) * [Synopsis](Transaction.md#synopsis) * [Creating Transactions](Transaction.md#creating-transactions) * [Statement Execution](Transaction.md#statement-execution) * [Terminate Transaction](Transaction.md#terminate-transaction) * [Commit a Transaction](Transaction.md#commit-a-transaction) * [Abort a Transaction](Transaction.md#abort-a-transaction) * [Transaction Ordering](Transaction.md#transaction-ordering) * [Direct Transactions](Transaction.md#direct-transactions) * [Manual Transaction Handling](Transaction.md#manual-transaction-handling) * [Accessing the Connection](Transaction.md#accessing-the-connection) * [Statement](Statement.md) * [`execute()`](Statement.md#execute) * [`tao::pq::internal::zsv`](Statement.md#taopqinternalzsv) * [Positional Parameters](Statement.md#positional-parameters) * [Multi-Query Commands](Statement.md#multi-query-commands) * [Prepared Statements](Statement.md#prepared-statements) * [Type Conversion](Statement.md#type-conversion) * [Parameter Type Conversion](Parameter-Type-Conversion.md) * [NULL](Parameter-Type-Conversion.md#null) * [Fundamental Types](Parameter-Type-Conversion.md#fundamental-types) * [`std::optional< T >`](Parameter-Type-Conversion.md#stdoptional-t-) * [`std::pair< T, U >`](Parameter-Type-Conversion.md#stdpair-t-u-) * [`std::tuple< Ts... >`](Parameter-Type-Conversion.md#stdtuple-ts-) * [Aggregates](Parameter-Type-Conversion.md#aggregates) * [Custom Data Types](Parameter-Type-Conversion.md#custom-data-types) * [`to_taopq()`](Parameter-Type-Conversion.md#to_taopq-) * [Intrusive Placement](Parameter-Type-Conversion.md#intrusive-placement) * [Non-Intrusive Placement](Parameter-Type-Conversion.md#non-intrusive-placement) * [`tao::pq::parameter_traits< T >`](Parameter-Type-Conversion.md#taopqparameter_traits-t-) * [Result](Result.md) * [Synopsis](Result.md#synopsis) * [Non-Query Results](Result.md#non-query-results) * [Query Results](Result.md#query-results) * [Basics](Result.md#basics) * [Row Access](Result.md#row-access) * [Field Access](Result.md#field-access) * [Fields](Result.md#fields) * [Field Data Conversion](Result.md#field-data-conversion) * [Row Data Conversion](Result.md#row-data-conversion) * [Result Type Conversion](Result-Type-Conversion.md) * [Fundamental Types](Result-Type-Conversion.md#fundamental-types) * [`std::optional< T >`](Result-Type-Conversion.md#stdoptional-t-) * [`std::pair< T, U >`](Result-Type-Conversion.md#stdpair-t-u-) * [`std::tuple< Ts... >`](Result-Type-Conversion.md#stdtuple-ts-) * [Aggregates](Result-Type-Conversion.md#aggregates) * [Custom Data Types](Result-Type-Conversion.md#custom-data-types) * [`from_taopq()`](Result-Type-Conversion.md#from_taopq) * [Intrusive Placement](Result-Type-Conversion.md#intrusive-placement) * [Non-Intrusive Placement](Result-Type-Conversion.md#non-intrusive-placement) * [`tao::pq::result_traits< T >`](Result-Type-Conversion.md#taopqresult_traits-t-) * [Aggregate Support](Aggregate-Support.md) * [Status](Aggregate-Support.md#status) * [Requirements](Aggregate-Support.md#requirements) * [Registration](Aggregate-Support.md#registration) * [Direct Result Conversion](Aggregate-Support.md#direct-result-conversion) * [Example](Aggregate-Support.md#example) * [Error Handling](Error-Handling.md) * [SQLSTATE](Error-Handling.md#sqlstate) * [Class of Error](Error-Handling.md#class-of-error) * [Specific Error Conditions](Error-Handling.md#specific-error-conditions) * [Connection Errors](Error-Handling.md#connection-errors) * [Other Exceptions](Error-Handling.md#other-exceptions) * [Binary Data](Binary-Data.md) * [The `BYTEA` Data Type](Binary-Data.md#the-bytea-data-type) * [C++ Binary Data](Binary-Data.md#c-binary-data) * [Passing Binary Data](Binary-Data.md#passing-binary-data) * [Receiving Binary Data](Binary-Data.md#receiving-binary-data) * [Bulk Transfer](Bulk-Transfer.md) * [Synopsis](Bulk-Transfer.md#synopsis) * [Large Object](Large-Object.md) * [Synopsis](Large-Object.md#synopsis) * [Creating a Large Object](Large-Object.md#creating-a-large-object) * [Removing a Large Object](Large-Object.md#removing-a-large-object) * [Importing a Large Object](Large-Object.md#importing-a-large-object) * [Exporting a Large Object](Large-Object.md#exporting-a-large-object) * [Opening an Existing Large Object](Large-Object.md#opening-an-existing-large-object) * [Writing Data to a Large Object](Large-Object.md#writing-data-to-a-large-object) * [Reading Data from a Large Object](Large-Object.md#reading-data-from-a-large-object) * [Seeking in a Large Object](Large-Object.md#seeking-in-a-large-object) * [Obtaining the Seek Position of a Large Object](Large-Object.md#obtaining-the-seek-position-of-a-large-object) * [Truncating a Large Object](Large-Object.md#truncating-a-large-object) * [Performance](Performance.md) ================================================ FILE: doc/Transaction.md ================================================ # Transaction Before we continue with our own documentation, we'd like to once again point you to the excellent [PostgreSQL documentation➚](https://www.postgresql.org/docs/current/tutorial-transactions.html) on transactions. We will assume that you are familiar with how transactions work on PostgreSQL and in general, so we will *not* repeat this here. ## Synopsis ```c++ namespace tao::pq { namespace internal { class zsv; // zero-terminated string view } class connection; class result; class transaction : public std::enable_shared_from_this< transaction > { public: // non-copyable, non-movable transaction( const transaction& ) = delete; transaction( transaction&& ) = delete; void operator=( const transaction& ) = delete; void operator=( transaction&& ) = delete; virtual ~transaction() = default; // create transactions auto subtransaction() -> std::shared_ptr< transaction >; // asynchronous statement execution template< typename... As > void send( const internal::zsv statement, As&&... as ); // asynchronous result retrieval auto get_result() -> result; // synchronous statement execution template< typename... As > auto execute( const internal::zsv statement, As&&... as ) { send( statement, std::forward< As >( as )... ); return get_result(); } // finalize void commit(); void rollback(); // access connection auto connection() const noexcept -> const std::shared_ptr< pq::connection >&; }; } ``` :point_up: Note that `tao::pq::internal::zsv` is explained in the [Statement](Statement.md) chapter. ## Creating Transactions In taoPQ, you create a top-level transaction from a connection, the methods available to do so are described in the [Connection](Connection.md) chapter. In short, you create a normal transaction with the connection's `transaction()`-method or a "direct transaction" with the connection's `direct()`-method. Both return a shared pointer to a `tao::pq::transaction`-derived object. From any transaction, you can create a subtransaction by calling the `subtransaction()`-method. ```c++ auto tao::pq::transaction::subtransaction() -> std::shared_ptr< tao::pq::transaction >; ``` It returns just another `tao::pq::transaction`-derived object from which you may create further, nested subtransactions if needed. All transactions then offer the above, unified interface. ## Statement Execution On all transactions you can execute SQL statements. If you execute a statement on a connection object directly, is creates an implicit direct transaction and forwards the execution to that temporary transaction. The actual statement execution, i.e. the `execute()`-method, is described in the [Statement](Statement.md) chapter. ## Terminate Transaction Transaction can be terminated in one of two ways. ### Commit a Transaction In order to commit a transaction you call the `commit()`-method. ```c++ void tao::pq::transaction::commit(); ``` All changes made by the transaction become visible to others and are guaranteed to be durable if a crash occurs. ### Abort a Transaction In order to abort a transaction you call the `rollback()`-method. ```c++ void tao::pq::transaction::rollback(); ``` Rolls back the current transaction and causes all the updates made by the transaction to be discarded. ## Transaction Ordering Any transactions created via taoPQ is registered in the connection object as the currently active transaction. At any given time, a connection can only have a single active transaction. If you attempt to use a transaction object in the wrong order, taoPQ will notice and throw an appropriate `std::logic_error` exception. :point_up: Note that the correct order depends on the *logical* lifetime of transactions. The logical lifetime of a transactions ends when you explicitly call either the `commit()`- or the `rollback()`-method, or if the object's lifetime ends. The destructor will automatically perform a call to the `rollback()`-method if the lifetime was not ended explicitly. This comes in handy when exceptions are thrown and the destructor call happens due to the associated stack unwinding. ## Direct Transactions Without an active transaction PostgreSQL works in ["autocommit"➚](https://www.postgresql.org/docs/current/sql-begin.html) mode. This means that each statement is executed in its own internal transaction and a commit is implicitly performed at the end of the statement (if execution was successful, otherwise a rollback is done). A "direct transaction" represents this concept in taoPQ. A direct transaction is special, as the `commit()`- and `rollback()`-methods are available, but normally not needed. Specifically, you don't need to call the `commit()`-method in order to make the changes you made permanent. However, calling the `commit()`-method ends the logical lifetime of the transaction and the transaction deregisters itself from the connection. In generic code you might receive a transaction from somewhere else and you might call the `commit()`-method regardless of whether the underlying transaction is a direct transaction or a normal transaction. In case you need to know whether a given transaction object is a direct transaction or not, you can call the `is_direct()`-method. ```c++ bool tao::pq::transaction::is_direct() const noexcept; ``` Opening a subtransaction from a direct connection is possible and simply starts a normal transaction on the connection object. ## Manual Transaction Handling You can manually begin, commit, or rollback transactions by executing [`BEGIN`➚](https://www.postgresql.org/docs/current/sql-begin.html), [`COMMIT`➚](https://www.postgresql.org/docs/current/sql-commit.html), or [`ROLLBACK`➚](https://www.postgresql.org/docs/current/sql-rollback.html) statements directly via the `execute()`-method. Likewise, you can manually create, commit, or rollback subtransactions by executing [`SAVEPOINT`➚](https://www.postgresql.org/docs/current/sql-savepoint.html), [`RELEASE SAVEPOINT`➚](https://www.postgresql.org/docs/current/sql-release-savepoint.html), or [`ROLLBACK TO SAVEPOINT`➚](https://www.postgresql.org/docs/current/sql-rollback-to.html) statements directly via the `execute()`-method. :point_up: We strongly advise against manual transaction handling, as it will not be tracked by taoPQ and might confuse our library's transaction ordering framework. We advise to use the methods offered by taoPQ instead of manually handling transactions. ## Accessing the Connection If you need to access the connection that a transaction is bound to, you can call the `connection()`-method. ```c++ auto tao::pq::transaction::connection() const noexcept -> const std::shared_ptr< tao::pq::connection >&; ``` :point_up: Note that the shared pointer will be empty if the logical lifetime of the transaction ended. --- This document is part of [taoPQ](https://github.com/taocpp/taopq). Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch
Distributed under the Boost Software License, Version 1.0
See accompanying file [LICENSE_1_0.txt](../LICENSE_1_0.txt) or copy at https://www.boost.org/LICENSE_1_0.txt ================================================ FILE: example/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) add_subdirectory(get_version) set(EXAMPLE_TARGETS taopq_example_get_version ) add_custom_target(examples COMMENT "Run all examples" DEPENDS ${EXAMPLE_TARGETS} ) foreach(tgt IN LISTS EXAMPLE_TARGETS) add_custom_command(TARGET examples POST_BUILD COMMAND "$" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMMENT "Running ${tgt}" VERBATIM ) endforeach() ================================================ FILE: example/get_version/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(taopq_example_get_version CXX) add_executable(${PROJECT_NAME} main.cpp) target_link_libraries(${PROJECT_NAME} PRIVATE taocpp::taopq) target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20 c_std_11) ================================================ FILE: example/get_version/main.cpp ================================================ #include #include #include #include #include static std::string get_env( std::string_view name ) { #if defined( _MSC_VER ) std::size_t required = 1024; std::vector< char > buffer( required ); const auto err = ::getenv_s( &required, buffer.data(), buffer.size(), std::string( name ).c_str() ); if( err != 0 ) { return {}; } return std::string( buffer.data() ); #else if( const char* v = std::getenv( std::string( name ).c_str() ) ) { return std::string( v ); } return {}; #endif } int main() { const auto database = get_env( "PGDATABASE" ); const auto connection = tao::pq::connection::create( database ); const auto result = connection->execute( "SELECT version()" ); std::cout << "PostgreSQL version: " << result.as< std::string >() << std::endl; return EXIT_SUCCESS; } ================================================ FILE: include/tao/pq/access_mode.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_ACCESS_MODE_HPP #define TAO_PQ_ACCESS_MODE_HPP #include #include #include namespace tao::pq { enum class access_mode : std::uint8_t { default_access_mode, read_write, read_only }; [[nodiscard]] constexpr auto taopq_format_as( const access_mode am ) noexcept -> std::string_view { switch( am ) { case access_mode::default_access_mode: return "default_access_mode"; case access_mode::read_write: return "read_write"; case access_mode::read_only: return "read_only"; default: return ""; } } } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/binary.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_BINARY_HPP #define TAO_PQ_BINARY_HPP #include #include #include namespace tao::pq { using binary = std::vector< std::byte >; using binary_view = std::span< const std::byte >; [[nodiscard]] auto to_binary_view( const auto* data, const std::size_t size ) noexcept -> binary_view requires( sizeof( *data ) == 1 ) { return { reinterpret_cast< const std::byte* >( data ), size }; } [[nodiscard]] auto to_binary_view( const auto& value ) noexcept -> binary_view { return pq::to_binary_view( std::data( value ), std::size( value ) ); } [[nodiscard]] auto to_binary( const auto* data, const std::size_t size ) -> binary requires( sizeof( *data ) == 1 ) { const auto* ptr = reinterpret_cast< const std::byte* >( data ); return { ptr, ptr + size }; } [[nodiscard]] auto to_binary( const auto& value ) -> binary { return pq::to_binary( std::data( value ), std::size( value ) ); } } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/bind.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_BIND_HPP #define TAO_PQ_BIND_HPP namespace tao::pq { template< typename > struct bind; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/connection.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_CONNECTION_HPP #define TAO_PQ_CONNECTION_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { class connection_pool; class pipeline; class table_reader; class table_writer; namespace internal { class top_level_transaction; class top_level_subtransaction; class nested_subtransaction; } // namespace internal class connection final : public std::enable_shared_from_this< connection > { private: friend class connection_pool; friend class table_reader; friend class table_writer; friend class transaction; friend class transaction_base; friend class internal::top_level_transaction; friend class internal::top_level_subtransaction; friend class internal::nested_subtransaction; std::unique_ptr< PGconn, decltype( &PQfinish ) > m_pgconn; transaction_base* m_current_transaction; std::optional< std::chrono::milliseconds > m_timeout; std::set< std::string, std::less<> > m_prepared_statements; std::function< poll::callback > m_poll; std::function< void( const notification& ) > m_notification_handler; std::map< std::string, std::function< void( const char* ) >, std::less<> > m_notification_handlers; std::shared_ptr< log > m_log; [[nodiscard]] auto escape_identifier( const std::string_view identifier ) const -> std::unique_ptr< char, decltype( &PQfreemem ) >; [[nodiscard]] auto attempt_rollback() const noexcept -> bool; static void check_prepared_name( const std::string_view name ); void send_params( const char* statement, const int n_params, const Oid types[], const char* const values[], const int lengths[], const int formats[] ); [[nodiscard]] auto timeout_end( const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now() ) const noexcept -> std::chrono::steady_clock::time_point { return m_timeout ? ( start + *m_timeout ) : start; } void wait( const bool wait_for_write, const std::chrono::steady_clock::time_point end ); void cancel(); [[nodiscard]] auto get_result( const std::chrono::steady_clock::time_point end ) -> std::unique_ptr< PGresult, decltype( &PQclear ) >; [[nodiscard]] auto get_fatal_error( const std::chrono::steady_clock::time_point end ) -> std::unique_ptr< PGresult, decltype( &PQclear ) >; void consume_empty_result( const std::chrono::steady_clock::time_point end ); [[nodiscard]] auto get_copy_data( char*& buffer, const std::chrono::steady_clock::time_point end ) -> std::size_t; [[nodiscard]] auto get_copy_data( char*& buffer ) -> std::size_t; void put_copy_data( const char* buffer, const std::size_t size ); void put_copy_end( const char* error_message = nullptr ); void clear_copy_data( const std::chrono::steady_clock::time_point end ); // pass-key idiom class private_key final { private_key() = default; friend class connection; friend class connection_pool; }; public: connection( const private_key /*unused*/, const std::string& connection_info ); connection( const connection& ) = delete; connection( connection&& ) = delete; void operator=( const connection& ) = delete; void operator=( connection&& ) = delete; ~connection() = default; [[nodiscard]] static auto create( const std::string& connection_info ) -> std::shared_ptr< connection >; [[nodiscard]] auto error_message() const -> const char*; [[nodiscard]] auto poll_callback() const noexcept -> decltype( auto ) { return m_poll; } void set_poll_callback( std::function< poll::callback > poll_cb ) noexcept { m_poll = std::move( poll_cb ); } void reset_poll_callback() { m_poll = internal::poll; } [[nodiscard]] auto notification_handler() const noexcept -> decltype( auto ) { return m_notification_handler; } void set_notification_handler( std::function< void( const notification& ) > handler ) noexcept { m_notification_handler = std::move( handler ); } void reset_notification_handler() noexcept { m_notification_handler = nullptr; } [[nodiscard]] auto notification_handler( const std::string_view channel ) const -> std::function< void( const char* payload ) >; void set_notification_handler( const std::string_view channel, const std::function< void( const char* payload ) >& handler ); void reset_notification_handler( const std::string_view channel ) noexcept; [[nodiscard]] auto log_handler() const noexcept -> decltype( auto ) { return m_log; } void set_log_handler( const std::shared_ptr< pq::log >& log ) noexcept { m_log = log; } void reset_log_handler() noexcept { m_log = nullptr; } [[nodiscard]] auto status() const noexcept -> connection_status; [[nodiscard]] auto transaction_status() const noexcept -> pq::transaction_status; [[nodiscard]] auto pipeline_status() const noexcept -> pq::pipeline_status; void enter_pipeline_mode(); void exit_pipeline_mode(); void pipeline_sync(); [[nodiscard]] auto is_open() const noexcept -> bool { return status() == connection_status::ok; } [[nodiscard]] auto is_idle() const noexcept -> bool { return transaction_status() == transaction_status::idle; } [[nodiscard]] auto is_busy() const noexcept -> bool; [[nodiscard]] auto flush() -> bool; void consume_input(); [[nodiscard]] auto direct() -> std::shared_ptr< pq::transaction >; [[nodiscard]] auto transaction() -> std::shared_ptr< pq::transaction >; [[nodiscard]] auto transaction( const access_mode am, const isolation_level il = isolation_level::default_isolation_level ) -> std::shared_ptr< pq::transaction >; [[nodiscard]] auto transaction( const isolation_level il, const access_mode am = access_mode::default_access_mode ) -> std::shared_ptr< pq::transaction >; [[nodiscard]] auto pipeline() -> std::shared_ptr< pq::pipeline >; void prepare( std::string name, const internal::zsv statement ); void deallocate( const std::string_view name ); template< parameter_type... As > auto execute( const internal::zsv statement, As&&... as ) { return direct()->execute( statement, std::forward< As >( as )... ); } void listen( const std::string_view channel ); void listen( const std::string_view channel, const std::function< void( const char* payload ) >& handler ); void unlisten( const std::string_view channel ); void notify( const std::string_view channel ); void notify( const std::string_view channel, const std::string_view payload ); void handle_notifications(); void get_notifications(); [[nodiscard]] auto socket() const -> int; [[nodiscard]] auto timeout() const noexcept -> decltype( auto ) { return m_timeout; } void set_timeout( const std::chrono::milliseconds timeout ) noexcept { m_timeout = timeout; } void reset_timeout() noexcept { m_timeout = std::nullopt; } [[nodiscard]] auto password( const internal::zsv passwd, const internal::zsv user, const internal::zsv algorithm = "scram-sha-256" ) -> std::string; [[nodiscard]] auto underlying_raw_ptr() noexcept -> PGconn* { return m_pgconn.get(); } [[nodiscard]] auto underlying_raw_ptr() const noexcept -> const PGconn* { return m_pgconn.get(); } }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/connection_pool.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_CONNECTION_POOL_HPP #define TAO_PQ_CONNECTION_POOL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { class connection_pool : public internal::pool< pq::connection > { private: const std::string m_connection_info; std::optional< std::chrono::milliseconds > m_timeout; std::function< poll::callback > m_poll; protected: [[nodiscard]] auto v_create() const -> std::unique_ptr< pq::connection > override; [[nodiscard]] auto v_is_valid( pq::connection& c ) const noexcept -> bool override { return c.is_idle(); } private: // pass-key idiom class private_key final { private_key() = default; friend class connection_pool; }; public: connection_pool( const private_key /*unused*/, const std::string_view connection_info ); void get() const = delete; template< typename T = connection_pool > [[nodiscard]] static auto create( const std::string_view connection_info ) -> std::shared_ptr< T > { return std::make_shared< T >( private_key(), connection_info ); } [[nodiscard]] auto timeout() const noexcept -> decltype( auto ) { return m_timeout; } void set_timeout( const std::chrono::milliseconds timeout ) noexcept { m_timeout = timeout; } void reset_timeout() noexcept { m_timeout = std::nullopt; } [[nodiscard]] auto poll_callback() const noexcept -> decltype( auto ) { return m_poll; } void set_poll_callback( std::function< poll::callback > poll_cb ) noexcept { m_poll = std::move( poll_cb ); } void reset_poll_callback() { m_poll = internal::poll; } [[nodiscard]] auto connection() -> std::shared_ptr< pq::connection >; template< parameter_type... As > auto execute( const internal::zsv statement, As&&... as ) { return connection()->direct()->execute( statement, std::forward< As >( as )... ); } }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/connection_status.hpp ================================================ // Copyright (c) 2022-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_CONNECTION_STATUS_HPP #define TAO_PQ_CONNECTION_STATUS_HPP #include #include #include #include namespace tao::pq { enum class connection_status : std::uint8_t { ok = CONNECTION_OK, bad = CONNECTION_BAD }; [[nodiscard]] constexpr auto taopq_format_as( const connection_status cs ) noexcept -> std::string_view { switch( cs ) { case connection_status::ok: return "ok"; case connection_status::bad: return "bad"; default: return ""; } } } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/exception.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_EXCEPTION_HPP #define TAO_PQ_EXCEPTION_HPP #include #include #include #include namespace tao::pq { struct error : std::runtime_error { using std::runtime_error::runtime_error; }; struct timeout_reached : error { using error::error; }; struct network_error : error { using error::error; }; // https://www.postgresql.org/docs/current/errcodes-appendix.html struct sql_error : error { std::string sqlstate; sql_error( const char* what, const std::string_view in_sqlstate ); }; // when a condition name from PostgreSQL is ambiguous, // we qualify the error class via a template parameter template< typename > struct string_data_right_truncation; template< typename > struct modifying_sql_data_not_permitted; template< typename > struct prohibited_sql_statement_attempted; template< typename > struct reading_sql_data_not_permitted; struct success // 00xxx : sql_error { using sql_error::sql_error; }; struct warning // 01xxx : sql_error { using sql_error::sql_error; }; struct null_value_eliminated_in_set_function // 01003 : warning { using warning::warning; }; template<> struct string_data_right_truncation< warning > // 01004 : warning { using warning::warning; }; struct privilege_not_revoked // 01006 : warning { using warning::warning; }; struct privilege_not_granted // 01007 : warning { using warning::warning; }; struct implicit_zero_bit_padding // 01008 : warning { using warning::warning; }; struct dynamic_result_sets_returned // 0100C : warning { using warning::warning; }; struct deprecated_feature // 01P01 : warning { using warning::warning; }; struct no_data // 02xxx : sql_error { using sql_error::sql_error; }; struct no_additional_dynamic_result_sets_returned // 02001 : no_data { using no_data::no_data; }; struct sql_statement_not_yet_complete // 03xxx : sql_error { using sql_error::sql_error; }; struct connection_error // 08xxx : sql_error { using sql_error::sql_error; explicit connection_error( const char* what ); }; struct sqlclient_unable_to_establish_sqlconnection // 08001 : connection_error { using connection_error::connection_error; }; struct connection_does_not_exist // 08003 : connection_error { using connection_error::connection_error; }; struct sqlserver_rejected_establishment_of_sqlconnection // 08004 : connection_error { using connection_error::connection_error; }; struct connection_failure // 08006 : connection_error { using connection_error::connection_error; }; struct transaction_resolution_unknown // 08007 : connection_error { using connection_error::connection_error; }; struct protocol_violation // 08P01 : connection_error { using connection_error::connection_error; }; struct triggered_action_exception // 09xxx : sql_error { using sql_error::sql_error; }; struct feature_not_supported // 0Axxx : sql_error { using sql_error::sql_error; }; struct invalid_transaction_initiation // 0Bxxx : sql_error { using sql_error::sql_error; }; struct locator_exception // 0Fxxx : sql_error { using sql_error::sql_error; }; struct invalid_locator_specification // 0F001 : locator_exception { using locator_exception::locator_exception; }; struct invalid_grantor // 0Lxxx : sql_error { using sql_error::sql_error; }; struct invalid_grant_operation // 0LP01 : invalid_grantor { using invalid_grantor::invalid_grantor; }; struct invalid_role_specification // 0Pxxx : sql_error { using sql_error::sql_error; }; struct diagnostics_exception // 0Zxxx : sql_error { using sql_error::sql_error; }; struct stacked_diagnostics_accessed_without_active_handler // 0Z002 : diagnostics_exception { using diagnostics_exception::diagnostics_exception; }; struct case_not_found // 20xxx : sql_error { using sql_error::sql_error; }; struct cardinality_violation // 21xxx : sql_error { using sql_error::sql_error; }; struct data_exception // 22xxx : sql_error { using sql_error::sql_error; }; struct array_subscript_error // 2202E : data_exception { using data_exception::data_exception; }; struct character_not_in_repertoire // 22021 : data_exception { using data_exception::data_exception; }; struct datetime_field_overflow // 22008 : data_exception { using data_exception::data_exception; }; struct division_by_zero // 22012 : data_exception { using data_exception::data_exception; }; struct error_in_assignment // 22005 : data_exception { using data_exception::data_exception; }; struct escape_character_conflict // 2200B : data_exception { using data_exception::data_exception; }; struct indicator_overflow // 22022 : data_exception { using data_exception::data_exception; }; struct interval_field_overflow // 22015 : data_exception { using data_exception::data_exception; }; struct invalid_argument_for_logarithm // 2201E : data_exception { using data_exception::data_exception; }; struct invalid_argument_for_ntile_function // 22014 : data_exception { using data_exception::data_exception; }; struct invalid_argument_for_nth_value_function // 22016 : data_exception { using data_exception::data_exception; }; struct invalid_argument_for_power_function // 2201F : data_exception { using data_exception::data_exception; }; struct invalid_argument_for_width_bucket_function // 2201G : data_exception { using data_exception::data_exception; }; struct invalid_character_value_for_cast // 22018 : data_exception { using data_exception::data_exception; }; struct invalid_datetime_format // 22007 : data_exception { using data_exception::data_exception; }; struct invalid_escape_character // 22019 : data_exception { using data_exception::data_exception; }; struct invalid_escape_octet // 2200D : data_exception { using data_exception::data_exception; }; struct invalid_escape_sequence // 22025 : data_exception { using data_exception::data_exception; }; struct nonstandard_use_of_escape_character // 22P06 : data_exception { using data_exception::data_exception; }; struct invalid_indicator_parameter_value // 22010 : data_exception { using data_exception::data_exception; }; struct invalid_parameter_value // 22023 : data_exception { using data_exception::data_exception; }; struct invalid_preceding_or_following_size // 22013 : data_exception { using data_exception::data_exception; }; struct invalid_regular_expression // 2201B : data_exception { using data_exception::data_exception; }; struct invalid_row_count_in_limit_clause // 2201W : data_exception { using data_exception::data_exception; }; struct invalid_row_count_in_result_offset_clause // 2201X : data_exception { using data_exception::data_exception; }; struct invalid_tablesample_argument // 2202H : data_exception { using data_exception::data_exception; }; struct invalid_tablesample_repeat // 2202G : data_exception { using data_exception::data_exception; }; struct invalid_time_zone_displacement_value // 22009 : data_exception { using data_exception::data_exception; }; struct invalid_use_of_escape_character // 2200C : data_exception { using data_exception::data_exception; }; struct most_specific_type_mismatch // 2200G : data_exception { using data_exception::data_exception; }; struct null_value_not_allowed // 22004 : data_exception { using data_exception::data_exception; }; struct null_value_no_indicator_parameter // 22002 : data_exception { using data_exception::data_exception; }; struct numeric_value_out_of_range // 22003 : data_exception { using data_exception::data_exception; }; struct sequence_generator_limit_exceeded // 2200H : data_exception { using data_exception::data_exception; }; struct string_data_length_mismatch // 22026 : data_exception { using data_exception::data_exception; }; template<> struct string_data_right_truncation< data_exception > // 22001 : data_exception { using data_exception::data_exception; }; struct substring_error // 22011 : data_exception { using data_exception::data_exception; }; struct trim_error // 22027 : data_exception { using data_exception::data_exception; }; struct unterminated_c_string // 22024 : data_exception { using data_exception::data_exception; }; struct zero_length_character_string // 2200F : data_exception { using data_exception::data_exception; }; struct floating_point_exception // 22P01 : data_exception { using data_exception::data_exception; }; struct invalid_text_representation // 22P02 : data_exception { using data_exception::data_exception; }; struct invalid_binary_representation // 22P03 : data_exception { using data_exception::data_exception; }; struct bad_copy_file_format // 22P04 : data_exception { using data_exception::data_exception; }; struct untranslatable_character // 22P05 : data_exception { using data_exception::data_exception; }; struct not_an_xml_document // 2200L : data_exception { using data_exception::data_exception; }; struct invalid_xml_document // 2200M : data_exception { using data_exception::data_exception; }; struct invalid_xml_content // 2200N : data_exception { using data_exception::data_exception; }; struct invalid_xml_comment // 2200S : data_exception { using data_exception::data_exception; }; struct invalid_xml_processing_instruction // 2200T : data_exception { using data_exception::data_exception; }; struct duplicate_json_object_key_value // 22030 : data_exception { using data_exception::data_exception; }; struct invalid_argument_for_sql_json_datetime_function // 22031 : data_exception { using data_exception::data_exception; }; struct invalid_json_text // 22032 : data_exception { using data_exception::data_exception; }; struct invalid_sql_json_subscript // 22033 : data_exception { using data_exception::data_exception; }; struct more_than_one_sql_json_item // 22034 : data_exception { using data_exception::data_exception; }; struct no_sql_json_item // 22035 : data_exception { using data_exception::data_exception; }; struct non_numeric_sql_json_item // 22036 : data_exception { using data_exception::data_exception; }; struct non_unique_keys_in_a_json_object // 22037 : data_exception { using data_exception::data_exception; }; struct singleton_sql_json_item_required // 22038 : data_exception { using data_exception::data_exception; }; struct sql_json_array_not_found // 22039 : data_exception { using data_exception::data_exception; }; struct sql_json_member_not_found // 2203A : data_exception { using data_exception::data_exception; }; struct sql_json_number_not_found // 2203B : data_exception { using data_exception::data_exception; }; struct sql_json_object_not_found // 2203C : data_exception { using data_exception::data_exception; }; struct too_many_json_array_elements // 2203D : data_exception { using data_exception::data_exception; }; struct too_many_json_object_members // 2203E : data_exception { using data_exception::data_exception; }; struct sql_json_scalar_required // 2203F : data_exception { using data_exception::data_exception; }; struct integrity_constraint_violation // 23xxx : sql_error { using sql_error::sql_error; }; struct restrict_violation // 23001 : integrity_constraint_violation { using integrity_constraint_violation::integrity_constraint_violation; }; struct not_null_violation // 23502 : integrity_constraint_violation { using integrity_constraint_violation::integrity_constraint_violation; }; struct foreign_key_violation // 23503 : integrity_constraint_violation { using integrity_constraint_violation::integrity_constraint_violation; }; struct unique_violation // 23505 : integrity_constraint_violation { using integrity_constraint_violation::integrity_constraint_violation; }; struct check_violation // 23514 : integrity_constraint_violation { using integrity_constraint_violation::integrity_constraint_violation; }; struct exclusion_violation // 23P01 : integrity_constraint_violation { using integrity_constraint_violation::integrity_constraint_violation; }; struct invalid_cursor_state // 24xxx : sql_error { using sql_error::sql_error; }; struct invalid_transaction_state // 25xxx : sql_error { using sql_error::sql_error; }; struct active_sql_transaction // 25001 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct branch_transaction_already_active // 25002 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct held_cursor_requires_same_isolation_level // 25008 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct inappropriate_access_mode_for_branch_transaction // 25003 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct inappropriate_isolation_level_for_branch_transaction // 25004 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct no_active_sql_transaction_for_branch_transaction // 25005 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct read_only_sql_transaction // 25006 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct schema_and_data_statement_mixing_not_supported // 25007 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct no_active_sql_transaction // 25P01 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct in_failed_sql_transaction // 25P02 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct idle_in_transaction_session_timeout // 25P03 : invalid_transaction_state { using invalid_transaction_state::invalid_transaction_state; }; struct invalid_sql_statement_name // 26xxx : sql_error { using sql_error::sql_error; }; struct triggered_data_change_violation // 27xxx : sql_error { using sql_error::sql_error; }; struct invalid_authorization_specification // 28xxx : sql_error { using sql_error::sql_error; }; struct invalid_password // 28P01 : invalid_authorization_specification { using invalid_authorization_specification::invalid_authorization_specification; }; struct dependent_privilege_descriptors_still_exist // 2Bxxx : sql_error { using sql_error::sql_error; }; struct dependent_objects_still_exist // 2BP01 : dependent_privilege_descriptors_still_exist { using dependent_privilege_descriptors_still_exist::dependent_privilege_descriptors_still_exist; }; struct invalid_transaction_termination // 2Dxxx : sql_error { using sql_error::sql_error; }; struct sql_routine_exception // 2Fxxx : sql_error { using sql_error::sql_error; }; template<> struct modifying_sql_data_not_permitted< sql_routine_exception > // 2F002 : sql_routine_exception { using sql_routine_exception::sql_routine_exception; }; template<> struct prohibited_sql_statement_attempted< sql_routine_exception > // 2F003 : sql_routine_exception { using sql_routine_exception::sql_routine_exception; }; template<> struct reading_sql_data_not_permitted< sql_routine_exception > // 2F004 : sql_routine_exception { using sql_routine_exception::sql_routine_exception; }; struct function_executed_no_return_statement // 2F005 : sql_routine_exception { using sql_routine_exception::sql_routine_exception; }; struct invalid_cursor_name // 34xxx : sql_error { using sql_error::sql_error; }; struct external_routine_exception // 38xxx : sql_error { using sql_error::sql_error; }; struct containing_sql_not_permitted // 38001 : external_routine_exception { using external_routine_exception::external_routine_exception; }; template<> struct modifying_sql_data_not_permitted< external_routine_exception > // 38002 : external_routine_exception { using external_routine_exception::external_routine_exception; }; template<> struct prohibited_sql_statement_attempted< external_routine_exception > // 38003 : external_routine_exception { using external_routine_exception::external_routine_exception; }; template<> struct reading_sql_data_not_permitted< external_routine_exception > // 38004 : external_routine_exception { using external_routine_exception::external_routine_exception; }; struct external_routine_invocation_exception // 39xxx : sql_error { using sql_error::sql_error; }; struct invalid_sqlstate_returned // 39001 : external_routine_invocation_exception { using external_routine_invocation_exception::external_routine_invocation_exception; }; struct external_null_value_not_allowed // 39004 : external_routine_invocation_exception { using external_routine_invocation_exception::external_routine_invocation_exception; }; struct trigger_protocol_violated // 39P01 : external_routine_invocation_exception { using external_routine_invocation_exception::external_routine_invocation_exception; }; struct srf_protocol_violated // 39P02 : external_routine_invocation_exception { using external_routine_invocation_exception::external_routine_invocation_exception; }; struct event_trigger_protocol_violated // 39P03 : external_routine_invocation_exception { using external_routine_invocation_exception::external_routine_invocation_exception; }; struct savepoint_exception // 3Bxxx : sql_error { using sql_error::sql_error; }; struct invalid_savepoint_specification // 3B001 : savepoint_exception { using savepoint_exception::savepoint_exception; }; struct invalid_catalog_name // 3Dxxx : sql_error { using sql_error::sql_error; }; struct invalid_schema_name // 3Fxxx : sql_error { using sql_error::sql_error; }; struct transaction_rollback // 40xxx : sql_error { using sql_error::sql_error; }; struct serialization_failure // 40001 : transaction_rollback { using transaction_rollback::transaction_rollback; }; struct transaction_integrity_constraint_violation // 40002 : transaction_rollback { using transaction_rollback::transaction_rollback; }; struct statement_completion_unknown // 40003 : transaction_rollback { using transaction_rollback::transaction_rollback; }; struct deadlock_detected // 40P01 : transaction_rollback { using transaction_rollback::transaction_rollback; }; struct syntax_error_or_access_rule_violation // 42xxx : sql_error { using sql_error::sql_error; }; struct insufficient_privilege // 42501 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct syntax_error // 42601 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_name // 42602 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_column_definition // 42611 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct name_too_long // 42622 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct duplicate_column // 42701 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct ambiguous_column // 42702 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct undefined_column // 42703 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct undefined_object // 42704 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct duplicate_object // 42710 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct duplicate_alias // 42712 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct duplicate_function // 42723 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct ambiguous_function // 42725 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct grouping_error // 42803 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct datatype_mismatch // 42804 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct wrong_object_type // 42809 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_foreign_key // 42830 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct cannot_coerce // 42846 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct undefined_function // 42883 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct generated_always // 428C9 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct reserved_name // 42939 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct undefined_table // 42P01 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct undefined_parameter // 42P02 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct duplicate_cursor // 42P03 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct duplicate_database // 42P04 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct duplicate_prepared_statement // 42P05 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct duplicate_schema // 42P06 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct duplicate_table // 42P07 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct ambiguous_parameter // 42P08 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct ambiguous_alias // 42P09 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_column_reference // 42P10 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_cursor_definition // 42P11 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_database_definition // 42P12 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_function_definition // 42P13 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_prepared_statement_definition // 42P14 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_schema_definition // 42P15 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_table_definition // 42P16 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_object_definition // 42P17 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct indeterminate_datatype // 42P18 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct invalid_recursion // 42P19 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct windowing_error // 42P20 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct collation_mismatch // 42P21 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct indeterminate_collation // 42P22 : syntax_error_or_access_rule_violation { using syntax_error_or_access_rule_violation::syntax_error_or_access_rule_violation; }; struct with_check_option_violation // 44xxx : sql_error { using sql_error::sql_error; }; struct insufficient_resources // 53xxx : sql_error { using sql_error::sql_error; }; struct disk_full // 53100 : insufficient_resources { using insufficient_resources::insufficient_resources; }; struct out_of_memory // 53200 : insufficient_resources { using insufficient_resources::insufficient_resources; }; struct too_many_connections // 53300 : insufficient_resources { using insufficient_resources::insufficient_resources; }; struct configuration_limit_exceeded // 53400 : insufficient_resources { using insufficient_resources::insufficient_resources; }; struct program_limit_exceeded // 54xxx : sql_error { using sql_error::sql_error; }; struct statement_too_complex // 54001 : program_limit_exceeded { using program_limit_exceeded::program_limit_exceeded; }; struct too_many_columns // 54011 : program_limit_exceeded { using program_limit_exceeded::program_limit_exceeded; }; struct too_many_arguments // 54023 : program_limit_exceeded { using program_limit_exceeded::program_limit_exceeded; }; struct object_not_in_prerequisite_state // 55xxx : sql_error { using sql_error::sql_error; }; struct object_in_use // 55006 : object_not_in_prerequisite_state { using object_not_in_prerequisite_state::object_not_in_prerequisite_state; }; struct cant_change_runtime_param // 55P02 : object_not_in_prerequisite_state { using object_not_in_prerequisite_state::object_not_in_prerequisite_state; }; struct lock_not_available // 55P03 : object_not_in_prerequisite_state { using object_not_in_prerequisite_state::object_not_in_prerequisite_state; }; struct unsafe_new_enum_value_usage // 55P04 : object_not_in_prerequisite_state { using object_not_in_prerequisite_state::object_not_in_prerequisite_state; }; struct operator_intervention // 57xxx : sql_error { using sql_error::sql_error; }; struct query_canceled // 57014 : operator_intervention { using operator_intervention::operator_intervention; }; struct admin_shutdown // 57P01 : operator_intervention { using operator_intervention::operator_intervention; }; struct crash_shutdown // 57P02 : operator_intervention { using operator_intervention::operator_intervention; }; struct cannot_connect_now // 57P03 : operator_intervention { using operator_intervention::operator_intervention; }; struct database_dropped // 57P04 : operator_intervention { using operator_intervention::operator_intervention; }; struct system_error // 58xxx : sql_error { using sql_error::sql_error; }; struct io_error // 58030 : system_error { using system_error::system_error; }; struct undefined_file // 58P01 : system_error { using system_error::system_error; }; struct duplicate_file // 58P02 : system_error { using system_error::system_error; }; struct snapshot_too_old // 72xxx : sql_error { using sql_error::sql_error; }; struct config_file_error // F0xxx : sql_error { using sql_error::sql_error; }; struct lock_file_exists // F0001 : config_file_error { using config_file_error::config_file_error; }; struct fdw_error // HVxxx : sql_error { using sql_error::sql_error; }; struct fdw_out_of_memory // HV001 : fdw_error { using fdw_error::fdw_error; }; struct fdw_dynamic_parameter_value_needed // HV002 : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_data_type // HV004 : fdw_error { using fdw_error::fdw_error; }; struct fdw_column_name_not_found // HV005 : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_data_type_descriptors // HV006 : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_column_name // HV007 : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_column_number // HV008 : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_use_of_null_pointer // HV009 : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_string_format // HV00A : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_handle // HV00B : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_option_index // HV00C : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_option_name // HV00D : fdw_error { using fdw_error::fdw_error; }; struct fdw_option_name_not_found // HV00J : fdw_error { using fdw_error::fdw_error; }; struct fdw_reply_handle // HV00K : fdw_error { using fdw_error::fdw_error; }; struct fdw_unable_to_create_execution // HV00L : fdw_error { using fdw_error::fdw_error; }; struct fdw_unable_to_create_reply // HV00M : fdw_error { using fdw_error::fdw_error; }; struct fdw_unable_to_establish_connection // HV00N : fdw_error { using fdw_error::fdw_error; }; struct fdw_no_schemas // HV00P : fdw_error { using fdw_error::fdw_error; }; struct fdw_schema_not_found // HV00Q : fdw_error { using fdw_error::fdw_error; }; struct fdw_table_not_found // HV00R : fdw_error { using fdw_error::fdw_error; }; struct fdw_function_sequence_error // HV010 : fdw_error { using fdw_error::fdw_error; }; struct fdw_too_many_handles // HV014 : fdw_error { using fdw_error::fdw_error; }; struct fdw_inconsistent_descriptor_information // HV021 : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_attribute_value // HV024 : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_string_length_or_buffer_length // HV090 : fdw_error { using fdw_error::fdw_error; }; struct fdw_invalid_descriptor_field_identifier // HV091 : fdw_error { using fdw_error::fdw_error; }; struct plpgsql_error // P0xxx : sql_error { using sql_error::sql_error; }; struct raise_exception // P0001 : plpgsql_error { using plpgsql_error::plpgsql_error; }; struct no_data_found // P0002 : plpgsql_error { using plpgsql_error::plpgsql_error; }; struct too_many_rows // P0003 : plpgsql_error { using plpgsql_error::plpgsql_error; }; struct assert_failure // P0004 : plpgsql_error { using plpgsql_error::plpgsql_error; }; struct internal_error // XXxxx : sql_error { using sql_error::sql_error; }; struct data_corrupted // XX001 : internal_error { using internal_error::internal_error; }; struct index_corrupted // XX002 : internal_error { using internal_error::internal_error; }; namespace internal { [[noreturn]] void throw_sqlstate( PGresult* pgresult ); [[noreturn]] void throw_sqlstate( const char* error_message, const std::string_view sql_state ); } // namespace internal } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/field.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_FIELD_HPP #define TAO_PQ_FIELD_HPP #include #include #include #include #include #include namespace tao::pq { class row; class field { private: friend class row; const row* m_row = nullptr; std::size_t m_column = 0; field() = default; field( const row& row, const std::size_t column ) noexcept : m_row( &row ), m_column( column ) {} public: [[nodiscard]] auto name() const -> std::string; [[nodiscard]] auto index() const noexcept -> std::size_t; [[nodiscard]] auto is_null() const -> bool; [[nodiscard]] auto get() const -> const char*; template< result_type T > requires( result_traits_size< T > == 1 ) [[nodiscard]] auto as() const -> T; // implemented in row.hpp template< result_type T > requires( result_traits_size< T > == 1 ) [[nodiscard]] auto optional() const { return as< std::optional< T > >(); } [[nodiscard]] auto operator==( null_t /*unused*/ ) const { return is_null(); } friend void swap( field& lhs, field& rhs ) noexcept { std::swap( lhs.m_row, rhs.m_row ); std::swap( lhs.m_column, rhs.m_column ); } }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/internal/aggregate.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_AGGREGATE_HPP #define TAO_PQ_INTERNAL_AGGREGATE_HPP #include #include #if !( ( __cpp_structured_bindings >= 202400L ) && ( __cplusplus >= 202400L ) ) #include #include #endif namespace tao::pq::internal { // clang-format off #if ( __cpp_structured_bindings >= 202400L ) && ( __cplusplus >= 202400L ) template< typename T > requires( std::is_aggregate_v< T > && !std::is_empty_v< T > && !std::is_union_v< T > ) constexpr auto tie_aggregate( const T& value ) noexcept { const auto& [ ...values ] = value; return std::tie( values... ); } #else // clang-format on struct convert_to_any { template< typename T > constexpr operator T() const noexcept; }; template< std::size_t > using indexed_convert_to_any = convert_to_any; template< typename, typename > inline constexpr bool check_aggregate_args_impl = false; template< typename T, std::size_t... Is > requires requires { T{ std::declval< indexed_convert_to_any< Is > >()... }; } inline constexpr bool check_aggregate_args_impl< T, std::index_sequence< Is... > > = true; template< typename T, std::size_t N > inline constexpr bool check_aggregate_args = check_aggregate_args_impl< T, std::make_index_sequence< N > >; template< typename T, std::size_t N = 1 > inline constexpr std::size_t minimum_aggregate_args = minimum_aggregate_args< T, N + 1 >; template< typename T, std::size_t N > requires check_aggregate_args< T, N > inline constexpr std::size_t minimum_aggregate_args< T, N > = N; template< typename T, std::size_t N = minimum_aggregate_args< T > > inline constexpr std::size_t count_aggregate_args = N - 1; template< typename T, std::size_t N > requires check_aggregate_args< T, N > inline constexpr std::size_t count_aggregate_args< T, N > = count_aggregate_args< T, N + 1 >; #define TAO_PQ_TIE( N, ... ) \ if constexpr( cnt == N ) { \ const auto& [ __VA_ARGS__ ] = value; \ return std::tie( __VA_ARGS__ ); \ } \ else \ static_assert( true ) #define TAO_PQ_10( P ) P##0, P##1, P##2, P##3, P##4, P##5, P##6, P##7, P##8, P##9 #define TAO_PQ_TIE10( N, ... ) \ TAO_PQ_TIE( N, __VA_ARGS__ ); \ TAO_PQ_TIE( N + 1, __VA_ARGS__, a ); \ TAO_PQ_TIE( N + 2, __VA_ARGS__, a, b ); \ TAO_PQ_TIE( N + 3, __VA_ARGS__, a, b, c ); \ TAO_PQ_TIE( N + 4, __VA_ARGS__, a, b, c, d ); \ TAO_PQ_TIE( N + 5, __VA_ARGS__, a, b, c, d, e ); \ TAO_PQ_TIE( N + 6, __VA_ARGS__, a, b, c, d, e, f ); \ TAO_PQ_TIE( N + 7, __VA_ARGS__, a, b, c, d, e, f, g ); \ TAO_PQ_TIE( N + 8, __VA_ARGS__, a, b, c, d, e, f, g, h ); \ TAO_PQ_TIE( N + 9, __VA_ARGS__, a, b, c, d, e, f, g, h, i ); template< typename T > requires( std::is_aggregate_v< T > && !std::is_empty_v< T > && !std::is_union_v< T > ) constexpr auto tie_aggregate( const T& value ) noexcept { constexpr auto cnt = count_aggregate_args< T >; TAO_PQ_TIE( 1, a ); TAO_PQ_TIE( 2, a, b ); TAO_PQ_TIE( 3, a, b, c ); TAO_PQ_TIE( 4, a, b, c, d ); TAO_PQ_TIE( 5, a, b, c, d, e ); TAO_PQ_TIE( 6, a, b, c, d, e, f ); TAO_PQ_TIE( 7, a, b, c, d, e, f, g ); TAO_PQ_TIE( 8, a, b, c, d, e, f, g, h ); TAO_PQ_TIE( 9, a, b, c, d, e, f, g, h, i ); TAO_PQ_TIE10( 10, TAO_PQ_10( a ) ); TAO_PQ_TIE10( 20, TAO_PQ_10( a ), TAO_PQ_10( b ) ); TAO_PQ_TIE10( 30, TAO_PQ_10( a ), TAO_PQ_10( b ), TAO_PQ_10( c ) ); TAO_PQ_TIE10( 40, TAO_PQ_10( a ), TAO_PQ_10( b ), TAO_PQ_10( c ), TAO_PQ_10( d ) ); TAO_PQ_TIE10( 50, TAO_PQ_10( a ), TAO_PQ_10( b ), TAO_PQ_10( c ), TAO_PQ_10( d ), TAO_PQ_10( e ) ); TAO_PQ_TIE10( 60, TAO_PQ_10( a ), TAO_PQ_10( b ), TAO_PQ_10( c ), TAO_PQ_10( d ), TAO_PQ_10( e ), TAO_PQ_10( f ) ); TAO_PQ_TIE10( 70, TAO_PQ_10( a ), TAO_PQ_10( b ), TAO_PQ_10( c ), TAO_PQ_10( d ), TAO_PQ_10( e ), TAO_PQ_10( f ), TAO_PQ_10( g ) ); TAO_PQ_TIE10( 80, TAO_PQ_10( a ), TAO_PQ_10( b ), TAO_PQ_10( c ), TAO_PQ_10( d ), TAO_PQ_10( e ), TAO_PQ_10( f ), TAO_PQ_10( g ), TAO_PQ_10( h ) ); TAO_PQ_TIE10( 90, TAO_PQ_10( a ), TAO_PQ_10( b ), TAO_PQ_10( c ), TAO_PQ_10( d ), TAO_PQ_10( e ), TAO_PQ_10( f ), TAO_PQ_10( g ), TAO_PQ_10( h ), TAO_PQ_10( i ) ); } #undef TAO_PQ_TIE #undef TAO_PQ_10 #undef TAO_PQ_TIE10 #endif } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/internal/demangle.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_DEMANGLE_HPP #define TAO_PQ_INTERNAL_DEMANGLE_HPP #include #include namespace tao::pq::internal { [[nodiscard]] auto demangle( const char* const symbol ) -> std::string; [[nodiscard]] inline auto demangle( const std::type_info& type_info ) { return demangle( type_info.name() ); } template< typename T > [[nodiscard]] auto demangle() { return internal::demangle( typeid( T ) ); } } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/internal/exclusive_scan.hpp ================================================ // Copyright (c) 2019-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_EXCLUSIVE_SCAN_HPP #define TAO_PQ_INTERNAL_EXCLUSIVE_SCAN_HPP #include #include namespace tao::pq::internal { template< typename S, typename = std::make_index_sequence< S::size() > > struct exclusive_scan; template< typename T, T... Ns, std::size_t... Is > struct exclusive_scan< std::integer_sequence< T, Ns... >, std::index_sequence< Is... > > { template< std::size_t I > static constexpr T partial_sum = ( T( 0 ) + ... + ( ( Is < I ) ? Ns : T( 0 ) ) ); using type = std::integer_sequence< T, partial_sum< Is >... >; }; template< typename S > using exclusive_scan_t = typename exclusive_scan< S >::type; } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/internal/format_as.hpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_FORMAT_AS_HPP #define TAO_PQ_INTERNAL_FORMAT_AS_HPP #include #include #include template< typename T > requires requires { taopq_format_as( std::declval< T >() ); } struct std::formatter< T > : std::formatter< decltype( taopq_format_as( std::declval< T >() ) ) > { auto format( const T& v, auto& ctx ) const { return std::formatter< decltype( taopq_format_as( v ) ) >::format( taopq_format_as( v ), ctx ); } }; template< typename T > requires requires { taopq_format_as( std::declval< T >() ); } auto operator<<( std::ostream& os, const T& v ) -> std::ostream& { return os << taopq_format_as( v ); } #endif ================================================ FILE: include/tao/pq/internal/from_chars.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_FROM_CHARS_HPP #define TAO_PQ_INTERNAL_FROM_CHARS_HPP #include #include #include #include #include #include #include namespace tao::pq::internal { template< typename T > [[nodiscard]] auto from_chars( const std::string_view value ) -> T { T result; const auto [ ptr, ec ] = std::from_chars( value.data(), value.data() + value.size(), result ); if( ec == std::errc() ) { if( ptr == value.data() + value.size() ) { return result; } } const auto type = internal::demangle< T >(); if( ( ec == std::errc() ) || ( ec == std::errc::invalid_argument ) ) { throw std::invalid_argument( std::format( "tao::pq::internal::from_chars<{}>(): {}", type, value ) ); } if( ec == std::errc::result_out_of_range ) { throw std::out_of_range( std::format( "tao::pq::internal::from_chars<{}>(): {}", type, value ) ); } TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE } } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/internal/gen.hpp ================================================ // Copyright (c) 2019-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_GEN_HPP #define TAO_PQ_INTERNAL_GEN_HPP #include #include #include namespace tao::pq::internal { template< typename, typename, typename > struct make; template< std::size_t... Is, std::size_t... Js, std::size_t... Ns > struct make< std::index_sequence< Is... >, std::index_sequence< Js... >, std::index_sequence< Ns... > > { template< std::size_t I > static constexpr std::size_t outer = ( 0 + ... + ( ( Ns <= I ) ? 1 : 0 ) ) - 1; template< std::size_t J > static constexpr std::size_t select = ( 0 + ... + ( ( Js == J ) ? Ns : 0 ) ); template< std::size_t I > static constexpr std::size_t inner = I - select< outer< I > >; using outer_sequence = std::index_sequence< outer< Is >... >; using inner_sequence = std::index_sequence< inner< Is >... >; }; template< std::size_t... Ns > using gen = make< std::make_index_sequence< ( 0 + ... + Ns ) >, std::make_index_sequence< sizeof...( Ns ) >, exclusive_scan_t< std::index_sequence< Ns... > > >; } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/internal/parameter_traits_helper.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_PARAMETER_TRAITS_HELPER_HPP #define TAO_PQ_INTERNAL_PARAMETER_TRAITS_HELPER_HPP #include #include #include #include #include #include namespace tao::pq::internal { struct char_pointer_helper { protected: const char* const m_p; public: explicit char_pointer_helper( const char* p ) noexcept : m_p( p ) {} static constexpr std::size_t columns = 1; static constexpr bool self_contained = false; template< std::size_t I > [[nodiscard]] static constexpr auto type() noexcept -> oid { return oid::invalid; } template< std::size_t I > [[nodiscard]] constexpr auto value() const noexcept -> const char* { return m_p; } template< std::size_t I > [[nodiscard]] static constexpr auto length() noexcept -> int { return 0; } template< std::size_t I > [[nodiscard]] static constexpr auto format() noexcept -> int { return 0; } }; // note the size of the buffer is hardcoded struct buffer_helper { protected: char m_buffer[ 32 ]; public: static constexpr std::size_t columns = 1; static constexpr bool self_contained = true; template< std::size_t I > [[nodiscard]] static constexpr auto type() noexcept -> oid { return oid::invalid; } template< std::size_t I > [[nodiscard]] auto value() const noexcept -> const char* { return m_buffer; } template< std::size_t I > [[nodiscard]] static constexpr auto length() noexcept -> int { return 0; } template< std::size_t I > [[nodiscard]] static constexpr auto format() noexcept -> int { return 0; } template< std::size_t I > void element( std::string& data ) const { data += m_buffer; } template< std::size_t I > void copy_to( std::string& data ) const { data += m_buffer; } }; // all uses of this helper need to fit into the buffer, see above struct to_chars_helper : buffer_helper { explicit to_chars_helper( const auto v ) noexcept { const auto [ ptr, ec ] = std::to_chars( std::begin( m_buffer ), std::end( m_buffer ), v ); assert( ec == std::errc() ); *ptr = '\0'; } }; } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/internal/poll.hpp ================================================ // Copyright (c) 2023-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_POLL_HPP #define TAO_PQ_INTERNAL_POLL_HPP #include namespace tao::pq::internal { [[nodiscard]] auto poll( const int socket, const bool wait_for_write, const int timeout_ms ) -> pq::poll::status; } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/internal/pool.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_POOL_HPP #define TAO_PQ_INTERNAL_POOL_HPP #include #include #include #include #include #include #include namespace tao::pq::internal { template< typename T > class pool : public std::enable_shared_from_this< pool< T > > { private: std::list< std::shared_ptr< T > > m_items; mutable std::mutex m_mutex; std::atomic< std::size_t > m_attached = 0; struct deleter final { std::weak_ptr< pool > m_pool; deleter() = default; explicit deleter( std::weak_ptr< pool >&& p ) noexcept : m_pool( std::move( p ) ) {} void operator()( T* item ) const noexcept { std::unique_ptr< T > up( item ); if( const auto p = m_pool.lock() ) { --( p->m_attached ); p->push( up ); } } }; protected: pool() = default; virtual ~pool() = default; // create a new T [[nodiscard]] virtual auto v_create() const -> std::unique_ptr< T > = 0; [[nodiscard]] virtual auto v_is_valid( T& ) const noexcept -> bool = 0; void push( std::unique_ptr< T >& up ) noexcept { if( this->v_is_valid( *up ) ) { std::shared_ptr< T > sp( up.release(), deleter() ); const std::lock_guard lock( m_mutex ); // potentially throws -> calls abort() due to noexcept! m_items.emplace_back( std::move( sp ) ); } } [[nodiscard]] auto pull() noexcept { std::shared_ptr< T > nrv; const std::lock_guard lock( m_mutex ); if( !m_items.empty() ) { nrv = std::move( m_items.back() ); m_items.pop_back(); } return nrv; } public: pool( const pool& ) = delete; pool( pool&& ) = delete; void operator=( const pool& ) = delete; void operator=( pool&& ) = delete; static void attach( const std::shared_ptr< T >& sp, std::weak_ptr< pool >&& p ) noexcept { const auto d = std::get_deleter< deleter >( sp ); assert( d ); if( const auto o = d->m_pool.lock() ) { --( o->m_attached ); } d->m_pool = std::move( p ); if( const auto n = d->m_pool.lock() ) { ++( n->m_attached ); } } static void detach( const std::shared_ptr< T >& sp ) noexcept { const auto d = std::get_deleter< deleter >( sp ); assert( d ); if( const auto o = d->m_pool.lock() ) { --( o->m_attached ); } d->m_pool.reset(); } // create a new T which is put into the pool when no longer used [[nodiscard]] auto create() -> std::shared_ptr< T > { const std::shared_ptr< T > c{ v_create().release(), pool::deleter( this->weak_from_this() ) }; ++m_attached; return c; } // get an instance from the pool or create a new one if necessary [[nodiscard]] auto get() -> std::shared_ptr< T > { while( const auto sp = pull() ) { if( this->v_is_valid( *sp ) ) { const auto d = std::get_deleter< deleter >( sp ); assert( d ); d->m_pool = this->weak_from_this(); ++m_attached; return sp; } } return create(); } [[nodiscard]] auto empty() const noexcept -> bool { const std::lock_guard lock( m_mutex ); return m_items.empty(); } [[nodiscard]] auto size() const noexcept -> std::size_t { const std::lock_guard lock( m_mutex ); return m_items.size(); } [[nodiscard]] auto attached() const noexcept -> std::size_t { return m_attached; } void erase_invalid() { std::list< std::shared_ptr< T > > deferred_delete; const std::lock_guard lock( m_mutex ); auto it = m_items.begin(); while( it != m_items.end() ) { if( !this->v_is_valid( **it ) ) { deferred_delete.splice( deferred_delete.end(), m_items, it++ ); } else { ++it; } } } }; } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/internal/resize_uninitialized.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_RESIZE_UNINITIALIZED_HPP #define TAO_PQ_INTERNAL_RESIZE_UNINITIALIZED_HPP #include #include #include namespace tao::pq::internal { // the below uses a hack to call private member functions of a class, described here: // https://github.com/facebook/folly/blob/master/folly/memory/UninitializedMemoryHacks.h namespace // NOLINT(google-build-namespaces) { struct odr_helper; void resize_uninitialized_proxy( std::string& v, const std::size_t n ) noexcept; #if defined( _LIBCPP_STRING ) // ...create a proxy to generate the actual implementation of the above function... template< typename T, auto F > struct string_proxy { // ...define the function declared above... friend void resize_uninitialized_proxy( T& v, const std::size_t n ) noexcept { ( v.*F )( n ); // v.__set_size( n ); v[ v.size() ] = typename T::value_type( 0 ); } }; // ...and here's the actual "trick": an explicit template instantiation skips the access checks, // so you can reference private members and forward them to the above proxy! template struct string_proxy< std::string, &std::string::__set_size >; #elif defined( _GLIBCXX_STRING ) && _GLIBCXX_USE_CXX11_ABI // NOLINT(misc-include-cleaner) template< typename T, auto F > struct string_proxy { friend void resize_uninitialized_proxy( T& v, const std::size_t n ) noexcept { ( v.*F )( n ); // v._M_set_length( n ); } }; template struct string_proxy< std::string, &std::string::_M_set_length >; #elif defined( _GLIBCXX_STRING ) template< typename T, auto F > struct string_proxy { friend void resize_uninitialized_proxy( T& v, const std::size_t n ) noexcept { // v._M_rep()->_M_set_length_and_sharable( n ); ( v.*F )()->_M_set_length_and_sharable( n ); } }; template struct string_proxy< std::string, &std::string::_M_rep >; #elif defined( _MSC_VER ) template< typename T, auto F > struct string_proxy { friend void resize_uninitialized_proxy( T& v, const std::size_t n ) noexcept { ( v.*F )( n ); // v._Eos( n ); } }; template struct string_proxy< std::string, &std::string::_Eos >; #else #error "No implementation for resize_uninitialized for std::string available on this platform." #endif #if defined( _LIBCPP_VECTOR ) void resize_uninitialized_proxy( std::vector< std::byte >& v, const std::size_t n ) noexcept; template< typename T, auto M > struct vector_proxy { friend void resize_uninitialized_proxy( T& v, const std::size_t n ) noexcept { // v.__end_ = v.data() + n; v.*M = v.data() + n; #ifndef _LIBCPP_HAS_NO_ASAN __sanitizer_annotate_contiguous_container( v.data(), v.data() + v.capacity(), v.data() + v.size(), v.data() + n ); #endif } }; template struct vector_proxy< std::vector< std::byte >, &std::vector< std::byte >::__end_ >; #elif defined( _GLIBCXX_VECTOR ) void resize_uninitialized_proxy( std::vector< std::byte >& v, const std::size_t n ) noexcept; template< typename T, typename B, auto Mimpl, auto Mfinish > struct vector_proxy { friend void resize_uninitialized_proxy( T& v, const std::size_t n ) noexcept { // v._M_impl._M_finish = v.data() + n; reinterpret_cast< B& >( v ).*Mimpl.*Mfinish = v.data() + n; } }; template struct vector_proxy< std::vector< std::byte >, std::vector< std::byte >::_Base, &std::vector< std::byte >::_M_impl, &decltype( std::declval< std::vector< std::byte > >()._M_impl )::_M_finish >; #elif defined( _MSC_VER ) void resize_uninitialized_proxy( std::vector< std::byte >& v, const std::size_t n ) noexcept; template< typename T, auto Mypair, auto Myval2, auto Mylast > struct vector_proxy { friend void resize_uninitialized_proxy( T& v, const std::size_t n ) noexcept { // v._Mypair._Myval2._Mylast = v.data() + n; v.*Mypair.*Myval2.*Mylast = v.data() + n; } }; template struct vector_proxy< std::vector< std::byte >, &std::vector< std::byte >::_Mypair, &decltype( std::declval< std::vector< std::byte > >()._Mypair )::_Myval2, &decltype( std::declval< std::vector< std::byte > >()._Mypair._Myval2 )::_Mylast >; #else // generic version struct no_init_byte { std::byte b; no_init_byte() noexcept {} // NOLINT(modernize-use-equals-default) }; static_assert( sizeof( std::vector< std::byte > ) == sizeof( std::vector< no_init_byte > ) ); static_assert( alignof( std::vector< std::byte > ) == alignof( std::vector< no_init_byte > ) ); inline void resize_uninitialized_proxy( std::vector< std::byte >& v, const std::size_t n ) noexcept { // undefined behaviour? reinterpret_cast< std::vector< no_init_byte >& >( v ).resize( n ); } #endif } // namespace template< typename = odr_helper > void resize_uninitialized( std::string& v, const std::size_t n ) { if( n <= v.size() ) { v.resize( n ); } else { if( n > v.capacity() ) { v.reserve( n ); } internal::resize_uninitialized_proxy( v, n ); } } template< typename = odr_helper > void resize_uninitialized( std::vector< std::byte >& v, const std::size_t n ) { if( n <= v.size() ) { v.resize( n ); } else { if( n > v.capacity() ) { v.reserve( n ); } internal::resize_uninitialized_proxy( v, n ); } } } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/internal/strtox.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_STRTOX_HPP #define TAO_PQ_INTERNAL_STRTOX_HPP namespace tao::pq::internal { [[nodiscard]] auto strtof( const char* input ) -> float; [[nodiscard]] auto strtod( const char* input ) -> double; [[nodiscard]] auto strtold( const char* input ) -> long double; } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/internal/unreachable.hpp ================================================ // Copyright (c) 2020-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_UNREACHABLE_HPP #define TAO_PQ_INTERNAL_UNREACHABLE_HPP #if defined( _MSC_VER ) && !defined( __clang__ ) #define TAO_PQ_INTERNAL_UNREACHABLE __assume( false ) #else #define TAO_PQ_INTERNAL_UNREACHABLE __builtin_unreachable() #endif #endif ================================================ FILE: include/tao/pq/internal/zsv.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_INTERNAL_ZSV_HPP #define TAO_PQ_INTERNAL_ZSV_HPP #include #include #include namespace tao::pq::internal { // zero-terminated string view struct zsv { const char* value; zsv( std::nullptr_t ) = delete; constexpr zsv( const char* v ) noexcept : value( v ) {} zsv( const std::string& v ) noexcept : value( v.c_str() ) {} constexpr operator const char*() const noexcept { return value; } constexpr operator std::string_view() const noexcept { return value; } }; } // namespace tao::pq::internal #endif ================================================ FILE: include/tao/pq/is_aggregate.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_IS_AGGREGATE_HPP #define TAO_PQ_IS_AGGREGATE_HPP namespace tao::pq { template< typename > inline constexpr bool is_aggregate = false; template< typename T > inline constexpr bool is_aggregate_result = is_aggregate< T >; template< typename T > inline constexpr bool is_aggregate_parameter = is_aggregate< T >; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/is_array.hpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_IS_ARRAY_HPP #define TAO_PQ_IS_ARRAY_HPP #include #include #include #include #include #include #include #include namespace tao::pq { namespace internal { template< typename > inline constexpr bool is_array = false; template< typename... Ts > inline constexpr bool is_array< std::list< Ts... > > = true; template< typename... Ts > inline constexpr bool is_array< std::set< Ts... > > = true; template< typename... Ts > inline constexpr bool is_array< std::unordered_set< Ts... > > = true; template< typename... Ts > inline constexpr bool is_array< std::vector< Ts... > > = true; template< typename... Ts > inline constexpr bool is_array< std::vector< std::byte, Ts... > > = false; } // namespace internal template< typename T > inline constexpr bool is_array = internal::is_array< T >; namespace internal { template< typename T > inline constexpr bool is_array_parameter = pq::is_array< T >; template< typename T, std::size_t N > inline constexpr bool is_array_parameter< std::span< T, N > > = true; template< std::size_t N > inline constexpr bool is_array_parameter< std::span< std::byte, N > > = false; template< std::size_t N > inline constexpr bool is_array_parameter< std::span< const std::byte, N > > = false; template< typename T, std::size_t N > inline constexpr bool is_array_parameter< std::array< T, N > > = true; } // namespace internal template< typename T > inline constexpr bool is_array_parameter = internal::is_array_parameter< T >; namespace internal { template< typename T > inline constexpr bool is_array_result = pq::is_array< T >; } // namespace internal template< typename T > inline constexpr bool is_array_result = internal::is_array_result< T >; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/isolation_level.hpp ================================================ // Copyright (c) 2020-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_ISOLATION_LEVEL_HPP #define TAO_PQ_ISOLATION_LEVEL_HPP #include #include #include namespace tao::pq { enum class isolation_level : std::uint8_t { default_isolation_level, serializable, repeatable_read, read_committed, read_uncommitted }; [[nodiscard]] constexpr auto taopq_format_as( const isolation_level il ) noexcept -> std::string_view { switch( il ) { case isolation_level::default_isolation_level: return "default_isolation_level"; case isolation_level::serializable: return "serializable"; case isolation_level::repeatable_read: return "repeatable_read"; case isolation_level::read_committed: return "read_committed"; case isolation_level::read_uncommitted: return "read_uncommitted"; default: return ""; } } } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/large_object.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_LARGE_OBJECT_HPP #define TAO_PQ_LARGE_OBJECT_HPP #include #include #include #include #include #include #include #include namespace tao::pq { class transaction; class large_object final { private: std::shared_ptr< transaction > m_transaction; int m_fd; public: [[nodiscard]] static auto create( const std::shared_ptr< transaction >& transaction, const oid desired_id = oid::invalid ) -> oid; static void remove( const std::shared_ptr< transaction >& transaction, const oid id ); [[nodiscard]] static auto import_file( const std::shared_ptr< transaction >& transaction, const char* filename, const oid desired_id = oid::invalid ) -> oid; static void export_file( const std::shared_ptr< transaction >& transaction, const oid id, const char* filename ); large_object( const std::shared_ptr< transaction >& transaction, const oid id, const std::ios_base::openmode m ); large_object( const large_object& ) = delete; large_object( large_object&& other ) noexcept; ~large_object(); void operator=( const large_object& ) = delete; auto operator=( large_object&& rhs ) -> large_object&; // NOLINT(performance-noexcept-move-constructor) void close(); [[nodiscard]] auto read( char* data, const std::size_t size ) -> std::size_t; void write( const char* data, const std::size_t size ); [[nodiscard]] auto read( std::byte* data, const std::size_t size ) -> std::size_t { return read( reinterpret_cast< char* >( data ), size ); } void write( const std::byte* data, const std::size_t size ) { write( reinterpret_cast< const char* >( data ), size ); } template< typename T = binary > [[nodiscard]] auto read( const std::size_t size ) -> T = delete; void write( const char* data ) { write( data, std::strlen( data ) ); } template< typename... Ts > void write( Ts&&... ts ) { const auto bv = pq::to_binary_view( std::forward< Ts >( ts )... ); write( bv.data(), bv.size() ); } void resize( const std::int64_t size ); auto seek( const std::int64_t offset, const std::ios_base::seekdir whence ) -> std::int64_t; [[nodiscard]] auto tell() const -> std::int64_t; }; template<> [[nodiscard]] auto large_object::read< std::string >( const std::size_t size ) -> std::string; template<> [[nodiscard]] auto large_object::read< binary >( const std::size_t size ) -> binary; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/log.hpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_LOG_HPP #define TAO_PQ_LOG_HPP #include #include #include #include namespace tao::pq { class connection; class transaction; struct log { struct connection_pool_t { // TODO... } connection_pool; struct connection_t { using send_query_t = std::function< void( connection&, const char* statement, int n_params, const Oid types[], const char* const values[], const int lengths[], const int formats[] ) >; using send_query_result_t = std::function< void( connection&, int result ) >; using send_query_prepared_t = std::function< void( connection&, const char* statement, int n_params, const char* const values[], const int lengths[], const int formats[] ) >; using send_query_prepared_result_t = std::function< void( connection&, int result ) >; using wait_t = std::function< void( connection&, bool wait_for_write, std::chrono::steady_clock::time_point end ) >; using poll_t = std::function< void( connection&, int socket, bool wait_for_write, int timeout_ms ) >; using poll_result_t = std::function< void( connection&, int socket, poll::status status ) >; using is_busy_result_t = std::function< void( const connection&, int result ) >; // noexcept using consume_input_t = std::function< void( connection& ) >; using consume_input_result_t = std::function< void( connection&, int result ) >; using flush_t = std::function< void( connection& ) >; using flush_result_t = std::function< void( connection&, int result ) >; using get_result_t = std::function< void( connection&, std::chrono::steady_clock::time_point end ) >; using get_result_result_t = std::function< void( connection&, PGresult* ) >; using enter_pipeline_mode_result_t = std::function< void( connection&, int result ) >; using exit_pipeline_mode_t = std::function< void( connection& ) >; using exit_pipeline_mode_result_t = std::function< void( connection&, int result ) >; using pipeline_sync_t = std::function< void( connection& ) >; using pipeline_sync_result_t = std::function< void( connection&, int result ) >; struct : send_query_t { send_query_result_t result; using send_query_t::operator=; } send_query; struct : send_query_prepared_t { send_query_prepared_result_t result; using send_query_prepared_t::operator=; } send_query_prepared; wait_t wait; struct : poll_t { poll_result_t result; using poll_t::operator=; } poll; struct { is_busy_result_t result; } is_busy; struct : consume_input_t { consume_input_result_t result; using consume_input_t::operator=; } consume_input; struct : flush_t { flush_result_t result; using flush_t::operator=; } flush; struct : get_result_t { get_result_result_t result; using get_result_t::operator=; } get_result; struct { enter_pipeline_mode_result_t result; } enter_pipeline_mode; struct : exit_pipeline_mode_t { exit_pipeline_mode_result_t result; using exit_pipeline_mode_t::operator=; } exit_pipeline_mode; struct : pipeline_sync_t { pipeline_sync_result_t result; using pipeline_sync_t::operator=; } pipeline_sync; } connection; struct transaction_t { // check std::current_exception() for more information using destructor_rollback_failed_t = std::function< void( transaction& ) >; // noexcept destructor_rollback_failed_t destructor_rollback_failed; } transaction; }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/notification.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_NOTIFICATION_HPP #define TAO_PQ_NOTIFICATION_HPP #include #include #include namespace tao::pq { class connection; class notification final { private: friend connection; const std::unique_ptr< PGnotify, decltype( &PQfreemem ) > m_pgnotify; explicit notification( PGnotify* notify ) noexcept : m_pgnotify( notify, &PQfreemem ) { assert( notify ); } public: [[nodiscard]] auto channel() const noexcept -> const char* { return m_pgnotify->relname; } [[nodiscard]] auto payload() const noexcept -> const char* { return m_pgnotify->extra; } [[nodiscard]] auto underlying_raw_ptr() noexcept -> PGnotify* { return m_pgnotify.get(); } [[nodiscard]] auto underlying_raw_ptr() const noexcept -> const PGnotify* { return m_pgnotify.get(); } }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/null.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_NULL_HPP #define TAO_PQ_NULL_HPP namespace tao::pq { struct null_t final { explicit constexpr null_t( int /*unused*/ ) {} }; inline constexpr null_t null{ 0 }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/oid.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_OID_HPP #define TAO_PQ_OID_HPP #include namespace tao::pq { static_assert( InvalidOid == 0 ); enum class oid : Oid // NOLINT(performance-enum-size) { invalid = 0, bytea = 17, text = 25 }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/parameter.hpp ================================================ // Copyright (c) 2023-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_PARAMETER_HPP #define TAO_PQ_PARAMETER_HPP #include #include #include #include #include #include #include namespace tao::pq { class transaction; template< std::size_t Max = 16 > class parameter; namespace internal { template< typename > inline constexpr bool is_parameter = false; template< std::size_t Max > inline constexpr bool is_parameter< parameter< Max > > = true; template< typename... As > inline constexpr std::size_t parameter_size = ( parameter_size< std::decay_t< As > > + ... + 0 ); template< typename A > inline constexpr std::size_t parameter_size< A > = parameter_traits< A >::columns; template< std::size_t Max > inline constexpr std::size_t parameter_size< parameter< Max > > = Max; } // namespace internal template< typename T > concept parameter_type_dynamic = internal::is_parameter< std::decay_t< T > >; template< typename T > concept parameter_type = parameter_type_direct< T > || parameter_type_dynamic< T >; template< std::size_t Max > class parameter { private: struct vbase { vbase() noexcept = default; virtual ~vbase() = default; vbase( const vbase& ) = delete; vbase( vbase&& ) = delete; void operator=( const vbase& ) = delete; void operator=( vbase&& ) = delete; }; template< typename T > struct binder : vbase { const parameter_traits< T > m_traits; explicit binder( const T& t ) noexcept( noexcept( parameter_traits< T >( t ) ) ) : m_traits( t ) {} }; template< typename T > struct holder : vbase { const T m_value; const parameter_traits< T > m_traits; explicit holder( T&& t ) noexcept( noexcept( parameter_traits< T >( t ) ) && std::is_nothrow_move_constructible_v< T > ) : m_value( std::move( t ) ), m_traits( m_value ) {} }; std::size_t m_pos = 0; vbase* m_params[ Max ]; int m_size = 0; Oid m_types[ Max ]; const char* m_values[ Max ]; int m_lengths[ Max ]; int m_formats[ Max ]; template< std::size_t > friend class parameter; friend class transaction_base; template< std::size_t... Is > void fill( const auto& t, std::index_sequence< Is... > /*unused*/ ) { ( ( m_types[ m_size + Is ] = static_cast< Oid >( t.template type< Is >() ) ), ... ); ( ( m_values[ m_size + Is ] = t.template value< Is >() ), ... ); ( ( m_lengths[ m_size + Is ] = t.template length< Is >() ), ... ); ( ( m_formats[ m_size + Is ] = t.template format< Is >() ), ... ); } template< parameter_type_direct A > void bind_impl( A&& a ) { using D = std::decay_t< A&& >; constexpr auto columns = parameter_traits< D >::columns; if( ( static_cast< std::size_t >( m_size ) + columns ) > Max ) { throw std::length_error( "too many parameters!" ); } constexpr auto hold = std::is_rvalue_reference_v< A&& > && !parameter_traits< D >::self_contained; using container_t = std::conditional_t< hold, holder< D >, binder< D > >; auto bptr = std::make_unique< container_t >( std::forward< A >( a ) ); parameter::fill( bptr->m_traits, std::make_index_sequence< columns >() ); m_params[ m_pos++ ] = bptr.release(); m_size += columns; } template< std::size_t N > void bind_impl( const parameter< N >& p ) { const std::size_t columns = p.m_size; if( m_size + columns > Max ) { throw std::length_error( "too many parameters!" ); } for( std::size_t n = 0; n < columns; ++n ) { m_types[ m_size + n ] = p.m_types[ n ]; m_values[ m_size + n ] = p.m_values[ n ]; m_lengths[ m_size + n ] = p.m_lengths[ n ]; m_formats[ m_size + n ] = p.m_formats[ n ]; } m_size += static_cast< int >( columns ); } template< std::size_t N > void bind_impl( parameter< N >& p ) { bind_impl( const_cast< const parameter< N >& >( p ) ); } template< std::size_t N > void bind_impl( parameter< N >&& p ) = delete; // NOLINT(modernize-use-equals-delete) public: template< parameter_type... As > explicit parameter( As&&... as ) noexcept( noexcept( std::declval< parameter >().bind( std::forward< As >( as )... ) ) ) { parameter::bind( std::forward< As >( as )... ); } ~parameter() { #if defined( __GNUC__ ) && !defined( __clang__ ) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif for( std::size_t i = 0; i != m_pos; ++i ) { delete m_params[ i ]; } #if defined( __GNUC__ ) && !defined( __clang__ ) #pragma GCC diagnostic pop #endif } explicit parameter( const parameter& p ) { parameter::bind( p ); } explicit parameter( parameter&& p ) = delete; void operator=( const parameter& ) = delete; void operator=( parameter&& ) = delete; template< parameter_type... As > void bind( As&&... as ) noexcept( sizeof...( As ) == 0 ) { ( parameter::bind_impl( std::forward< As >( as ) ), ... ); } template< parameter_type... As > void reset( As&&... as ) noexcept( noexcept( std::declval< parameter >().bind( std::forward< As >( as )... ) ) ) { for( std::size_t i = 0; i != m_pos; ++i ) { delete m_params[ i ]; } m_pos = 0; m_size = 0; parameter::bind( std::forward< As >( as )... ); } }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/parameter_traits.hpp ================================================ // Copyright (c) 2020-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_PARAMETER_TRAITS_HPP #define TAO_PQ_PARAMETER_TRAITS_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { template< typename > struct parameter_traits; namespace internal { // helper for arrays void array_append( std::string& buffer, std::string_view data ); // helper for table_writer void table_writer_append( std::string& buffer, std::string_view data ); template< std::size_t N > void snprintf( char ( &buffer )[ N ], const char* format, const auto v ) noexcept { static_assert( N >= 32 ); if( std::isfinite( v ) ) { [[maybe_unused]] const auto result = std::snprintf( buffer, N, format, v ); assert( result > 0 ); assert( static_cast< std::size_t >( result ) < N ); } else if( std::isnan( v ) ) { #if defined( _MSC_VER ) [[maybe_unused]] const auto result = strncpy_s( buffer, "NAN", N ); assert( result == 0 ); #else std::strcpy( buffer, "NAN" ); // NOLINT(clang-analyzer-security.insecureAPI.strcpy) #endif } else { #if defined( _MSC_VER ) [[maybe_unused]] const auto result = strncpy_s( buffer, ( v < 0 ) ? "-INF" : "INF", N ); assert( result == 0 ); #else std::strcpy( buffer, ( v < 0 ) ? "-INF" : "INF" ); // NOLINT(clang-analyzer-security.insecureAPI.strcpy) #endif } } template< typename T > concept parameter_type_has_element = requires( const parameter_traits< std::decay_t< T > >& t, std::string& s ) { { t.template element< 0 >( s ) } -> std::same_as< void >; }; } // namespace internal template< typename T > concept parameter_type_direct = requires( const parameter_traits< std::decay_t< T > >& t, std::string& s ) { { parameter_traits< std::decay_t< T > >::columns } -> std::same_as< const std::size_t& >; { parameter_traits< std::decay_t< T > >::self_contained } -> std::same_as< const bool& >; { t.template type< 0 >() } -> std::same_as< oid >; { t.template value< 0 >() } -> std::same_as< const char* >; { t.template length< 0 >() } -> std::same_as< int >; { t.template format< 0 >() } -> std::same_as< int >; { t.template copy_to< 0 >( s ) } -> std::same_as< void >; } && ( ( parameter_traits< std::decay_t< T > >::columns >= 2 ) || internal::parameter_type_has_element< T > ); template<> struct parameter_traits< null_t > { explicit parameter_traits( null_t /*unused*/ ) noexcept {} static constexpr std::size_t columns = 1; static constexpr bool self_contained = true; template< std::size_t I > [[nodiscard]] static constexpr auto type() noexcept -> oid { return oid::invalid; } template< std::size_t I > [[nodiscard]] static constexpr auto value() noexcept -> const char* { return nullptr; } template< std::size_t I > [[nodiscard]] static constexpr auto length() noexcept -> int { return 0; } template< std::size_t I > [[nodiscard]] static constexpr auto format() noexcept -> int { return 0; } template< std::size_t I > static void element( std::string& data ) { data += "NULL"; } template< std::size_t I > static void copy_to( std::string& data ) { data += "\\N"; } }; template<> struct parameter_traits< bool > : internal::char_pointer_helper { static constexpr bool self_contained = true; explicit parameter_traits( const bool v ) noexcept : internal::char_pointer_helper( v ? "TRUE" : "FALSE" ) {} template< std::size_t I > void element( std::string& data ) const { data += m_p; } template< std::size_t I > void copy_to( std::string& data ) const { data += m_p; } }; template<> struct parameter_traits< char > { const char m_value[ 2 ]; // NOLINT(modernize-use-default-member-init) explicit parameter_traits( const char v ) noexcept : m_value{ v, '\0' } {} static constexpr std::size_t columns = 1; static constexpr bool self_contained = true; template< std::size_t I > [[nodiscard]] static constexpr auto type() noexcept -> oid { return oid::invalid; } template< std::size_t I > [[nodiscard]] auto value() const noexcept -> const char* { return m_value; } template< std::size_t I > [[nodiscard]] static constexpr auto length() noexcept -> int { return 0; } template< std::size_t I > [[nodiscard]] static constexpr auto format() noexcept -> int { return 0; } template< std::size_t I > void element( std::string& data ) const { internal::array_append( data, std::string_view( m_value, 1 ) ); } template< std::size_t I > void copy_to( std::string& data ) const { internal::table_writer_append( data, std::string_view( m_value, 1 ) ); } }; template<> struct parameter_traits< signed char > : internal::to_chars_helper { using internal::to_chars_helper::to_chars_helper; }; template<> struct parameter_traits< unsigned char > : internal::to_chars_helper { using internal::to_chars_helper::to_chars_helper; }; template<> struct parameter_traits< short > : internal::to_chars_helper { using internal::to_chars_helper::to_chars_helper; }; template<> struct parameter_traits< unsigned short > : internal::to_chars_helper { using internal::to_chars_helper::to_chars_helper; }; template<> struct parameter_traits< int > : internal::to_chars_helper { using internal::to_chars_helper::to_chars_helper; }; template<> struct parameter_traits< unsigned int > : internal::to_chars_helper { using internal::to_chars_helper::to_chars_helper; }; template<> struct parameter_traits< long > : internal::to_chars_helper { using internal::to_chars_helper::to_chars_helper; }; template<> struct parameter_traits< unsigned long > : internal::to_chars_helper { using internal::to_chars_helper::to_chars_helper; }; template<> struct parameter_traits< long long > : internal::to_chars_helper { using internal::to_chars_helper::to_chars_helper; }; template<> struct parameter_traits< unsigned long long > : internal::to_chars_helper { using internal::to_chars_helper::to_chars_helper; }; template<> struct parameter_traits< float > : internal::buffer_helper { explicit parameter_traits( const float v ) noexcept { internal::snprintf( m_buffer, "%.9g", v ); } }; template<> struct parameter_traits< double > : internal::buffer_helper { explicit parameter_traits( const double v ) noexcept { internal::snprintf( m_buffer, "%.17g", v ); } }; template<> struct parameter_traits< long double > : internal::buffer_helper { explicit parameter_traits( const long double v ) noexcept { internal::snprintf( m_buffer, "%.21Lg", v ); } }; template<> struct parameter_traits< const char* > : internal::char_pointer_helper { using internal::char_pointer_helper::char_pointer_helper; template< std::size_t I > void element( std::string& data ) const { internal::array_append( data, m_p ); } template< std::size_t I > void copy_to( std::string& data ) const { internal::table_writer_append( data, m_p ); } }; // for string_views (which are not zero-terminated) we can use binary format and, // surprisingly, it does not seem to cause any issues template<> struct parameter_traits< std::string_view > { private: const std::string_view m_v; public: explicit parameter_traits( const std::string_view v ) noexcept : m_v( v ) {} static constexpr std::size_t columns = 1; static constexpr bool self_contained = false; template< std::size_t I > [[nodiscard]] static constexpr auto type() noexcept -> oid { return oid::text; } template< std::size_t I > [[nodiscard]] auto value() const noexcept -> const char* { return m_v.data(); } template< std::size_t I > [[nodiscard]] auto length() const noexcept -> int { return static_cast< int >( m_v.size() ); } template< std::size_t I > [[nodiscard]] static constexpr auto format() noexcept -> int { return 1; } template< std::size_t I > void element( std::string& data ) const { internal::array_append( data, m_v ); } template< std::size_t I > void copy_to( std::string& data ) const { internal::table_writer_append( data, m_v ); } }; template<> struct parameter_traits< std::string > : parameter_traits< std::string_view > { using parameter_traits< std::string_view >::parameter_traits; }; template< std::size_t Extent > struct parameter_traits< std::span< const std::byte, Extent > > { private: const std::span< const std::byte, Extent > m_v; public: explicit parameter_traits( const std::span< const std::byte, Extent > v ) noexcept : m_v( v ) {} static constexpr std::size_t columns = 1; static constexpr bool self_contained = false; template< std::size_t I > [[nodiscard]] static constexpr auto type() noexcept -> oid { return oid::bytea; } template< std::size_t I > [[nodiscard]] constexpr auto value() const noexcept -> const char* { return reinterpret_cast< const char* >( m_v.data() ); } template< std::size_t I > [[nodiscard]] constexpr auto length() const noexcept -> int { return static_cast< int >( m_v.size() ); } template< std::size_t I > [[nodiscard]] static constexpr auto format() noexcept -> int { return 1; } template< std::size_t I > void element( std::string& data ) const { // generate bytea hex format constexpr char hex[] = "0123456789abcdef"; auto pos = data.size(); internal::resize_uninitialized( data, pos + 3 + ( 2 * m_v.size() ) ); data[ pos++ ] = '\\'; data[ pos++ ] = '\\'; data[ pos++ ] = 'x'; for( auto c : m_v ) { data[ pos++ ] = hex[ static_cast< unsigned char >( c ) >> 4 ]; data[ pos++ ] = hex[ static_cast< unsigned char >( c ) & 15 ]; } } template< std::size_t I > void copy_to( std::string& data ) const { element< I >( data ); } }; template< std::size_t Extent > struct parameter_traits< std::span< std::byte, Extent > > : parameter_traits< std::span< const std::byte, Extent > > { using parameter_traits< std::span< const std::byte, Extent > >::parameter_traits; }; template< typename Allocator > struct parameter_traits< std::vector< std::byte, Allocator > > : parameter_traits< binary_view > { using parameter_traits< binary_view >::parameter_traits; }; // default free function to detect member function to_taopq() [[nodiscard]] auto to_taopq( const auto& t ) noexcept( noexcept( t.to_taopq() ) ) requires requires { t.to_taopq(); } { return t.to_taopq(); } // default free function to detect bind::to_taopq() template< typename T > [[nodiscard]] auto to_taopq( const T& t ) noexcept( noexcept( bind< T >::to_taopq( t ) ) ) requires requires { bind< T >::to_taopq( t ); } { return bind< T >::to_taopq( t ); } // note: calls to to_taopq are unqualified to enable ADL namespace internal { template< typename T > struct parameter_holder { using result_t = decltype( to_taopq( std::declval< const T& >() ) ); const result_t result; explicit parameter_holder( const T& t ) noexcept( noexcept( result_t( to_taopq( t ) ) ) ) : result( to_taopq( t ) ) {} }; } // namespace internal template< typename T > requires requires( const T& t ) { to_taopq( t ); } struct parameter_traits< T > : private internal::parameter_holder< T >, public parameter_traits< typename internal::parameter_holder< T >::result_t > { static constexpr bool self_contained = true; using typename internal::parameter_holder< T >::result_t; explicit parameter_traits( const T& t ) noexcept( noexcept( internal::parameter_holder< T >( t ), parameter_traits< result_t >( std::declval< result_t >() ) ) ) : internal::parameter_holder< T >( t ), parameter_traits< result_t >( this->result ) {} }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/parameter_traits_aggregate.hpp ================================================ // Copyright (c) 2020-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_PARAMETER_TRAITS_AGGREGATE_HPP #define TAO_PQ_PARAMETER_TRAITS_AGGREGATE_HPP #include #include #include namespace tao::pq { namespace internal { template< typename T > struct parameter_tie_aggregate { using result_t = decltype( internal::tie_aggregate( std::declval< const T& >() ) ); const result_t result; explicit parameter_tie_aggregate( const T& t ) noexcept : result( internal::tie_aggregate( t ) ) {} }; } // namespace internal template< typename T > requires is_aggregate_parameter< T > struct parameter_traits< T > : private internal::parameter_tie_aggregate< T >, public parameter_traits< typename internal::parameter_tie_aggregate< T >::result_t > { using typename internal::parameter_tie_aggregate< T >::result_t; explicit parameter_traits( const T& t ) noexcept( noexcept( internal::parameter_tie_aggregate< T >( t ), parameter_traits< result_t >( std::declval< result_t >() ) ) ) : internal::parameter_tie_aggregate< T >( t ), parameter_traits< result_t >( this->result ) {} }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/parameter_traits_array.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_PARAMETER_TRAITS_ARRAY_HPP #define TAO_PQ_PARAMETER_TRAITS_ARRAY_HPP #include #include #include #include #include namespace tao::pq { namespace internal { template< typename T > concept array_parameter_type = pq::is_array_parameter< T > && ( pq::is_array_parameter< typename T::value_type > || ( parameter_traits< typename T::value_type >::columns == 1 ) ); template< typename T > void to_array( std::string& data, const T& v ) { parameter_traits< T >( v ).template element< 0 >( data ); } template< typename T > requires pq::is_array_parameter< T > void to_array( std::string& data, const T& v ) { data += '{'; if( v.empty() ) { data += '}'; } else { for( const auto& e : v ) { internal::to_array( data, e ); data += ','; } *data.rbegin() = '}'; } } } // namespace internal template< internal::array_parameter_type T > struct parameter_traits< T > { private: std::string m_data; public: explicit parameter_traits( const T& v ) { internal::to_array( m_data, v ); } static constexpr std::size_t columns = 1; static constexpr bool self_contained = true; template< std::size_t I > [[nodiscard]] static constexpr auto type() noexcept -> oid { return oid::invalid; } template< std::size_t I > [[nodiscard]] auto value() const noexcept -> const char* { return m_data.c_str(); } template< std::size_t I > [[nodiscard]] static constexpr auto length() noexcept -> int { return 0; } template< std::size_t I > [[nodiscard]] static constexpr auto format() noexcept -> int { return 0; } template< std::size_t I > void element( std::string& data ) const { internal::array_append( data, m_data ); } template< std::size_t I > void copy_to( std::string& data ) const { internal::table_writer_append( data, m_data ); } }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/parameter_traits_optional.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_PARAMETER_TRAITS_OPTIONAL_HPP #define TAO_PQ_PARAMETER_TRAITS_OPTIONAL_HPP #include #include #include #include #include #include #include template< typename T > struct tao::pq::parameter_traits< std::optional< T > > { private: using U = parameter_traits< std::decay_t< T > >; std::optional< U > m_forwarder; public: explicit parameter_traits( const std::optional< T >& v ) noexcept( noexcept( m_forwarder.emplace( *v ) ) ) { if( v ) { m_forwarder.emplace( *v ); } } explicit parameter_traits( std::optional< T >&& v ) noexcept( noexcept( m_forwarder.emplace( std::move( *v ) ) ) ) { if( v ) { m_forwarder.emplace( std::move( *v ) ); } } static constexpr std::size_t columns = U::columns; static constexpr bool self_contained = U::self_contained; template< std::size_t I > [[nodiscard]] static constexpr auto type() noexcept -> oid { return U::template type< I >(); } template< std::size_t I > [[nodiscard]] constexpr auto value() const noexcept( noexcept( m_forwarder ? m_forwarder->template value< I >() : nullptr ) ) -> const char* { return m_forwarder ? m_forwarder->template value< I >() : nullptr; } template< std::size_t I > [[nodiscard]] constexpr auto length() const noexcept( noexcept( m_forwarder ? m_forwarder->template length< I >() : 0 ) ) -> int { return m_forwarder ? m_forwarder->template length< I >() : 0; } template< std::size_t I > [[nodiscard]] static constexpr auto format() noexcept -> int { return U::template format< I >(); } template< std::size_t I > void element( std::string& data ) const { if( m_forwarder ) { m_forwarder->template element< I >( data ); } else { data += "NULL"; } } template< std::size_t I > void copy_to( std::string& data ) const { if( m_forwarder ) { m_forwarder->template copy_to< I >( data ); } else { data += "\\N"; } } }; #endif ================================================ FILE: include/tao/pq/parameter_traits_pair.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_PARAMETER_TRAITS_PAIR_HPP #define TAO_PQ_PARAMETER_TRAITS_PAIR_HPP #include #include #include #include #include #include #include template< typename T, typename U > struct tao::pq::parameter_traits< std::pair< T, U > > { private: using first_t = parameter_traits< std::decay_t< T > >; using second_t = parameter_traits< std::decay_t< U > >; using pair_t = std::pair< first_t, second_t >; pair_t m_pair; using gen = internal::gen< first_t::columns, second_t::columns >; public: explicit parameter_traits( const std::pair< T, U >& pair ) noexcept( noexcept( pair_t( pair ) ) ) : m_pair( pair ) {} explicit parameter_traits( std::pair< T, U >&& pair ) noexcept( noexcept( pair_t( std::move( pair ) ) ) ) : m_pair( std::move( pair ) ) {} static constexpr std::size_t columns = first_t::columns + second_t::columns; static constexpr bool self_contained = first_t::self_contained && second_t::self_contained; template< std::size_t I > [[nodiscard]] constexpr auto type() const noexcept( noexcept( std::get< gen::template outer< I > >( m_pair ).template type< gen::template inner< I > >() ) ) -> oid { return std::get< gen::template outer< I > >( m_pair ).template type< gen::template inner< I > >(); } template< std::size_t I > [[nodiscard]] constexpr auto value() const noexcept( noexcept( std::get< gen::template outer< I > >( m_pair ).template value< gen::template inner< I > >() ) ) -> const char* { return std::get< gen::template outer< I > >( m_pair ).template value< gen::template inner< I > >(); } template< std::size_t I > [[nodiscard]] constexpr auto length() const noexcept( noexcept( std::get< gen::template outer< I > >( m_pair ).template length< gen::template inner< I > >() ) ) -> int { return std::get< gen::template outer< I > >( m_pair ).template length< gen::template inner< I > >(); } template< std::size_t I > [[nodiscard]] constexpr auto format() const noexcept( noexcept( std::get< gen::template outer< I > >( m_pair ).template format< gen::template inner< I > >() ) ) -> int { return std::get< gen::template outer< I > >( m_pair ).template format< gen::template inner< I > >(); } template< std::size_t I > void copy_to( std::string& data ) const { std::get< gen::template outer< I > >( m_pair ).template copy_to< gen::template inner< I > >( data ); } }; #endif ================================================ FILE: include/tao/pq/parameter_traits_tuple.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_PARAMETER_TRAITS_TUPLE_HPP #define TAO_PQ_PARAMETER_TRAITS_TUPLE_HPP #include #include #include #include #include #include #include #include template< typename... Ts > requires( sizeof...( Ts ) != 0 ) struct tao::pq::parameter_traits< std::tuple< Ts... > > { private: using tuple_t = std::tuple< parameter_traits< std::decay_t< Ts > >... >; tuple_t m_tuple; using gen = internal::gen< parameter_traits< std::decay_t< Ts > >::columns... >; public: explicit parameter_traits( const std::tuple< Ts... >& tuple ) noexcept( noexcept( tuple_t( tuple ) ) ) : m_tuple( tuple ) {} explicit parameter_traits( std::tuple< Ts... >&& tuple ) noexcept( noexcept( tuple_t( std::move( tuple ) ) ) ) : m_tuple( std::move( tuple ) ) {} static constexpr std::size_t columns = ( 0 + ... + parameter_traits< std::decay_t< Ts > >::columns ); static constexpr bool self_contained = ( parameter_traits< std::decay_t< Ts > >::self_contained && ... ); template< std::size_t I > [[nodiscard]] constexpr auto type() const noexcept( noexcept( std::get< gen::template outer< I > >( m_tuple ).template type< gen::template inner< I > >() ) ) -> oid { return std::get< gen::template outer< I > >( m_tuple ).template type< gen::template inner< I > >(); } template< std::size_t I > [[nodiscard]] constexpr auto value() const noexcept( noexcept( std::get< gen::template outer< I > >( m_tuple ).template value< gen::template inner< I > >() ) ) -> const char* { return std::get< gen::template outer< I > >( m_tuple ).template value< gen::template inner< I > >(); } template< std::size_t I > [[nodiscard]] constexpr auto length() const noexcept( noexcept( std::get< gen::template outer< I > >( m_tuple ).template length< gen::template inner< I > >() ) ) -> int { return std::get< gen::template outer< I > >( m_tuple ).template length< gen::template inner< I > >(); } template< std::size_t I > [[nodiscard]] constexpr auto format() const noexcept( noexcept( std::get< gen::template outer< I > >( m_tuple ).template format< gen::template inner< I > >() ) ) -> int { return std::get< gen::template outer< I > >( m_tuple ).template format< gen::template inner< I > >(); } template< std::size_t I > requires( sizeof...( Ts ) == 1 ) void element( std::string& data ) const { std::get< gen::template outer< I > >( m_tuple ).template element< gen::template inner< I > >( data ); } template< std::size_t I > void copy_to( std::string& data ) const { std::get< gen::template outer< I > >( m_tuple ).template copy_to< gen::template inner< I > >( data ); } }; #endif ================================================ FILE: include/tao/pq/pipeline.hpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_PIPELINE_HPP #define TAO_PQ_PIPELINE_HPP #include #include #include namespace tao::pq { class connection; class transaction; class pipeline : public transaction_base { private: std::shared_ptr< transaction_base > m_previous; friend class transaction; // pass-key idiom class private_key final { private_key() = default; friend class transaction; }; public: pipeline( const private_key /*unused*/, const std::shared_ptr< pq::connection >& connection ); ~pipeline() override { try { finish(); } // LCOV_EXCL_START catch( ... ) { // NOLINT(bugprone-empty-catch) // TODO: How to handle this case properly? } // LCOV_EXCL_STOP } pipeline( const pipeline& ) = delete; pipeline( pipeline&& ) = delete; void operator=( const pipeline& ) = delete; void operator=( pipeline&& ) = delete; void sync(); void consume_sync( const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now() ); void finish(); }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/pipeline_status.hpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_PIPELINE_STATUS_HPP #define TAO_PQ_PIPELINE_STATUS_HPP #include #include #include #include namespace tao::pq { enum class pipeline_status : std::uint8_t { on = PQ_PIPELINE_ON, off = PQ_PIPELINE_OFF, aborted = PQ_PIPELINE_ABORTED }; [[nodiscard]] constexpr auto taopq_format_as( const pipeline_status ps ) noexcept -> std::string_view { switch( ps ) { case pipeline_status::on: return "on"; case pipeline_status::off: return "off"; case pipeline_status::aborted: return "aborted"; default: return ""; } } } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/poll.hpp ================================================ // Copyright (c) 2023-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_POLL_HPP #define TAO_PQ_POLL_HPP #include #include #include namespace tao::pq::poll { enum class status : std::uint8_t { timeout, readable, writable, again }; [[nodiscard]] constexpr auto taopq_format_as( const status st ) noexcept -> std::string_view { switch( st ) { case status::timeout: return "timeout"; case status::readable: return "readable"; case status::writable: return "writable"; case status::again: return "again"; default: return ""; } } using callback = status( const int socket, const bool wait_for_write, const int timeout_ms ); } // namespace tao::pq::poll #endif ================================================ FILE: include/tao/pq/result.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_RESULT_HPP #define TAO_PQ_RESULT_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { class connection; class table_reader; class table_writer; class transaction_base; class result final { private: friend class connection; friend class table_reader; friend class table_writer; friend class transaction_base; const std::shared_ptr< PGresult > m_pgresult; const std::size_t m_columns; const std::size_t m_rows; void check_row( const std::size_t row ) const; explicit result( PGresult* pgresult ); public: [[nodiscard]] auto status() const noexcept -> result_status; [[nodiscard]] auto has_rows_affected() const noexcept -> bool; [[nodiscard]] auto rows_affected() const -> std::size_t; [[nodiscard]] auto columns() const noexcept -> std::size_t { return m_columns; } [[nodiscard]] auto name( const std::size_t column ) const -> std::string; [[nodiscard]] auto index( const internal::zsv in_name ) const -> std::size_t; [[nodiscard]] auto size() const noexcept -> std::size_t { assert( m_columns != 0 ); return m_rows; } [[nodiscard]] auto empty() const noexcept -> bool { return size() == 0; } private: class const_iterator : private row { private: friend class result; explicit const_iterator( const row& r ) noexcept : row( r ) {} public: using difference_type = std::int32_t; using value_type = const row; using pointer = const row*; using reference = const row&; using iterator_category = std::random_access_iterator_tag; const_iterator() = default; auto operator++() noexcept -> const_iterator& { ++m_row; return *this; } auto operator++( int ) noexcept -> const_iterator { const_iterator nrv( *this ); ++*this; return nrv; } auto operator+=( const difference_type n ) noexcept -> const_iterator& { m_row += n; return *this; } auto operator--() noexcept -> const_iterator& { --m_row; return *this; } auto operator--( int ) noexcept -> const_iterator { const_iterator nrv( *this ); --*this; return nrv; } auto operator-=( const difference_type n ) noexcept -> const_iterator& { m_row -= n; return *this; } [[nodiscard]] auto operator*() const noexcept -> const row& { return *this; } [[nodiscard]] auto operator->() const noexcept -> const row* { return this; } [[nodiscard]] auto operator[]( const difference_type n ) const noexcept -> row { return *( *this + n ); } friend void swap( const_iterator& lhs, const_iterator& rhs ) noexcept { swap( static_cast< row& >( lhs ), static_cast< row& >( rhs ) ); } [[nodiscard]] friend auto operator+( const const_iterator& lhs, const difference_type rhs ) noexcept -> const_iterator { const_iterator nrv( lhs ); nrv += rhs; return nrv; } [[nodiscard]] friend auto operator+( const difference_type lhs, const const_iterator& rhs ) noexcept -> const_iterator { const_iterator nrv( rhs ); nrv += lhs; return nrv; } [[nodiscard]] friend auto operator-( const const_iterator& lhs, const difference_type rhs ) noexcept -> const_iterator { const_iterator nrv( lhs ); nrv -= rhs; return nrv; } [[nodiscard]] friend auto operator-( const const_iterator& lhs, const const_iterator& rhs ) noexcept -> difference_type { return static_cast< difference_type >( lhs.m_row ) - static_cast< difference_type >( rhs.m_row ); } [[nodiscard]] friend auto operator==( const const_iterator& lhs, const const_iterator& rhs ) noexcept { return lhs.m_row == rhs.m_row; } [[nodiscard]] friend auto operator<=>( const const_iterator& lhs, const const_iterator& rhs ) noexcept { return lhs.m_row <=> rhs.m_row; } }; public: [[nodiscard]] auto begin() const noexcept -> const_iterator; [[nodiscard]] auto end() const noexcept -> const_iterator; [[nodiscard]] auto cbegin() const noexcept { return begin(); } [[nodiscard]] auto cend() const noexcept { return end(); } [[nodiscard]] auto is_null( const std::size_t row, const std::size_t column ) const -> bool; [[nodiscard]] auto get( const std::size_t row, const std::size_t column ) const -> const char*; [[nodiscard]] auto operator[]( const std::size_t row ) const noexcept { return pq::row( *this, row, 0, m_columns ); } [[nodiscard]] auto at( const std::size_t row ) const -> pq::row; template< result_type T > [[nodiscard]] auto as() const -> T { switch( size() ) { case 0: throw std::runtime_error( "invalid empty result, expected 1 row" ); case 1: return ( *this )[ 0 ].as< T >(); default: throw std::runtime_error( std::format( "invalid result size: {} rows, expected 1 row", m_rows ) ); } } template< result_type T > [[nodiscard]] auto optional() const -> std::optional< T > { switch( size() ) { case 0: return std::nullopt; case 1: return ( *this )[ 0 ].as< T >(); default: throw std::runtime_error( std::format( "invalid result size: {} rows, expected 0 or 1 rows", m_rows ) ); } } template< result_type T, result_type U > [[nodiscard]] auto pair() const { return as< std::pair< T, U > >(); } template< result_type... Ts > [[nodiscard]] auto tuple() const { return as< std::tuple< Ts... > >(); } template< typename T > requires result_type< typename T::value_type > [[nodiscard]] auto as_container() const -> T { assert( m_columns != 0 ); T nrv; if constexpr( requires { nrv.reserve( size() ); } ) { nrv.reserve( size() ); } for( const auto& row : *this ) { nrv.insert( nrv.end(), row.as< typename T::value_type >() ); } return nrv; } template< typename... Ts > requires result_type< typename std::vector< Ts... >::value_type > [[nodiscard]] auto vector() const { return as_container< std::vector< Ts... > >(); } template< typename... Ts > requires result_type< typename std::list< Ts... >::value_type > [[nodiscard]] auto list() const { return as_container< std::list< Ts... > >(); } template< typename... Ts > requires result_type< typename std::set< Ts... >::value_type > [[nodiscard]] auto set() const { return as_container< std::set< Ts... > >(); } template< typename... Ts > requires result_type< typename std::multiset< Ts... >::value_type > [[nodiscard]] auto multiset() const { return as_container< std::multiset< Ts... > >(); } template< typename... Ts > requires result_type< typename std::unordered_set< Ts... >::value_type > [[nodiscard]] auto unordered_set() const { return as_container< std::unordered_set< Ts... > >(); } template< typename... Ts > requires result_type< typename std::unordered_multiset< Ts... >::value_type > [[nodiscard]] auto unordered_multiset() const { return as_container< std::unordered_multiset< Ts... > >(); } template< typename... Ts > requires result_type< typename std::map< Ts... >::value_type > [[nodiscard]] auto map() const { return as_container< std::map< Ts... > >(); } template< typename... Ts > requires result_type< typename std::multimap< Ts... >::value_type > [[nodiscard]] auto multimap() const { return as_container< std::multimap< Ts... > >(); } template< typename... Ts > requires result_type< typename std::unordered_map< Ts... >::value_type > [[nodiscard]] auto unordered_map() const { return as_container< std::unordered_map< Ts... > >(); } template< typename... Ts > requires result_type< typename std::unordered_multimap< Ts... >::value_type > [[nodiscard]] auto unordered_multimap() const { return as_container< std::unordered_multimap< Ts... > >(); } [[nodiscard]] auto underlying_raw_ptr() noexcept -> PGresult* { return m_pgresult.get(); } [[nodiscard]] auto underlying_raw_ptr() const noexcept -> const PGresult* { return m_pgresult.get(); } }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/result_status.hpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_RESULT_STATUS_HPP #define TAO_PQ_RESULT_STATUS_HPP #include #include #include #include namespace tao::pq { enum class result_status : std::uint8_t { empty_query = PGRES_EMPTY_QUERY, command_ok = PGRES_COMMAND_OK, tuples_ok = PGRES_TUPLES_OK, copy_out = PGRES_COPY_OUT, copy_in = PGRES_COPY_IN, bad_response = PGRES_BAD_RESPONSE, nonfatal_error = PGRES_NONFATAL_ERROR, fatal_error = PGRES_FATAL_ERROR, single_tuple = PGRES_SINGLE_TUPLE, #if defined( LIBPQ_HAS_CHUNK_MODE ) tuples_chunk = PGRES_TUPLES_CHUNK, #endif pipeline_sync = PGRES_PIPELINE_SYNC, pipeline_aborted = PGRES_PIPELINE_ABORTED }; [[nodiscard]] constexpr auto taopq_format_as( const result_status rs ) noexcept -> std::string_view { switch( rs ) { case result_status::empty_query: return "empty_query"; case result_status::command_ok: return "command_ok"; case result_status::tuples_ok: return "tuples_ok"; case result_status::copy_out: return "copy_out"; case result_status::copy_in: return "copy_in"; case result_status::bad_response: return "bad_response"; case result_status::nonfatal_error: return "nonfatal_error"; case result_status::fatal_error: return "fatal_error"; case result_status::single_tuple: return "single_tuple"; #if defined( LIBPQ_HAS_CHUNK_MODE ) case result_status::tuples_chunk: return "tuples_chunk"; #endif case result_status::pipeline_sync: return "pipeline_sync"; case result_status::pipeline_aborted: return "pipeline_aborted"; default: return ""; } } } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/result_traits.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_RESULT_TRAITS_HPP #define TAO_PQ_RESULT_TRAITS_HPP #include #include #include #include #include #include #include #include #include namespace tao::pq { template< typename > struct result_traits; template< typename > inline constexpr std::size_t result_traits_size = 1; template< typename T > requires requires { result_traits< T >::size; } inline constexpr std::size_t result_traits_size< T > = result_traits< T >::size; template< typename T > concept result_type_direct = ( ( result_traits_size< T > == 1 ) && !is_aggregate_result< T > ) && requires( const char* s ) { { result_traits< T >::from( s ) } -> std::same_as< T >; }; class row; template< typename T > concept result_type_composite = ( ( result_traits_size< T > != 1 ) || is_aggregate_result< T > ) && requires( const row& r ) { { result_traits< T >::from( r ) } -> std::same_as< T >; }; template< typename T > concept result_type = result_type_direct< T > || result_type_composite< T >; template<> struct result_traits< const char* > { [[nodiscard]] static auto from( const char* value ) { return value; } }; template<> struct result_traits< std::string_view > { [[nodiscard]] static auto from( const char* value ) -> std::string_view { return value; } }; template<> struct result_traits< bool > { [[nodiscard]] static auto from( const char* value ) -> bool; }; template<> struct result_traits< char > { [[nodiscard]] static auto from( const char* value ) -> char; }; template<> struct result_traits< signed char > { [[nodiscard]] static auto from( const char* value ) -> signed char; }; template<> struct result_traits< unsigned char > { [[nodiscard]] static auto from( const char* value ) -> unsigned char; }; template<> struct result_traits< short > { [[nodiscard]] static auto from( const char* value ) -> short; }; template<> struct result_traits< unsigned short > { [[nodiscard]] static auto from( const char* value ) -> unsigned short; }; template<> struct result_traits< int > { [[nodiscard]] static auto from( const char* value ) -> int; }; template<> struct result_traits< unsigned > { [[nodiscard]] static auto from( const char* value ) -> unsigned; }; template<> struct result_traits< long > { [[nodiscard]] static auto from( const char* value ) -> long; }; template<> struct result_traits< unsigned long > { [[nodiscard]] static auto from( const char* value ) -> unsigned long; }; template<> struct result_traits< long long > { [[nodiscard]] static auto from( const char* value ) -> long long; }; template<> struct result_traits< unsigned long long > { [[nodiscard]] static auto from( const char* value ) -> unsigned long long; }; template<> struct result_traits< float > { [[nodiscard]] static auto from( const char* value ) -> float; }; template<> struct result_traits< double > { [[nodiscard]] static auto from( const char* value ) -> double; }; template<> struct result_traits< long double > { [[nodiscard]] static auto from( const char* value ) -> long double; }; template<> struct result_traits< std::string > { [[nodiscard]] static auto from( const char* value ) -> std::string { return value; } }; template<> struct result_traits< binary > { [[nodiscard]] static auto from( const char* value ) -> binary; }; namespace internal { template< typename, typename, typename > struct from_taopq; template< typename T, typename R, typename... As > struct from_taopq< T, R, R( As... ) > { static constexpr std::size_t size = ( 0 + ... + result_traits_size< std::decay_t< As > > ); template< typename Row, std::size_t... Ns > [[nodiscard]] static auto from( const Row& row, std::index_sequence< Ns... > /*unused*/ ) -> R { return T::from_taopq( row.template get< std::decay_t< As > >( Ns )... ); } template< typename Row > [[nodiscard]] static auto from( const Row& row ) -> R { return from_taopq::from( row, exclusive_scan_t< std::index_sequence< result_traits_size< std::decay_t< As > >... > >() ); } }; template< typename T, typename R, typename... As > struct from_taopq< T, R, R( As... ) noexcept > : from_taopq< T, R, R( As... ) > {}; } // namespace internal template< typename T > requires requires { T::from_taopq; } struct result_traits< T > : internal::from_taopq< T, T, decltype( T::from_taopq ) > {}; template< typename T > requires requires { bind< T >::from_taopq; } struct result_traits< T > : internal::from_taopq< bind< T >, T, decltype( bind< T >::from_taopq ) > {}; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/result_traits_aggregate.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_RESULT_TRAITS_AGGREGATE_HPP #define TAO_PQ_RESULT_TRAITS_AGGREGATE_HPP #include #include #include #include #include #include #include #include namespace tao::pq { namespace internal { template< typename... Ts > struct decay_tuple; template< typename... Ts > struct decay_tuple< std::tuple< Ts... > > { using type = std::tuple< std::decay_t< Ts >... >; }; template< typename T, typename = typename decay_tuple< decltype( internal::tie_aggregate( std::declval< T >() ) ) >::type > struct aggregate_result; template< typename T, typename... Ts > struct aggregate_result< T, std::tuple< Ts... > > { static constexpr std::size_t size = ( 0 + ... + result_traits_size< Ts > ); template< typename Row, std::size_t... Ns > [[nodiscard]] static auto from( const Row& row, std::index_sequence< Ns... > /*unused*/ ) { return T{ row.template get< Ts >( Ns )... }; } template< typename Row > [[nodiscard]] static auto from( const Row& row ) { return aggregate_result::from( row, exclusive_scan_t< std::index_sequence< result_traits_size< Ts >... > >() ); } }; } // namespace internal template< typename T > requires is_aggregate_result< T > struct result_traits< T > : internal::aggregate_result< T > {}; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/result_traits_array.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_RESULT_TRAITS_ARRAY_HPP #define TAO_PQ_RESULT_TRAITS_ARRAY_HPP #include #include #include #include namespace tao::pq { namespace internal { template< typename T > concept array_result_type = pq::is_array_result< T > && ( pq::is_array_result< typename T::value_type > || ( result_traits_size< typename T::value_type > == 1 ) ); [[nodiscard]] auto parse_quoted( const char*& value ) -> std::string; [[nodiscard]] auto parse_unquoted( const char*& value ) -> std::string; template< typename T > [[nodiscard]] auto parse( const char*& value ) -> T { if( *value == '"' ) { const std::string input = parse_quoted( ++value ); return result_traits< T >::from( input.c_str() ); } const std::string input = parse_unquoted( value ); if( input == "NULL" ) { if constexpr( requires { result_traits< T >::null(); } ) { return result_traits< T >::null(); } else { throw std::invalid_argument( "unexpected NULL value" ); } } return result_traits< T >::from( input.c_str() ); } template< typename T > requires pq::is_array_result< T > [[nodiscard]] auto parse( const char*& value ) -> T { if( *value++ != '{' ) { throw std::invalid_argument( "expected '{'" ); } T container; if( *value == '}' ) { ++value; return container; } while( true ) { using value_type = typename T::value_type; if constexpr( requires { container.push_back( parse< value_type >( value ) ); } ) { container.push_back( parse< value_type >( value ) ); } else { container.insert( parse< value_type >( value ) ); } switch( *value++ ) { case ',': case ';': break; case '}': return container; default: throw std::invalid_argument( "expected ',', ';', or '}'" ); } } } } // namespace internal template< internal::array_result_type T > struct result_traits< T > { static auto from( const char* value ) -> T { const auto container = internal::parse< T >( value ); if( *value != '\0' ) { throw std::invalid_argument( "unexpected additional data" ); } return container; } }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/result_traits_optional.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_RESULT_TRAITS_OPTIONAL_HPP #define TAO_PQ_RESULT_TRAITS_OPTIONAL_HPP #include #include #include template< typename T > struct tao::pq::result_traits< std::optional< T > > { static constexpr std::size_t size = result_traits_size< T >; [[nodiscard]] static auto null() noexcept -> std::optional< T > { return std::nullopt; } [[nodiscard]] static auto from( const char* value ) -> std::optional< T > { return result_traits< T >::from( value ); } template< typename Row > [[nodiscard]] static auto from( const Row& row ) -> std::optional< T > { for( std::size_t column = 0; column < row.columns(); ++column ) { if( !row.is_null( column ) ) { return result_traits< T >::from( row ); } } return null(); } }; #endif ================================================ FILE: include/tao/pq/result_traits_pair.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_RESULT_TRAITS_PAIR_HPP #define TAO_PQ_RESULT_TRAITS_PAIR_HPP #include #include #include #include template< typename T, typename U > struct tao::pq::result_traits< std::pair< T, U > > { using DT = std::decay_t< T >; using DU = std::decay_t< U >; static constexpr std::size_t size = result_traits_size< DT > + result_traits_size< DU >; template< typename Row > [[nodiscard]] static auto from( const Row& row ) { return std::pair< T, U >( row.template get< DT >( 0 ), row.template get< DU >( result_traits_size< DT > ) ); } }; #endif ================================================ FILE: include/tao/pq/result_traits_tuple.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_RESULT_TRAITS_TUPLE_HPP #define TAO_PQ_RESULT_TRAITS_TUPLE_HPP #include #include #include #include #include #include template<> struct tao::pq::result_traits< std::tuple<> > { static constexpr std::size_t size = 0; }; template< typename T > struct tao::pq::result_traits< std::tuple< T > > { static constexpr std::size_t size = result_traits_size< T >; template< typename U = T > requires std::is_same_v< T, U > && requires { result_traits< T >::null(); } [[nodiscard]] static auto null() -> std::tuple< T > { return std::tuple< T >( result_traits< T >::null() ); } [[nodiscard]] static auto from( const char* value ) { return std::tuple< T >( result_traits< T >::from( value ) ); } }; template< typename... Ts > requires( sizeof...( Ts ) >= 2 ) struct tao::pq::result_traits< std::tuple< Ts... > > { static constexpr std::size_t size = ( 0 + ... + result_traits_size< Ts > ); template< typename Row, std::size_t... Ns > [[nodiscard]] static auto from( const Row& row, std::index_sequence< Ns... > /*unused*/ ) { return std::tuple< Ts... >( row.template get< Ts >( Ns )... ); } template< typename Row > [[nodiscard]] static auto from( const Row& row ) { return result_traits::from( row, internal::exclusive_scan_t< std::index_sequence< result_traits_size< Ts >... > >() ); } }; #endif ================================================ FILE: include/tao/pq/row.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_ROW_HPP #define TAO_PQ_ROW_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { class result; class row { protected: friend class field; friend class result; const result* m_result = nullptr; std::size_t m_row = 0; std::size_t m_offset = 0; std::size_t m_columns = 0; row() = default; row( const result& in_result, const std::size_t in_row, const std::size_t in_offset, const std::size_t in_columns ) noexcept : m_result( &in_result ), m_row( in_row ), m_offset( in_offset ), m_columns( in_columns ) {} void ensure_column( const std::size_t column ) const; public: [[nodiscard]] auto slice( const std::size_t offset, const std::size_t in_columns ) const -> row; [[nodiscard]] auto columns() const noexcept -> std::size_t { return m_columns; } [[nodiscard]] auto name( const std::size_t column ) const -> std::string; [[nodiscard]] auto index( const internal::zsv in_name ) const -> std::size_t; private: class const_iterator : private field { private: friend class row; explicit const_iterator( const field& f ) noexcept : field( f ) {} public: using difference_type = std::int32_t; using value_type = const field; using pointer = const field*; using reference = const field&; using iterator_category = std::random_access_iterator_tag; const_iterator() = default; auto operator++() noexcept -> const_iterator& { ++m_column; return *this; } auto operator++( int ) noexcept -> const_iterator { const_iterator nrv( *this ); ++*this; return nrv; } auto operator+=( const difference_type n ) noexcept -> const_iterator& { m_column += n; return *this; } auto operator--() noexcept -> const_iterator& { --m_column; return *this; } auto operator--( int ) noexcept -> const_iterator { const_iterator nrv( *this ); --*this; return nrv; } auto operator-=( const difference_type n ) noexcept -> const_iterator& { m_column -= n; return *this; } [[nodiscard]] auto operator*() const noexcept -> const field& { return *this; } [[nodiscard]] auto operator->() const noexcept -> const field* { return this; } [[nodiscard]] auto operator[]( const difference_type n ) const noexcept -> field { return *( *this + n ); } friend void swap( const_iterator& lhs, const_iterator& rhs ) noexcept { swap( static_cast< field& >( lhs ), static_cast< field& >( rhs ) ); } [[nodiscard]] friend auto operator+( const const_iterator& lhs, const difference_type rhs ) noexcept -> const_iterator { const_iterator nrv( lhs ); nrv += rhs; return nrv; } [[nodiscard]] friend auto operator+( const difference_type lhs, const const_iterator& rhs ) noexcept -> const_iterator { const_iterator nrv( rhs ); nrv += lhs; return nrv; } [[nodiscard]] friend auto operator-( const const_iterator& lhs, const difference_type rhs ) noexcept -> const_iterator { const_iterator nrv( lhs ); nrv -= rhs; return nrv; } [[nodiscard]] friend auto operator-( const const_iterator& lhs, const const_iterator& rhs ) noexcept -> difference_type { return static_cast< difference_type >( lhs.index() ) - static_cast< difference_type >( rhs.index() ); } [[nodiscard]] friend auto operator==( const const_iterator& lhs, const const_iterator& rhs ) noexcept { return lhs.index() == rhs.index(); } [[nodiscard]] friend auto operator<=>( const const_iterator& lhs, const const_iterator& rhs ) noexcept { return lhs.index() <=> rhs.index(); } }; public: [[nodiscard]] auto begin() const noexcept -> const_iterator; [[nodiscard]] auto end() const noexcept -> const_iterator; [[nodiscard]] auto cbegin() const noexcept { return begin(); } [[nodiscard]] auto cend() const noexcept { return end(); } [[nodiscard]] auto is_null( const std::size_t column ) const -> bool; [[nodiscard]] auto get( const std::size_t column ) const -> const char*; template< result_type_direct T > [[nodiscard]] auto get( const std::size_t column ) const -> T { if constexpr( requires { result_traits< T >::null(); } ) { if( is_null( column ) ) { return result_traits< T >::null(); } } return result_traits< T >::from( get( column ) ); } template< result_type_composite T > [[nodiscard]] auto get( const std::size_t column ) const -> T { return result_traits< T >::from( slice( column, result_traits_size< T > ) ); } template< result_type T > [[nodiscard]] auto optional( const std::size_t column ) const { return get< std::optional< T > >( column ); } template< result_type T > [[nodiscard]] auto as() const -> T { if( result_traits_size< T > != m_columns ) { throw std::out_of_range( std::format( "datatype '{}' requires {} columns, but row/slice has {} columns", internal::demangle< T >(), result_traits_size< T >, m_columns ) ); } return get< T >( 0 ); } template< typename T > requires is_aggregate_result< T > [[nodiscard]] operator T() const { return as< T >(); } template< result_type T > [[nodiscard]] auto optional() const { return as< std::optional< T > >(); } template< result_type T, result_type U > [[nodiscard]] auto pair() const { return as< std::pair< T, U > >(); } template< result_type... Ts > [[nodiscard]] auto tuple() const { return as< std::tuple< Ts... > >(); } [[nodiscard]] auto at( const std::size_t column ) const -> field; [[nodiscard]] auto operator[]( const std::size_t column ) const noexcept -> field { return { *this, m_offset + column }; } [[nodiscard]] auto at( const internal::zsv in_name ) const -> field { // row::index does the necessary checks, so we forward to operator[] return ( *this )[ row::index( in_name ) ]; } [[nodiscard]] auto operator[]( const internal::zsv in_name ) const -> field { return ( *this )[ row::index( in_name ) ]; } friend void swap( row& lhs, row& rhs ) noexcept { std::swap( lhs.m_result, rhs.m_result ); std::swap( lhs.m_row, rhs.m_row ); std::swap( lhs.m_offset, rhs.m_offset ); std::swap( lhs.m_columns, rhs.m_columns ); } }; template< result_type T > requires( result_traits_size< T > == 1 ) auto field::as() const -> T { return m_row->get< T >( m_column ); } } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/table_field.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_TABLE_FIELD_HPP #define TAO_PQ_TABLE_FIELD_HPP #include #include #include #include #include namespace tao::pq { class table_row; class table_field { private: friend class table_row; const table_row* m_row = nullptr; std::size_t m_column = 0; table_field() = default; table_field( const table_row& row, const std::size_t column ) noexcept : m_row( &row ), m_column( column ) {} public: [[nodiscard]] auto index() const noexcept -> std::size_t; [[nodiscard]] auto is_null() const -> bool; [[nodiscard]] auto get() const -> const char*; template< result_type T > requires( result_traits_size< T > == 1 ) [[nodiscard]] auto as() const -> T; // implemented in table_row.hpp template< result_type T > requires( result_traits_size< T > == 1 ) [[nodiscard]] auto optional() const { return as< std::optional< T > >(); } [[nodiscard]] auto operator==( null_t /*unused*/ ) const { return is_null(); } friend void swap( table_field& lhs, table_field& rhs ) noexcept { std::swap( lhs.m_row, rhs.m_row ); std::swap( lhs.m_column, rhs.m_column ); } }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/table_reader.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_TABLE_READER_HPP #define TAO_PQ_TABLE_READER_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { class table_reader final { protected: std::shared_ptr< transaction_base > m_previous; std::shared_ptr< transaction > m_transaction; std::size_t m_columns; // NOLINT(modernize-use-default-member-init) std::unique_ptr< char, decltype( &PQfreemem ) > m_buffer; std::vector< const char* > m_data; void check_result(); public: template< parameter_type... As > table_reader( const std::shared_ptr< transaction >& transaction, const internal::zsv statement, As&&... as ) : m_previous( transaction ), m_transaction( std::make_shared< internal::transaction_guard >( transaction->connection() ) ), m_columns( 0 ), m_buffer( nullptr, &PQfreemem ) { m_transaction->send( statement, std::forward< As >( as )... ); check_result(); } ~table_reader() = default; table_reader( const table_reader& ) = delete; table_reader( table_reader&& ) = delete; void operator=( const table_reader& ) = delete; void operator=( table_reader&& ) = delete; [[nodiscard]] auto columns() const noexcept -> std::size_t { return m_columns; } // note: the following API is experimental and subject to change [[nodiscard]] auto get_raw_data() -> std::string_view; [[nodiscard]] auto parse_data() noexcept -> bool; [[nodiscard]] auto get_row() -> bool { std::ignore = get_raw_data(); return parse_data(); } [[nodiscard]] auto has_data() const noexcept -> bool { return !m_data.empty(); } [[nodiscard]] auto raw_data() const noexcept -> const std::vector< const char* >& { return m_data; } [[nodiscard]] auto row() noexcept -> table_row { assert( has_data() ); return { *this, 0, columns() }; } private: class const_iterator : private table_row { private: friend class table_reader; const_iterator( const table_row& r ) noexcept : table_row( r ) {} public: using difference_type = std::int32_t; using value_type = const table_row; using pointer = const table_row*; using reference = const table_row&; auto operator++() noexcept -> const_iterator& { if( !m_reader->get_row() ) { m_columns = 0; } return *this; } [[nodiscard]] auto operator*() const noexcept -> const table_row& { return *this; } [[nodiscard]] auto operator->() const noexcept -> const table_row* { return this; } friend void swap( const_iterator& lhs, const_iterator& rhs ) noexcept { swap( static_cast< table_row& >( lhs ), static_cast< table_row& >( rhs ) ); } [[nodiscard]] friend auto operator==( const const_iterator& lhs, const const_iterator& rhs ) noexcept { return lhs.m_columns == rhs.m_columns; } }; public: [[nodiscard]] auto begin() -> const_iterator; [[nodiscard]] auto end() noexcept -> const_iterator; [[nodiscard]] auto cbegin() { return begin(); } [[nodiscard]] auto cend() noexcept { return end(); } template< typename T > requires result_type< typename T::value_type > [[nodiscard]] auto as_container() -> T { T nrv; for( const auto& row : *this ) { nrv.insert( nrv.end(), row.as< typename T::value_type >() ); } return nrv; } template< typename... Ts > requires result_type< typename std::vector< Ts... >::value_type > [[nodiscard]] auto vector() { return as_container< std::vector< Ts... > >(); } template< typename... Ts > requires result_type< typename std::list< Ts... >::value_type > [[nodiscard]] auto list() { return as_container< std::list< Ts... > >(); } template< typename... Ts > requires result_type< typename std::set< Ts... >::value_type > [[nodiscard]] auto set() { return as_container< std::set< Ts... > >(); } template< typename... Ts > requires result_type< typename std::multiset< Ts... >::value_type > [[nodiscard]] auto multiset() { return as_container< std::multiset< Ts... > >(); } template< typename... Ts > requires result_type< typename std::unordered_set< Ts... >::value_type > [[nodiscard]] auto unordered_set() { return as_container< std::unordered_set< Ts... > >(); } template< typename... Ts > requires result_type< typename std::unordered_multiset< Ts... >::value_type > [[nodiscard]] auto unordered_multiset() { return as_container< std::unordered_multiset< Ts... > >(); } template< typename... Ts > requires result_type< typename std::map< Ts... >::value_type > [[nodiscard]] auto map() { return as_container< std::map< Ts... > >(); } template< typename... Ts > requires result_type< typename std::multimap< Ts... >::value_type > [[nodiscard]] auto multimap() { return as_container< std::multimap< Ts... > >(); } template< typename... Ts > requires result_type< typename std::unordered_map< Ts... >::value_type > [[nodiscard]] auto unordered_map() { return as_container< std::unordered_map< Ts... > >(); } template< typename... Ts > requires result_type< typename std::unordered_multimap< Ts... >::value_type > [[nodiscard]] auto unordered_multimap() { return as_container< std::unordered_multimap< Ts... > >(); } }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/table_row.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_TABLE_ROW_HPP #define TAO_PQ_TABLE_ROW_HPP #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { class table_reader; class table_row { protected: friend class table_field; friend class table_reader; table_reader* m_reader; std::size_t m_offset; std::size_t m_columns; table_row( table_reader& in_reader, const std::size_t in_offset, const std::size_t in_columns ) noexcept : m_reader( &in_reader ), m_offset( in_offset ), m_columns( in_columns ) {} void ensure_column( const std::size_t column ) const; public: [[nodiscard]] auto slice( const std::size_t offset, const std::size_t in_columns ) const -> table_row; [[nodiscard]] auto columns() const noexcept -> std::size_t { return m_columns; } private: class const_iterator : private table_field { private: friend class table_row; explicit const_iterator( const table_field& f ) noexcept : table_field( f ) {} public: using difference_type = std::int32_t; using value_type = const table_field; using pointer = const table_field*; using reference = const table_field&; using iterator_category = std::random_access_iterator_tag; const_iterator() = default; auto operator++() noexcept -> const_iterator& { ++m_column; return *this; } auto operator++( int ) noexcept -> const_iterator { const_iterator nrv( *this ); ++*this; return nrv; } auto operator+=( const difference_type n ) noexcept -> const_iterator& { m_column += n; return *this; } auto operator--() noexcept -> const_iterator& { --m_column; return *this; } auto operator--( int ) noexcept -> const_iterator { const_iterator nrv( *this ); --*this; return nrv; } auto operator-=( const difference_type n ) noexcept -> const_iterator& { m_column -= n; return *this; } [[nodiscard]] auto operator*() const noexcept -> const table_field& { return *this; } [[nodiscard]] auto operator->() const noexcept -> const table_field* { return this; } [[nodiscard]] auto operator[]( const difference_type n ) const noexcept -> table_field { return *( *this + n ); } friend void swap( const_iterator& lhs, const_iterator& rhs ) noexcept { swap( static_cast< table_field& >( lhs ), static_cast< table_field& >( rhs ) ); } [[nodiscard]] friend auto operator+( const const_iterator& lhs, const difference_type rhs ) noexcept -> const_iterator { const_iterator nrv( lhs ); nrv += rhs; return nrv; } [[nodiscard]] friend auto operator+( const difference_type lhs, const const_iterator& rhs ) noexcept -> const_iterator { const_iterator nrv( rhs ); nrv += lhs; return nrv; } [[nodiscard]] friend auto operator-( const const_iterator& lhs, const difference_type rhs ) noexcept -> const_iterator { const_iterator nrv( lhs ); nrv -= rhs; return nrv; } [[nodiscard]] friend auto operator-( const const_iterator& lhs, const const_iterator& rhs ) noexcept -> difference_type { return static_cast< difference_type >( lhs.index() ) - static_cast< difference_type >( rhs.index() ); } [[nodiscard]] friend auto operator==( const const_iterator& lhs, const const_iterator& rhs ) noexcept { return lhs.index() == rhs.index(); } [[nodiscard]] friend auto operator<=>( const const_iterator& lhs, const const_iterator& rhs ) noexcept { return lhs.index() <=> rhs.index(); } }; public: [[nodiscard]] auto begin() const noexcept -> const_iterator; [[nodiscard]] auto end() const noexcept -> const_iterator; [[nodiscard]] auto cbegin() const noexcept { return begin(); } [[nodiscard]] auto cend() const noexcept { return end(); } [[nodiscard]] auto is_null( const std::size_t column ) const -> bool; [[nodiscard]] auto get( const std::size_t column ) const -> const char*; template< result_type_direct T > [[nodiscard]] auto get( const std::size_t column ) const -> T { const char* const value = get( column ); if( value == nullptr ) { if constexpr( requires { result_traits< T >::null(); } ) { return result_traits< T >::null(); } else { throw std::invalid_argument( "unexpected NULL value" ); } } return result_traits< T >::from( value ); } template< result_type_composite T > [[nodiscard]] auto get( const std::size_t column ) const -> T { return result_traits< T >::from( slice( column, result_traits_size< T > ) ); } template< result_type T > [[nodiscard]] auto optional( const std::size_t column ) const { return get< std::optional< T > >( column ); } template< result_type T > [[nodiscard]] auto as() const -> T { if( result_traits_size< T > != m_columns ) { throw std::out_of_range( std::format( "datatype '{}' requires {} columns, but table_row/slice has {} columns", internal::demangle< T >(), result_traits_size< T >, m_columns ) ); } return get< T >( 0 ); } template< result_type T > [[nodiscard]] auto optional() const { return as< std::optional< T > >(); } template< result_type T, result_type U > [[nodiscard]] auto pair() const { return as< std::pair< T, U > >(); } template< result_type... Ts > [[nodiscard]] auto tuple() const { return as< std::tuple< Ts... > >(); } [[nodiscard]] auto at( const std::size_t column ) const -> table_field; [[nodiscard]] auto operator[]( const std::size_t column ) const noexcept -> table_field { return { *this, m_offset + column }; } friend void swap( table_row& lhs, table_row& rhs ) noexcept { std::swap( lhs.m_reader, rhs.m_reader ); std::swap( lhs.m_offset, rhs.m_offset ); std::swap( lhs.m_columns, rhs.m_columns ); } }; template< result_type T > requires( result_traits_size< T > == 1 ) auto table_field::as() const -> T { return m_row->get< T >( m_column ); } } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/table_writer.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_TABLE_WRITER_HPP #define TAO_PQ_TABLE_WRITER_HPP #include #include #include #include #if !( defined( __cpp_pack_indexing ) && ( __cplusplus >= 202302L ) ) #include #endif #include #include #include #include #include #include #include namespace tao::pq { class table_writer final { protected: std::shared_ptr< transaction_base > m_previous; std::shared_ptr< transaction > m_transaction; #if defined( __cpp_pack_indexing ) && ( __cplusplus >= 202302L ) template< std::size_t... Os, std::size_t... Is > void insert_indexed( std::index_sequence< Os... > /*unused*/, std::index_sequence< Is... > /*unused*/, const auto&... ts ) { std::string buffer; ( ( ts...[ Os ].template copy_to< Is >( buffer ), buffer += '\t' ), ... ); *buffer.rbegin() = '\n'; table_writer::insert_raw( buffer ); } template< typename... Ts > void insert_traits( const Ts&... ts ) { using gen = internal::gen< Ts::columns... >; table_writer::insert_indexed( typename gen::outer_sequence(), typename gen::inner_sequence(), ts... ); } #else template< std::size_t... Os, std::size_t... Is, typename... Ts > void insert_indexed( std::index_sequence< Os... > /*unused*/, std::index_sequence< Is... > /*unused*/, const std::tuple< Ts... >& tuple ) { std::string buffer; ( ( std::get< Os >( tuple ).template copy_to< Is >( buffer ), buffer += '\t' ), ... ); *buffer.rbegin() = '\n'; table_writer::insert_raw( buffer ); } template< typename... Ts > void insert_traits( const Ts&... ts ) { using gen = internal::gen< Ts::columns... >; table_writer::insert_indexed( typename gen::outer_sequence(), typename gen::inner_sequence(), std::tie( ts... ) ); } #endif void check_result(); public: template< typename... As > table_writer( const std::shared_ptr< transaction >& transaction, const internal::zsv statement, As&&... as ) : m_previous( transaction ), m_transaction( std::make_shared< internal::transaction_guard >( transaction->connection() ) ) { m_transaction->send( statement, std::forward< As >( as )... ); check_result(); } ~table_writer(); table_writer( const table_writer& ) = delete; table_writer( table_writer&& ) = delete; void operator=( const table_writer& ) = delete; void operator=( table_writer&& ) = delete; void insert_raw( const std::string_view data ); template< parameter_type... As > requires( sizeof...( As ) >= 1 ) void insert( As&&... as ) { return insert_traits( parameter_traits< std::decay_t< As > >( std::forward< As >( as ) )... ); } auto commit() -> std::size_t; }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/transaction.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_TRANSACTION_HPP #define TAO_PQ_TRANSACTION_HPP #include #include #include #include #include #include #include namespace tao::pq { class pipeline; class transaction : public transaction_base { protected: using transaction_base::transaction_base; [[nodiscard]] virtual auto v_is_direct() const noexcept -> bool = 0; virtual void v_commit() = 0; virtual void v_rollback() = 0; virtual void v_reset() noexcept = 0; void rollback_in_dtor() noexcept; public: [[nodiscard]] auto subtransaction() -> std::shared_ptr< transaction >; [[nodiscard]] auto pipeline() -> std::shared_ptr< pq::pipeline >; template< parameter_type... As > auto execute( const internal::zsv statement, As&&... as ) { const auto start = std::chrono::steady_clock::now(); transaction_base::send( statement, std::forward< As >( as )... ); return transaction_base::get_result( start ); } void commit(); void rollback(); }; namespace internal { class subtransaction_base : public transaction { private: const std::shared_ptr< pq::transaction_base > m_previous; protected: explicit subtransaction_base( const std::shared_ptr< pq::connection >& connection ) : transaction( connection ), m_previous( current_transaction()->shared_from_this() ) { current_transaction() = this; } ~subtransaction_base() override { if( m_connection ) { current_transaction() = m_previous.get(); // LCOV_EXCL_LINE } } [[nodiscard]] auto v_is_direct() const noexcept -> bool final { return false; } void v_reset() noexcept final { current_transaction() = m_previous.get(); m_connection.reset(); } public: subtransaction_base( const subtransaction_base& ) = delete; subtransaction_base( subtransaction_base&& ) = delete; void operator=( const subtransaction_base& ) = delete; void operator=( subtransaction_base&& ) = delete; }; // blocker for table_reader and table_writer class transaction_guard final : public subtransaction_base { public: explicit transaction_guard( const std::shared_ptr< pq::connection >& connection ) : subtransaction_base( connection ) {} private: // LCOV_EXCL_START void v_commit() override {} void v_rollback() override {} // LCOV_EXCL_STOP }; } // namespace internal } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/transaction_base.hpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_TRANSACTION_BASE_HPP #define TAO_PQ_TRANSACTION_BASE_HPP #include #include #include #if !( defined( __cpp_pack_indexing ) && ( __cplusplus >= 202302L ) ) #include #endif #include #include #include #include #include #include #include #include namespace tao::pq { class connection; class table_reader; class table_writer; class transaction_base : public std::enable_shared_from_this< transaction_base > { protected: std::shared_ptr< pq::connection > m_connection; friend class table_reader; friend class table_writer; explicit transaction_base( const std::shared_ptr< pq::connection >& connection ) noexcept; public: virtual ~transaction_base() = default; transaction_base( const transaction_base& ) = delete; transaction_base( transaction_base&& ) = delete; void operator=( const transaction_base& ) = delete; void operator=( transaction_base&& ) = delete; protected: [[nodiscard]] auto current_transaction() const noexcept -> transaction_base*&; void check_current_transaction() const; void send_params( const char* statement, const int n_params, const Oid types[], const char* const values[], const int lengths[], const int formats[] ); #if defined( __cpp_pack_indexing ) && ( __cplusplus >= 202302L ) template< std::size_t... Os, std::size_t... Is > void send_indexed( const char* statement, std::index_sequence< Os... > /*unused*/, std::index_sequence< Is... > /*unused*/, const auto&... ts ) { const Oid types[] = { static_cast< Oid >( ts...[ Os ].template type< Is >() )... }; const char* const values[] = { ts...[ Os ].template value< Is >()... }; const int lengths[] = { ts...[ Os ].template length< Is >()... }; const int formats[] = { ts...[ Os ].template format< Is >()... }; send_params( statement, sizeof...( Os ), types, values, lengths, formats ); } template< typename... Ts > void send_traits( const char* statement, const Ts&... ts ) { using gen = internal::gen< Ts::columns... >; transaction_base::send_indexed( statement, typename gen::outer_sequence(), typename gen::inner_sequence(), ts... ); } #else template< std::size_t... Os, std::size_t... Is, typename... Ts > void send_indexed( const char* statement, std::index_sequence< Os... > /*unused*/, std::index_sequence< Is... > /*unused*/, const std::tuple< Ts... >& tuple ) { const Oid types[] = { static_cast< Oid >( std::get< Os >( tuple ).template type< Is >() )... }; const char* const values[] = { std::get< Os >( tuple ).template value< Is >()... }; const int lengths[] = { std::get< Os >( tuple ).template length< Is >()... }; const int formats[] = { std::get< Os >( tuple ).template format< Is >()... }; send_params( statement, sizeof...( Os ), types, values, lengths, formats ); } template< typename... Ts > void send_traits( const char* statement, const Ts&... ts ) { using gen = internal::gen< Ts::columns... >; transaction_base::send_indexed( statement, typename gen::outer_sequence(), typename gen::inner_sequence(), std::tie( ts... ) ); } #endif public: [[nodiscard]] auto connection() const noexcept -> const std::shared_ptr< pq::connection >& { return m_connection; } void send( const internal::zsv statement ) { send_params( statement, 0, nullptr, nullptr, nullptr, nullptr ); } template< parameter_type_direct... As > void send( const internal::zsv statement, As&&... as ) { send_traits( statement, parameter_traits< std::decay_t< As > >( std::forward< As >( as ) )... ); } template< parameter_type... As > requires( parameter_type_dynamic< As > || ... ) void send( const internal::zsv statement, As&&... as ) { const parameter< internal::parameter_size< As... > > p( std::forward< As >( as )... ); send_params( statement, p.m_size, p.m_types, p.m_values, p.m_lengths, p.m_formats ); } template< parameter_type_dynamic A > void send( const internal::zsv statement, A&& p ) { send_params( statement, p.m_size, p.m_types, p.m_values, p.m_lengths, p.m_formats ); } void set_single_row_mode(); #if defined( LIBPQ_HAS_CHUNK_MODE ) void set_chunk_mode( const int rows ); #endif [[nodiscard]] auto get_result( const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now() ) -> result; void consume_pipeline_sync( const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now() ); }; } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/transaction_status.hpp ================================================ // Copyright (c) 2022-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_TRANSACTION_STATUS_HPP #define TAO_PQ_TRANSACTION_STATUS_HPP #include #include #include #include namespace tao::pq { enum class transaction_status : std::uint8_t { idle = PQTRANS_IDLE, in_transaction = PQTRANS_INTRANS, active = PQTRANS_ACTIVE, error = PQTRANS_INERROR, unknown = PQTRANS_UNKNOWN }; [[nodiscard]] constexpr auto taopq_format_as( const transaction_status ts ) noexcept -> std::string_view { switch( ts ) { case transaction_status::idle: return "idle"; case transaction_status::in_transaction: return "in_transaction"; case transaction_status::active: return "active"; case transaction_status::error: return "error"; case transaction_status::unknown: return "unknown"; default: return ""; } } } // namespace tao::pq #endif ================================================ FILE: include/tao/pq/version.hpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_VERSION_HPP #define TAO_PQ_VERSION_HPP #define TAO_PQ_VERSION "0.9.0" #define TAO_PQ_VERSION_MAJOR 0 #define TAO_PQ_VERSION_MINOR 9 #define TAO_PQ_VERSION_PATCH 0 #endif ================================================ FILE: include/tao/pq.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef TAO_PQ_HPP #define TAO_PQ_HPP // NOLINTBEGIN(misc-include-cleaner) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // NOLINTEND(misc-include-cleaner) #endif ================================================ FILE: src/lib/pq/connection.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { namespace internal { class transaction_base : public transaction { protected: explicit transaction_base( const std::shared_ptr< pq::connection >& connection ) : transaction( connection ) { if( current_transaction() != nullptr ) { throw std::logic_error( "invalid transaction order" ); } current_transaction() = this; } ~transaction_base() override { if( m_connection ) { current_transaction() = nullptr; } } void v_reset() noexcept final { current_transaction() = nullptr; m_connection.reset(); } public: transaction_base( const transaction_base& ) = delete; transaction_base( transaction_base&& ) = delete; void operator=( const transaction_base& ) = delete; void operator=( transaction_base&& ) = delete; }; class autocommit_transaction final : public transaction_base { public: explicit autocommit_transaction( const std::shared_ptr< pq::connection >& connection ) : transaction_base( connection ) {} private: [[nodiscard]] auto v_is_direct() const noexcept -> bool override { return true; } void v_commit() override {} void v_rollback() override {} }; namespace { [[nodiscard]] inline auto isolation_level_extension( const isolation_level il ) -> const char* { switch( il ) { case isolation_level::default_isolation_level: return ""; case isolation_level::serializable: return " ISOLATION LEVEL SERIALIZABLE"; case isolation_level::repeatable_read: return " ISOLATION LEVEL REPEATABLE READ"; case isolation_level::read_committed: return " ISOLATION LEVEL READ COMMITTED"; case isolation_level::read_uncommitted: return " ISOLATION LEVEL READ UNCOMMITTED"; } TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE } [[nodiscard]] inline auto access_mode_extension( const access_mode am ) -> const char* { switch( am ) { case access_mode::default_access_mode: return ""; case access_mode::read_write: return " READ WRITE"; case access_mode::read_only: return " READ ONLY"; } TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE } } // namespace class top_level_transaction final : public transaction_base { public: top_level_transaction( const std::shared_ptr< pq::connection >& connection, const isolation_level il, const access_mode am ) : transaction_base( connection ) { this->execute( std::format( "START TRANSACTION{}{}", isolation_level_extension( il ), access_mode_extension( am ) ) ); } ~top_level_transaction() override { if( m_connection && m_connection->attempt_rollback() ) { rollback_in_dtor(); } } top_level_transaction( const top_level_transaction& ) = delete; top_level_transaction( top_level_transaction&& ) = delete; void operator=( const top_level_transaction& ) = delete; void operator=( top_level_transaction&& ) = delete; private: [[nodiscard]] auto v_is_direct() const noexcept -> bool override { return false; } void v_commit() override { execute( "COMMIT TRANSACTION" ); } void v_rollback() override { execute( "ROLLBACK TRANSACTION" ); } }; namespace { [[nodiscard]] constexpr auto is_identifier( const std::string_view value ) noexcept -> bool { return !value.empty() && ( std::isdigit( static_cast< unsigned char >( value[ 0 ] ) ) == 0 ) && ( value.find_first_not_of( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" ) == std::string_view::npos ); } } // namespace } // namespace internal auto connection::escape_identifier( const std::string_view identifier ) const -> std::unique_ptr< char, decltype( &PQfreemem ) > { std::unique_ptr< char, decltype( &PQfreemem ) > buffer( PQescapeIdentifier( m_pgconn.get(), identifier.data(), identifier.size() ), &PQfreemem ); if( !buffer ) { throw std::invalid_argument( error_message() ); // LCOV_EXCL_LINE } return buffer; } auto connection::attempt_rollback() const noexcept -> bool { switch( transaction_status() ) { // LCOV_EXCL_START case transaction_status::idle: case transaction_status::active: return false; // LCOV_EXCL_STOP case transaction_status::in_transaction: case transaction_status::error: case transaction_status::unknown: return true; } TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE } void connection::check_prepared_name( const std::string_view name ) { if( !internal::is_identifier( name ) ) { throw std::invalid_argument( std::format( "invalid prepared statement name: {}", name ) ); } } void connection::send_params( const char* statement, const int n_params, const Oid types[], const char* const values[], const int lengths[], const int formats[] ) { const auto is_prepared = m_prepared_statements.contains( statement ); if( m_log ) { if( is_prepared ) { if( m_log->connection.send_query_prepared ) { m_log->connection.send_query_prepared( *this, statement, n_params, values, lengths, formats ); } } else { if( m_log->connection.send_query ) { m_log->connection.send_query( *this, statement, n_params, types, values, lengths, formats ); } } } const auto result = is_prepared ? PQsendQueryPrepared( m_pgconn.get(), statement, n_params, values, lengths, formats, 0 ) : PQsendQueryParams( m_pgconn.get(), statement, n_params, types, values, lengths, formats, 0 ); if( m_log ) { if( is_prepared ) { if( m_log->connection.send_query_prepared.result ) { m_log->connection.send_query_prepared.result( *this, result ); } } else { if( m_log->connection.send_query.result ) { m_log->connection.send_query.result( *this, result ); } } } if( result == 0 ) { throw pq::connection_error( error_message() ); // LCOV_EXCL_LINE } } void connection::wait( const bool wait_for_write, const std::chrono::steady_clock::time_point end ) { if( m_log && m_log->connection.wait ) { m_log->connection.wait( *this, wait_for_write, end ); } while( true ) { int timeout_ms = -1; if( m_timeout ) { timeout_ms = std::max( static_cast< int >( std::chrono::duration_cast< std::chrono::milliseconds >( end - std::chrono::steady_clock::now() ).count() ), 0 ); } const auto so = socket(); if( m_log && m_log->connection.poll ) { m_log->connection.poll( *this, so, wait_for_write, timeout_ms ); } const auto status = m_poll( so, wait_for_write, timeout_ms ); if( m_log && m_log->connection.poll.result ) { m_log->connection.poll.result( *this, so, status ); } switch( status ) { case poll::status::timeout: m_pgconn.reset(); throw timeout_reached( "timeout reached" ); case poll::status::readable: get_notifications(); return; // LCOV_EXCL_START case poll::status::writable: return; case poll::status::again: break; default: TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_STOP } } } void connection::cancel() { const std::unique_ptr< PGcancel, decltype( &PQfreeCancel ) > p( PQgetCancel( m_pgconn.get() ), &PQfreeCancel ); if( p ) { char buffer[ 256 ]; if( PQcancel( p.get(), buffer, sizeof( buffer ) ) == 0 ) { throw pq::error( buffer ); // LCOV_EXCL_LINE } } } auto connection::get_result( const std::chrono::steady_clock::time_point end ) -> std::unique_ptr< PGresult, decltype( &PQclear ) > { if( m_log && m_log->connection.get_result ) { m_log->connection.get_result( *this, end ); } bool wait_for_write = true; while( is_busy() ) { if( wait_for_write ) { wait_for_write = flush(); } connection::wait( wait_for_write, end ); } std::unique_ptr< PGresult, decltype( &PQclear ) > result( PQgetResult( m_pgconn.get() ), &PQclear ); if( m_log && m_log->connection.get_result.result ) { m_log->connection.get_result.result( *this, result.get() ); } handle_notifications(); return result; } auto connection::get_fatal_error( const std::chrono::steady_clock::time_point end ) -> std::unique_ptr< PGresult, decltype( &PQclear ) > { auto result = connection::get_result( end ); if( !result ) { throw std::runtime_error( "unable to obtain result" ); } const auto status = PQresultStatus( result.get() ); if( status != PGRES_FATAL_ERROR ) { throw std::runtime_error( std::format( "unexpected result status: {}", PQresStatus( status ) ) ); } return result; } void connection::consume_empty_result( const std::chrono::steady_clock::time_point end ) { if( const auto result = connection::get_result( end ) ) { const auto status = PQresultStatus( result.get() ); throw std::runtime_error( std::format( "unexpected result status: {}", PQresStatus( status ) ) ); } } auto connection::get_copy_data( char*& buffer, const std::chrono::steady_clock::time_point end ) -> std::size_t { while( true ) { const auto result = PQgetCopyData( m_pgconn.get(), &buffer, 1 ); if( result > 0 ) { return static_cast< std::size_t >( result ); } switch( result ) { case 0: connection::wait( false, end ); break; case -1: return 0; // LCOV_EXCL_START case -2: throw pq::error( std::format( "PQgetCopyData() failed: {}", error_message() ) ); default: TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_STOP } } } auto connection::get_copy_data( char*& buffer ) -> std::size_t { return connection::get_copy_data( buffer, timeout_end() ); } void connection::put_copy_data( const char* buffer, const std::size_t size ) { const auto end = timeout_end(); while( true ) { switch( PQputCopyData( m_pgconn.get(), buffer, static_cast< int >( size ) ) ) { case 1: return; // LCOV_EXCL_START case 0: connection::wait( true, end ); break; case -1: throw pq::error( std::format( "PQputCopyData() failed: {}", error_message() ) ); default: TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_STOP } } } void connection::put_copy_end( const char* error_message ) { const auto end = timeout_end(); while( true ) { switch( PQputCopyEnd( m_pgconn.get(), error_message ) ) { case 1: return; // LCOV_EXCL_START case 0: connection::wait( true, end ); break; case -1: throw pq::error( std::format( "PQputCopyEnd() failed: {}", connection::error_message() ) ); default: TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_STOP } } } void connection::clear_copy_data( const std::chrono::steady_clock::time_point end ) { char* ptr; while( connection::get_copy_data( ptr, end ) > 0 ) { PQfreemem( ptr ); } } connection::connection( const private_key /*unused*/, const std::string& connection_info ) : m_pgconn( PQconnectdb( connection_info.c_str() ), &PQfinish ), m_current_transaction( nullptr ), m_poll( internal::poll ) { if( !is_open() ) { // note that we can not access the sqlstate after PQconnectdb(), // see https://stackoverflow.com/q/23349086/2073257 throw pq::connection_error( error_message() ); } if( PQsetnonblocking( m_pgconn.get(), 1 ) != 0 ) { throw pq::connection_error( error_message() ); // LCOV_EXCL_LINE } } auto connection::create( const std::string& connection_info ) -> std::shared_ptr< connection > { return std::make_shared< connection >( private_key(), connection_info ); } auto connection::error_message() const -> const char* { return PQerrorMessage( m_pgconn.get() ); } auto connection::notification_handler( const std::string_view channel ) const -> std::function< void( const char* payload ) > { const auto it = m_notification_handlers.find( channel ); if( it != m_notification_handlers.end() ) { return it->second; } return {}; } void connection::set_notification_handler( const std::string_view channel, const std::function< void( const char* payload ) >& handler ) { m_notification_handlers[ std::string( channel ) ] = handler; } void connection::reset_notification_handler( const std::string_view channel ) noexcept { const auto it = m_notification_handlers.find( channel ); if( it != m_notification_handlers.end() ) { m_notification_handlers.erase( it ); } } auto connection::status() const noexcept -> connection_status { return static_cast< connection_status >( PQstatus( m_pgconn.get() ) ); } auto connection::transaction_status() const noexcept -> pq::transaction_status { return static_cast< pq::transaction_status >( PQtransactionStatus( m_pgconn.get() ) ); } auto connection::pipeline_status() const noexcept -> pq::pipeline_status { return static_cast< pq::pipeline_status >( PQpipelineStatus( m_pgconn.get() ) ); } void connection::enter_pipeline_mode() { const auto result = PQenterPipelineMode( m_pgconn.get() ); if( m_log && m_log->connection.enter_pipeline_mode.result ) { m_log->connection.enter_pipeline_mode.result( *this, result ); } if( result == 0 ) { throw pq::connection_error( "unable to enter pipeline mode" ); } } void connection::exit_pipeline_mode() { if( m_log && m_log->connection.exit_pipeline_mode ) { m_log->connection.exit_pipeline_mode( *this ); } const auto result = PQexitPipelineMode( m_pgconn.get() ); if( m_log && m_log->connection.exit_pipeline_mode.result ) { m_log->connection.exit_pipeline_mode.result( *this, result ); } if( result == 0 ) { throw pq::connection_error( error_message() ); } } void connection::pipeline_sync() { if( m_log && m_log->connection.pipeline_sync ) { m_log->connection.pipeline_sync( *this ); } const auto result = PQpipelineSync( m_pgconn.get() ); if( m_log && m_log->connection.pipeline_sync.result ) { m_log->connection.pipeline_sync.result( *this, result ); } if( result == 0 ) { throw pq::connection_error( "unable to sync pipeline" ); } } auto connection::is_busy() const noexcept -> bool { const auto result = PQisBusy( m_pgconn.get() ); if( m_log && m_log->connection.is_busy.result ) { m_log->connection.is_busy.result( *this, result ); } return result != 0; } auto connection::flush() -> bool { if( m_log && m_log->connection.flush ) { m_log->connection.flush( *this ); } const auto result = PQflush( m_pgconn.get() ); if( m_log && m_log->connection.flush.result ) { m_log->connection.flush.result( *this, result ); } switch( result ) { case 0: return false; case 1: return true; default: throw pq::error( std::format( "PQflush() failed: {}", error_message() ) ); } } void connection::consume_input() { if( m_log && m_log->connection.consume_input ) { m_log->connection.consume_input( *this ); } const auto result = PQconsumeInput( m_pgconn.get() ); if( m_log && m_log->connection.consume_input.result ) { m_log->connection.consume_input.result( *this, result ); } if( result == 0 ) { throw pq::connection_error( error_message() ); } } auto connection::direct() -> std::shared_ptr< pq::transaction > { return std::make_shared< internal::autocommit_transaction >( shared_from_this() ); } auto connection::transaction() -> std::shared_ptr< pq::transaction > { return std::make_shared< internal::top_level_transaction >( shared_from_this(), isolation_level::default_isolation_level, access_mode::default_access_mode ); } auto connection::transaction( const access_mode am, const isolation_level il ) -> std::shared_ptr< pq::transaction > { return std::make_shared< internal::top_level_transaction >( shared_from_this(), il, am ); } auto connection::transaction( const isolation_level il, const access_mode am ) -> std::shared_ptr< pq::transaction > { return std::make_shared< internal::top_level_transaction >( shared_from_this(), il, am ); } auto connection::pipeline() -> std::shared_ptr< pq::pipeline > { return direct()->pipeline(); } void connection::prepare( std::string name, const internal::zsv statement ) { connection::check_prepared_name( name ); const auto end = timeout_end(); if( PQsendPrepare( m_pgconn.get(), name.c_str(), statement, 0, nullptr ) == 0 ) { throw pq::connection_error( error_message() ); // LCOV_EXCL_LINE } const auto result = connection::get_result( end ); switch( PQresultStatus( result.get() ) ) { case PGRES_COMMAND_OK: connection::consume_empty_result( end ); break; case PGRES_TUPLES_OK: case PGRES_EMPTY_QUERY: case PGRES_COPY_IN: case PGRES_COPY_OUT: TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE default: connection::consume_empty_result( end ); internal::throw_sqlstate( result.get() ); } m_prepared_statements.insert( std::move( name ) ); } void connection::deallocate( const std::string_view name ) { connection::check_prepared_name( name ); const auto it = m_prepared_statements.find( name ); if( it == m_prepared_statements.end() ) { throw std::runtime_error( std::format( "prepared statement not found: {}", name ) ); } connection::execute( std::format( "DEALLOCATE {}", connection::escape_identifier( name ).get() ) ); m_prepared_statements.erase( it ); } void connection::listen( const std::string_view channel ) { connection::execute( std::format( "LISTEN {}", connection::escape_identifier( channel ).get() ) ); } void connection::listen( const std::string_view channel, const std::function< void( const char* payload ) >& handler ) { connection::set_notification_handler( channel, handler ); connection::listen( channel ); } void connection::unlisten( const std::string_view channel ) { connection::execute( std::format( "UNLISTEN {}", connection::escape_identifier( channel ).get() ) ); } void connection::notify( const std::string_view channel ) { connection::execute( std::format( "NOTIFY {}", connection::escape_identifier( channel ).get() ) ); } void connection::notify( const std::string_view channel, const std::string_view payload ) { connection::execute( "SELECT pg_notify( $1, $2 )", channel, payload ); } void connection::handle_notifications() { while( PGnotify* pgnotify = PQnotifies( m_pgconn.get() ) ) { const notification notify( pgnotify ); if( m_notification_handler ) { m_notification_handler( notify ); } const auto it = m_notification_handlers.find( notify.channel() ); if( it != m_notification_handlers.end() ) { it->second( notify.payload() ); } } } void connection::get_notifications() { consume_input(); handle_notifications(); } auto connection::socket() const -> int { const auto fd = PQsocket( m_pgconn.get() ); if( fd < 0 ) { throw pq::error( "PQsocket(): unable to retrieve file descriptor" ); // LCOV_EXCL_LINE } return fd; } auto connection::password( const internal::zsv passwd, const internal::zsv user, const internal::zsv algorithm ) -> std::string { const std::unique_ptr< char, decltype( &PQfreemem ) > buffer( PQencryptPasswordConn( m_pgconn.get(), passwd, user, algorithm ), &PQfreemem ); if( !buffer ) { throw std::invalid_argument( error_message() ); // LCOV_EXCL_LINE } return buffer.get(); } } // namespace tao::pq ================================================ FILE: src/lib/pq/connection_pool.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include namespace tao::pq { auto connection_pool::v_create() const -> std::unique_ptr< pq::connection > { return std::make_unique< pq::connection >( pq::connection::private_key(), m_connection_info ); } connection_pool::connection_pool( const private_key /*unused*/, const std::string_view connection_info ) : m_connection_info( connection_info ), m_poll( internal::poll ) {} auto connection_pool::connection() -> std::shared_ptr< pq::connection > { auto result = internal::pool< pq::connection >::get(); if( m_timeout ) { result->set_timeout( *m_timeout ); } else { result->reset_timeout(); } result->set_poll_callback( m_poll ); return result; } } // namespace tao::pq ================================================ FILE: src/lib/pq/exception.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include namespace tao::pq { sql_error::sql_error( const char* what, const std::string_view in_sqlstate ) : error( what ), sqlstate( in_sqlstate ) {} connection_error::connection_error( const char* what ) : connection_error( what, "08000" ) {} namespace internal { void throw_sqlstate( PGresult* pgresult ) { const char* error_message = PQresultErrorMessage( pgresult ); const char* sql_state = PQresultErrorField( pgresult, PG_DIAG_SQLSTATE ); if( sql_state == nullptr ) { throw std::runtime_error( error_message ); } internal::throw_sqlstate( error_message, sql_state ); } void throw_sqlstate( const char* error_message, const std::string_view sql_state ) { // LCOV_EXCL_START switch( sql_state[ 0 ] ) { case '0': switch( sql_state[ 1 ] ) { case '0': throw success( error_message, sql_state ); case '1': if( sql_state == "01003" ) { throw null_value_eliminated_in_set_function( error_message, sql_state ); } if( sql_state == "01004" ) { throw string_data_right_truncation< warning >( error_message, sql_state ); } if( sql_state == "01006" ) { throw privilege_not_revoked( error_message, sql_state ); } if( sql_state == "01007" ) { throw privilege_not_granted( error_message, sql_state ); } if( sql_state == "01008" ) { throw implicit_zero_bit_padding( error_message, sql_state ); } if( sql_state == "0100C" ) { throw dynamic_result_sets_returned( error_message, sql_state ); } if( sql_state == "01P01" ) { throw deprecated_feature( error_message, sql_state ); } throw warning( error_message, sql_state ); case '2': if( sql_state == "02001" ) { throw no_additional_dynamic_result_sets_returned( error_message, sql_state ); } throw no_data( error_message, sql_state ); case '3': throw sql_statement_not_yet_complete( error_message, sql_state ); case '8': if( sql_state == "08001" ) { throw sqlclient_unable_to_establish_sqlconnection( error_message, sql_state ); } if( sql_state == "08003" ) { throw connection_does_not_exist( error_message, sql_state ); } if( sql_state == "08004" ) { throw sqlserver_rejected_establishment_of_sqlconnection( error_message, sql_state ); } if( sql_state == "08006" ) { throw connection_failure( error_message, sql_state ); } if( sql_state == "08007" ) { throw transaction_resolution_unknown( error_message, sql_state ); } if( sql_state == "08P01" ) { throw protocol_violation( error_message, sql_state ); } throw connection_error( error_message, sql_state ); case '9': throw triggered_action_exception( error_message, sql_state ); case 'A': throw feature_not_supported( error_message, sql_state ); case 'B': throw invalid_transaction_initiation( error_message, sql_state ); case 'F': if( sql_state == "0F001" ) { throw invalid_locator_specification( error_message, sql_state ); } throw locator_exception( error_message, sql_state ); case 'L': if( sql_state == "0LP01" ) { throw invalid_grant_operation( error_message, sql_state ); } throw invalid_grantor( error_message, sql_state ); case 'P': throw invalid_role_specification( error_message, sql_state ); case 'Z': if( sql_state == "0Z002" ) { throw stacked_diagnostics_accessed_without_active_handler( error_message, sql_state ); } throw diagnostics_exception( error_message, sql_state ); default: throw sql_error( error_message, sql_state ); } case '2': switch( sql_state[ 1 ] ) { case '0': throw case_not_found( error_message, sql_state ); case '1': throw cardinality_violation( error_message, sql_state ); case '2': if( sql_state == "2202E" ) { throw array_subscript_error( error_message, sql_state ); } if( sql_state == "22021" ) { throw character_not_in_repertoire( error_message, sql_state ); } if( sql_state == "22008" ) { throw datetime_field_overflow( error_message, sql_state ); } if( sql_state == "22012" ) { throw division_by_zero( error_message, sql_state ); } if( sql_state == "22005" ) { throw error_in_assignment( error_message, sql_state ); } if( sql_state == "2200B" ) { throw escape_character_conflict( error_message, sql_state ); } if( sql_state == "22022" ) { throw indicator_overflow( error_message, sql_state ); } if( sql_state == "22015" ) { throw interval_field_overflow( error_message, sql_state ); } if( sql_state == "2201E" ) { throw invalid_argument_for_logarithm( error_message, sql_state ); } if( sql_state == "22014" ) { throw invalid_argument_for_ntile_function( error_message, sql_state ); } if( sql_state == "22016" ) { throw invalid_argument_for_nth_value_function( error_message, sql_state ); } if( sql_state == "2201F" ) { throw invalid_argument_for_power_function( error_message, sql_state ); } if( sql_state == "2201G" ) { throw invalid_argument_for_width_bucket_function( error_message, sql_state ); } if( sql_state == "22018" ) { throw invalid_character_value_for_cast( error_message, sql_state ); } if( sql_state == "22007" ) { throw invalid_datetime_format( error_message, sql_state ); } if( sql_state == "22019" ) { throw invalid_escape_character( error_message, sql_state ); } if( sql_state == "2200D" ) { throw invalid_escape_octet( error_message, sql_state ); } if( sql_state == "22025" ) { throw invalid_escape_sequence( error_message, sql_state ); } if( sql_state == "22P06" ) { throw nonstandard_use_of_escape_character( error_message, sql_state ); } if( sql_state == "22010" ) { throw invalid_indicator_parameter_value( error_message, sql_state ); } if( sql_state == "22023" ) { throw invalid_parameter_value( error_message, sql_state ); } if( sql_state == "22013" ) { throw invalid_preceding_or_following_size( error_message, sql_state ); } if( sql_state == "2201B" ) { throw invalid_regular_expression( error_message, sql_state ); } if( sql_state == "2201W" ) { throw invalid_row_count_in_limit_clause( error_message, sql_state ); } if( sql_state == "2201X" ) { throw invalid_row_count_in_result_offset_clause( error_message, sql_state ); } if( sql_state == "2202H" ) { throw invalid_tablesample_argument( error_message, sql_state ); } if( sql_state == "2202G" ) { throw invalid_tablesample_repeat( error_message, sql_state ); } if( sql_state == "22009" ) { throw invalid_time_zone_displacement_value( error_message, sql_state ); } if( sql_state == "2200C" ) { throw invalid_use_of_escape_character( error_message, sql_state ); } if( sql_state == "2200G" ) { throw most_specific_type_mismatch( error_message, sql_state ); } if( sql_state == "22004" ) { throw null_value_not_allowed( error_message, sql_state ); } if( sql_state == "22002" ) { throw null_value_no_indicator_parameter( error_message, sql_state ); } if( sql_state == "22003" ) { throw numeric_value_out_of_range( error_message, sql_state ); } if( sql_state == "2200H" ) { throw sequence_generator_limit_exceeded( error_message, sql_state ); } if( sql_state == "22026" ) { throw string_data_length_mismatch( error_message, sql_state ); } if( sql_state == "22001" ) { throw string_data_right_truncation< data_exception >( error_message, sql_state ); } if( sql_state == "22011" ) { throw substring_error( error_message, sql_state ); } if( sql_state == "22027" ) { throw trim_error( error_message, sql_state ); } if( sql_state == "22024" ) { throw unterminated_c_string( error_message, sql_state ); } if( sql_state == "2200F" ) { throw zero_length_character_string( error_message, sql_state ); } if( sql_state == "22P01" ) { throw floating_point_exception( error_message, sql_state ); } if( sql_state == "22P02" ) { throw invalid_text_representation( error_message, sql_state ); } if( sql_state == "22P03" ) { throw invalid_binary_representation( error_message, sql_state ); } if( sql_state == "22P04" ) { throw bad_copy_file_format( error_message, sql_state ); } if( sql_state == "22P05" ) { throw untranslatable_character( error_message, sql_state ); } if( sql_state == "2200L" ) { throw not_an_xml_document( error_message, sql_state ); } if( sql_state == "2200M" ) { throw invalid_xml_document( error_message, sql_state ); } if( sql_state == "2200N" ) { throw invalid_xml_content( error_message, sql_state ); } if( sql_state == "2200S" ) { throw invalid_xml_comment( error_message, sql_state ); } if( sql_state == "2200T" ) { throw invalid_xml_processing_instruction( error_message, sql_state ); } if( sql_state == "22030" ) { throw duplicate_json_object_key_value( error_message, sql_state ); } if( sql_state == "22031" ) { throw invalid_argument_for_sql_json_datetime_function( error_message, sql_state ); } if( sql_state == "22032" ) { throw invalid_json_text( error_message, sql_state ); } if( sql_state == "22033" ) { throw invalid_sql_json_subscript( error_message, sql_state ); } if( sql_state == "22034" ) { throw more_than_one_sql_json_item( error_message, sql_state ); } if( sql_state == "22035" ) { throw no_sql_json_item( error_message, sql_state ); } if( sql_state == "22036" ) { throw non_numeric_sql_json_item( error_message, sql_state ); } if( sql_state == "22037" ) { throw non_unique_keys_in_a_json_object( error_message, sql_state ); } if( sql_state == "22038" ) { throw singleton_sql_json_item_required( error_message, sql_state ); } if( sql_state == "22039" ) { throw sql_json_array_not_found( error_message, sql_state ); } if( sql_state == "2203A" ) { throw sql_json_member_not_found( error_message, sql_state ); } if( sql_state == "2203B" ) { throw sql_json_number_not_found( error_message, sql_state ); } if( sql_state == "2203C" ) { throw sql_json_object_not_found( error_message, sql_state ); } if( sql_state == "2203D" ) { throw too_many_json_array_elements( error_message, sql_state ); } if( sql_state == "2203E" ) { throw too_many_json_object_members( error_message, sql_state ); } if( sql_state == "2203F" ) { throw sql_json_scalar_required( error_message, sql_state ); } throw data_exception( error_message, sql_state ); case '3': if( sql_state == "23001" ) { throw restrict_violation( error_message, sql_state ); } if( sql_state == "23502" ) { throw not_null_violation( error_message, sql_state ); } if( sql_state == "23503" ) { throw foreign_key_violation( error_message, sql_state ); } if( sql_state == "23505" ) { throw unique_violation( error_message, sql_state ); } if( sql_state == "23514" ) { throw check_violation( error_message, sql_state ); } if( sql_state == "23P01" ) { throw exclusion_violation( error_message, sql_state ); } throw integrity_constraint_violation( error_message, sql_state ); case '4': throw invalid_cursor_state( error_message, sql_state ); case '5': if( sql_state == "25001" ) { throw active_sql_transaction( error_message, sql_state ); } if( sql_state == "25002" ) { throw branch_transaction_already_active( error_message, sql_state ); } if( sql_state == "25008" ) { throw held_cursor_requires_same_isolation_level( error_message, sql_state ); } if( sql_state == "25003" ) { throw inappropriate_access_mode_for_branch_transaction( error_message, sql_state ); } if( sql_state == "25004" ) { throw inappropriate_isolation_level_for_branch_transaction( error_message, sql_state ); } if( sql_state == "25005" ) { throw no_active_sql_transaction_for_branch_transaction( error_message, sql_state ); } if( sql_state == "25006" ) { throw read_only_sql_transaction( error_message, sql_state ); } if( sql_state == "25007" ) { throw schema_and_data_statement_mixing_not_supported( error_message, sql_state ); } if( sql_state == "25P01" ) { throw no_active_sql_transaction( error_message, sql_state ); } if( sql_state == "25P02" ) { throw in_failed_sql_transaction( error_message, sql_state ); } if( sql_state == "25P03" ) { throw idle_in_transaction_session_timeout( error_message, sql_state ); } throw invalid_transaction_state( error_message, sql_state ); case '6': throw invalid_sql_statement_name( error_message, sql_state ); case '7': throw triggered_data_change_violation( error_message, sql_state ); case '8': if( sql_state == "28P01" ) { throw invalid_password( error_message, sql_state ); } throw invalid_authorization_specification( error_message, sql_state ); case 'B': if( sql_state == "2BP01" ) { throw dependent_objects_still_exist( error_message, sql_state ); } throw dependent_privilege_descriptors_still_exist( error_message, sql_state ); case 'D': throw invalid_transaction_termination( error_message, sql_state ); case 'F': if( sql_state == "2F002" ) { throw modifying_sql_data_not_permitted< sql_routine_exception >( error_message, sql_state ); } if( sql_state == "2F003" ) { throw prohibited_sql_statement_attempted< sql_routine_exception >( error_message, sql_state ); } if( sql_state == "2F004" ) { throw reading_sql_data_not_permitted< sql_routine_exception >( error_message, sql_state ); } if( sql_state == "2F005" ) { throw function_executed_no_return_statement( error_message, sql_state ); } throw sql_routine_exception( error_message, sql_state ); default: throw sql_error( error_message, sql_state ); } case '3': switch( sql_state[ 1 ] ) { case '4': throw invalid_cursor_name( error_message, sql_state ); case '8': if( sql_state == "38001" ) { throw containing_sql_not_permitted( error_message, sql_state ); } if( sql_state == "38002" ) { throw modifying_sql_data_not_permitted< external_routine_exception >( error_message, sql_state ); } if( sql_state == "38003" ) { throw prohibited_sql_statement_attempted< external_routine_exception >( error_message, sql_state ); } if( sql_state == "38004" ) { throw reading_sql_data_not_permitted< external_routine_exception >( error_message, sql_state ); } throw external_routine_exception( error_message, sql_state ); case '9': if( sql_state == "39001" ) { throw invalid_sqlstate_returned( error_message, sql_state ); } if( sql_state == "39004" ) { throw external_null_value_not_allowed( error_message, sql_state ); } if( sql_state == "39P01" ) { throw trigger_protocol_violated( error_message, sql_state ); } if( sql_state == "39P02" ) { throw srf_protocol_violated( error_message, sql_state ); } if( sql_state == "39P03" ) { throw event_trigger_protocol_violated( error_message, sql_state ); } throw external_routine_invocation_exception( error_message, sql_state ); case 'B': if( sql_state == "3B001" ) { throw invalid_savepoint_specification( error_message, sql_state ); } throw savepoint_exception( error_message, sql_state ); case 'D': throw invalid_catalog_name( error_message, sql_state ); case 'F': throw invalid_schema_name( error_message, sql_state ); default: throw sql_error( error_message, sql_state ); } case '4': switch( sql_state[ 1 ] ) { case '0': if( sql_state == "40001" ) { throw serialization_failure( error_message, sql_state ); } if( sql_state == "40002" ) { throw transaction_integrity_constraint_violation( error_message, sql_state ); } if( sql_state == "40003" ) { throw statement_completion_unknown( error_message, sql_state ); } if( sql_state == "40P01" ) { throw deadlock_detected( error_message, sql_state ); } throw transaction_rollback( error_message, sql_state ); case '2': if( sql_state == "42501" ) { throw insufficient_privilege( error_message, sql_state ); } if( sql_state == "42601" ) { throw syntax_error( error_message, sql_state ); } if( sql_state == "42602" ) { throw invalid_name( error_message, sql_state ); } if( sql_state == "42611" ) { throw invalid_column_definition( error_message, sql_state ); } if( sql_state == "42622" ) { throw name_too_long( error_message, sql_state ); } if( sql_state == "42701" ) { throw duplicate_column( error_message, sql_state ); } if( sql_state == "42702" ) { throw ambiguous_column( error_message, sql_state ); } if( sql_state == "42703" ) { throw undefined_column( error_message, sql_state ); } if( sql_state == "42704" ) { throw undefined_object( error_message, sql_state ); } if( sql_state == "42710" ) { throw duplicate_object( error_message, sql_state ); } if( sql_state == "42712" ) { throw duplicate_alias( error_message, sql_state ); } if( sql_state == "42723" ) { throw duplicate_function( error_message, sql_state ); } if( sql_state == "42725" ) { throw ambiguous_function( error_message, sql_state ); } if( sql_state == "42803" ) { throw grouping_error( error_message, sql_state ); } if( sql_state == "42804" ) { throw datatype_mismatch( error_message, sql_state ); } if( sql_state == "42809" ) { throw wrong_object_type( error_message, sql_state ); } if( sql_state == "42830" ) { throw invalid_foreign_key( error_message, sql_state ); } if( sql_state == "42846" ) { throw cannot_coerce( error_message, sql_state ); } if( sql_state == "42883" ) { throw undefined_function( error_message, sql_state ); } if( sql_state == "428C9" ) { throw generated_always( error_message, sql_state ); } if( sql_state == "42939" ) { throw reserved_name( error_message, sql_state ); } if( sql_state == "42P01" ) { throw undefined_table( error_message, sql_state ); } if( sql_state == "42P02" ) { throw undefined_parameter( error_message, sql_state ); } if( sql_state == "42P03" ) { throw duplicate_cursor( error_message, sql_state ); } if( sql_state == "42P04" ) { throw duplicate_database( error_message, sql_state ); } if( sql_state == "42P05" ) { throw duplicate_prepared_statement( error_message, sql_state ); } if( sql_state == "42P06" ) { throw duplicate_schema( error_message, sql_state ); } if( sql_state == "42P07" ) { throw duplicate_table( error_message, sql_state ); } if( sql_state == "42P08" ) { throw ambiguous_parameter( error_message, sql_state ); } if( sql_state == "42P09" ) { throw ambiguous_alias( error_message, sql_state ); } if( sql_state == "42P10" ) { throw invalid_column_reference( error_message, sql_state ); } if( sql_state == "42P11" ) { throw invalid_cursor_definition( error_message, sql_state ); } if( sql_state == "42P12" ) { throw invalid_database_definition( error_message, sql_state ); } if( sql_state == "42P13" ) { throw invalid_function_definition( error_message, sql_state ); } if( sql_state == "42P14" ) { throw invalid_prepared_statement_definition( error_message, sql_state ); } if( sql_state == "42P15" ) { throw invalid_schema_definition( error_message, sql_state ); } if( sql_state == "42P16" ) { throw invalid_table_definition( error_message, sql_state ); } if( sql_state == "42P17" ) { throw invalid_object_definition( error_message, sql_state ); } if( sql_state == "42P18" ) { throw indeterminate_datatype( error_message, sql_state ); } if( sql_state == "42P19" ) { throw invalid_recursion( error_message, sql_state ); } if( sql_state == "42P20" ) { throw windowing_error( error_message, sql_state ); } if( sql_state == "42P21" ) { throw collation_mismatch( error_message, sql_state ); } if( sql_state == "42P22" ) { throw indeterminate_collation( error_message, sql_state ); } throw syntax_error_or_access_rule_violation( error_message, sql_state ); case '4': throw with_check_option_violation( error_message, sql_state ); default: throw sql_error( error_message, sql_state ); } case '5': switch( sql_state[ 1 ] ) { case '3': if( sql_state == "53100" ) { throw disk_full( error_message, sql_state ); } if( sql_state == "53200" ) { throw out_of_memory( error_message, sql_state ); } if( sql_state == "53300" ) { throw too_many_connections( error_message, sql_state ); } if( sql_state == "53400" ) { throw configuration_limit_exceeded( error_message, sql_state ); } throw insufficient_resources( error_message, sql_state ); case '4': if( sql_state == "54001" ) { throw statement_too_complex( error_message, sql_state ); } if( sql_state == "54011" ) { throw too_many_columns( error_message, sql_state ); } if( sql_state == "54023" ) { throw too_many_arguments( error_message, sql_state ); } throw program_limit_exceeded( error_message, sql_state ); case '5': if( sql_state == "55006" ) { throw object_in_use( error_message, sql_state ); } if( sql_state == "55P02" ) { throw cant_change_runtime_param( error_message, sql_state ); } if( sql_state == "55P03" ) { throw lock_not_available( error_message, sql_state ); } if( sql_state == "55P04" ) { throw unsafe_new_enum_value_usage( error_message, sql_state ); } throw object_not_in_prerequisite_state( error_message, sql_state ); case '7': if( sql_state == "57014" ) { throw query_canceled( error_message, sql_state ); } if( sql_state == "57P01" ) { throw admin_shutdown( error_message, sql_state ); } if( sql_state == "57P02" ) { throw crash_shutdown( error_message, sql_state ); } if( sql_state == "57P03" ) { throw cannot_connect_now( error_message, sql_state ); } if( sql_state == "57P04" ) { throw database_dropped( error_message, sql_state ); } throw operator_intervention( error_message, sql_state ); case '8': if( sql_state == "58030" ) { throw io_error( error_message, sql_state ); } if( sql_state == "58P01" ) { throw undefined_file( error_message, sql_state ); } if( sql_state == "58P02" ) { throw duplicate_file( error_message, sql_state ); } throw system_error( error_message, sql_state ); default: throw sql_error( error_message, sql_state ); } case '7': switch( sql_state[ 1 ] ) { case '2': throw snapshot_too_old( error_message, sql_state ); default: throw sql_error( error_message, sql_state ); } case 'F': switch( sql_state[ 1 ] ) { case '0': if( sql_state == "F0001" ) { throw lock_file_exists( error_message, sql_state ); } throw config_file_error( error_message, sql_state ); default: throw sql_error( error_message, sql_state ); } case 'H': switch( sql_state[ 1 ] ) { case 'V': if( sql_state == "HV001" ) { throw fdw_out_of_memory( error_message, sql_state ); } if( sql_state == "HV002" ) { throw fdw_dynamic_parameter_value_needed( error_message, sql_state ); } if( sql_state == "HV004" ) { throw fdw_invalid_data_type( error_message, sql_state ); } if( sql_state == "HV005" ) { throw fdw_column_name_not_found( error_message, sql_state ); } if( sql_state == "HV006" ) { throw fdw_invalid_data_type_descriptors( error_message, sql_state ); } if( sql_state == "HV007" ) { throw fdw_invalid_column_name( error_message, sql_state ); } if( sql_state == "HV008" ) { throw fdw_invalid_column_number( error_message, sql_state ); } if( sql_state == "HV009" ) { throw fdw_invalid_use_of_null_pointer( error_message, sql_state ); } if( sql_state == "HV00A" ) { throw fdw_invalid_string_format( error_message, sql_state ); } if( sql_state == "HV00B" ) { throw fdw_invalid_handle( error_message, sql_state ); } if( sql_state == "HV00C" ) { throw fdw_invalid_option_index( error_message, sql_state ); } if( sql_state == "HV00D" ) { throw fdw_invalid_option_name( error_message, sql_state ); } if( sql_state == "HV00J" ) { throw fdw_option_name_not_found( error_message, sql_state ); } if( sql_state == "HV00K" ) { throw fdw_reply_handle( error_message, sql_state ); } if( sql_state == "HV00L" ) { throw fdw_unable_to_create_execution( error_message, sql_state ); } if( sql_state == "HV00M" ) { throw fdw_unable_to_create_reply( error_message, sql_state ); } if( sql_state == "HV00N" ) { throw fdw_unable_to_establish_connection( error_message, sql_state ); } if( sql_state == "HV00P" ) { throw fdw_no_schemas( error_message, sql_state ); } if( sql_state == "HV00Q" ) { throw fdw_schema_not_found( error_message, sql_state ); } if( sql_state == "HV00R" ) { throw fdw_table_not_found( error_message, sql_state ); } if( sql_state == "HV010" ) { throw fdw_function_sequence_error( error_message, sql_state ); } if( sql_state == "HV014" ) { throw fdw_too_many_handles( error_message, sql_state ); } if( sql_state == "HV021" ) { throw fdw_inconsistent_descriptor_information( error_message, sql_state ); } if( sql_state == "HV024" ) { throw fdw_invalid_attribute_value( error_message, sql_state ); } if( sql_state == "HV090" ) { throw fdw_invalid_string_length_or_buffer_length( error_message, sql_state ); } if( sql_state == "HV091" ) { throw fdw_invalid_descriptor_field_identifier( error_message, sql_state ); } throw fdw_error( error_message, sql_state ); default: throw sql_error( error_message, sql_state ); } case 'P': switch( sql_state[ 1 ] ) { case '0': if( sql_state == "P0001" ) { throw raise_exception( error_message, sql_state ); } if( sql_state == "P0002" ) { throw no_data_found( error_message, sql_state ); } if( sql_state == "P0003" ) { throw too_many_rows( error_message, sql_state ); } if( sql_state == "P0004" ) { throw assert_failure( error_message, sql_state ); } throw plpgsql_error( error_message, sql_state ); default: throw sql_error( error_message, sql_state ); } case 'X': switch( sql_state[ 1 ] ) { case 'X': if( sql_state == "XX001" ) { throw data_corrupted( error_message, sql_state ); } if( sql_state == "XX002" ) { throw index_corrupted( error_message, sql_state ); } throw internal_error( error_message, sql_state ); default: throw sql_error( error_message, sql_state ); } default: throw sql_error( error_message, sql_state ); } // LCOV_EXCL_STOP } } // namespace internal } // namespace tao::pq ================================================ FILE: src/lib/pq/field.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include namespace tao::pq { auto field::name() const -> std::string { return m_row->name( m_column ); } auto field::index() const noexcept -> std::size_t { return m_column - m_row->m_offset; } auto field::is_null() const -> bool { return m_row->is_null( m_column ); } auto field::get() const -> const char* { return m_row->get( m_column ); } } // namespace tao::pq ================================================ FILE: src/lib/pq/internal/demangle.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #if !defined( _WIN32 ) #include #include #include #endif namespace tao::pq::internal { auto demangle( const char* const symbol ) -> std::string { #if defined( _WIN32 ) return symbol; #else const std::unique_ptr< char, decltype( &std::free ) > demangled( abi::__cxa_demangle( symbol, nullptr, nullptr, nullptr ), &std::free ); return demangled ? demangled.get() : symbol; #endif } } // namespace tao::pq::internal ================================================ FILE: src/lib/pq/internal/poll.cpp ================================================ // Copyright (c) 2023-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #if defined( _WIN32 ) #include #else #include #endif #include #include #include namespace tao::pq::internal { namespace { // LCOV_EXCL_START [[nodiscard, maybe_unused]] auto errno_result_to_string( const int e, char* buffer, int result ) -> std::string { if( result == 0 ) { return buffer; } return std::format( "unknown error code {}", e ); } [[nodiscard, maybe_unused]] auto errno_result_to_string( const int /*unused*/, char* /*unused*/, char* result ) -> std::string { return result; } [[nodiscard]] auto errno_to_string( const int e ) -> std::string { char buffer[ 256 ]; #if defined( _WIN32 ) #ifdef _MSC_VER return errno_result_to_string( e, buffer, strerror_s( buffer, e ) ); #else return errno_result_to_string( e, buffer, strerror_s( buffer, sizeof( buffer ), e ) ); #endif #else return errno_result_to_string( e, buffer, strerror_r( e, buffer, sizeof( buffer ) ) ); #endif } // LCOV_EXCL_STOP } // namespace auto poll( const int socket, const bool wait_for_write, const int timeout_ms ) -> pq::poll::status { #if defined( _WIN32 ) const short events = POLLIN | ( wait_for_write ? POLLOUT : 0 ); WSAPOLLFD pfd = { static_cast< SOCKET >( socket ), events, 0 }; const auto result = WSAPoll( &pfd, 1, timeout_ms ); switch( result ) { case 0: return pq::poll::status::timeout; case 1: if( ( pfd.revents & events ) == 0 ) { throw network_error( std::format( "WSAPoll() failed, events {}, revents {}", events, pfd.revents ) ); } return ( ( pfd.revents & POLLIN ) != 0 ) ? pq::poll::status::readable : pq::poll::status::writable; case SOCKET_ERROR: { const int e = WSAGetLastError(); throw network_error( std::format( "WSAPoll() failed: {}", errno_to_string( e ) ) ); } default: TAO_PQ_INTERNAL_UNREACHABLE; } #else const short events = POLLIN | ( wait_for_write ? POLLOUT : 0 ); pollfd pfd = { .fd = socket, .events = events, .revents = 0 }; errno = 0; const auto result = ::poll( &pfd, 1, timeout_ms ); switch( result ) { case 0: return pq::poll::status::timeout; case 1: if( ( pfd.revents & events ) == 0 ) { throw network_error( std::format( "poll() failed, events {}, revents {}", events, pfd.revents ) ); // LCOV_EXCL_LINE } return ( ( pfd.revents & POLLIN ) != 0 ) ? pq::poll::status::readable : pq::poll::status::writable; // LCOV_EXCL_START case -1: { const int e = errno; if( ( e != EINTR ) && ( e != EAGAIN ) ) { throw network_error( std::format( "poll() failed: ", errno_to_string( e ) ) ); } return pq::poll::status::again; } default: TAO_PQ_INTERNAL_UNREACHABLE; } // LCOV_EXCL_STOP #endif } } // namespace tao::pq::internal ================================================ FILE: src/lib/pq/internal/strtox.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include namespace tao::pq::internal { namespace { template< typename > inline constexpr const char* message = nullptr; // clang-format off template<> inline constexpr auto message< float > = "tao::pq::internal::strtof() failed for input: "; template<> inline constexpr auto message< double > = "tao::pq::internal::strtod() failed for input: "; template<> inline constexpr auto message< long double > = "tao::pq::internal::strtold() failed for input: "; // clang-format on template< typename T > [[nodiscard]] auto failure_message( const char* input ) -> std::string { return std::string( message< T > ) + input; } template< typename T > [[nodiscard]] auto call_floating_point( const char* nptr, char** endptr ) -> T; template<> [[nodiscard]] auto call_floating_point< float >( const char* nptr, char** endptr ) -> float { return std::strtof( nptr, endptr ); } template<> [[nodiscard]] auto call_floating_point< double >( const char* nptr, char** endptr ) -> double { return std::strtod( nptr, endptr ); } template<> [[nodiscard]] auto call_floating_point< long double >( const char* nptr, char** endptr ) -> long double { return std::strtold( nptr, endptr ); } template< typename T > [[nodiscard]] auto str_to_floating_point( const char* input ) -> T { assert( input ); if( *input == '\0' || std::isspace( *input ) ) { throw std::runtime_error( failure_message< T >( input ) ); } char* end; errno = 0; const T result = call_floating_point< T >( input, &end ); switch( errno ) { case 0: if( *end == '\0' ) { return result; } throw std::runtime_error( failure_message< T >( input ) ); case ERANGE: if( result == 0 ) { throw std::underflow_error( failure_message< T >( input ) ); } else { throw std::overflow_error( failure_message< T >( input ) ); } default: throw std::runtime_error( std::format( "code should be unreachable, errno: {}, input: \"{}\"", errno, input ) ); // LCOV_EXCL_LINE } } } // namespace [[nodiscard]] auto strtof( const char* input ) -> float { return str_to_floating_point< float >( input ); } [[nodiscard]] auto strtod( const char* input ) -> double { return str_to_floating_point< double >( input ); } [[nodiscard]] auto strtold( const char* input ) -> long double { return str_to_floating_point< long double >( input ); } } // namespace tao::pq::internal ================================================ FILE: src/lib/pq/large_object.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { namespace { [[nodiscard]] constexpr auto to_mode( const std::ios_base::openmode m ) noexcept -> int { return ( ( ( m & std::ios_base::in ) != 0 ) ? INV_READ : 0 ) | ( ( ( m & std::ios_base::out ) != 0 ) ? INV_WRITE : 0 ); } } // namespace auto large_object::create( const std::shared_ptr< transaction >& transaction, const oid desired_id ) -> oid { const oid id = static_cast< oid >( lo_create( transaction->connection()->underlying_raw_ptr(), static_cast< Oid >( desired_id ) ) ); if( id == oid::invalid ) { throw std::runtime_error( std::format( "tao::pq::large_object::create() failed: {}", transaction->connection()->error_message() ) ); } return id; } void large_object::remove( const std::shared_ptr< transaction >& transaction, const oid id ) { if( lo_unlink( transaction->connection()->underlying_raw_ptr(), static_cast< Oid >( id ) ) == -1 ) { throw std::runtime_error( std::format( "tao::pq::large_object::remove() failed: {}", transaction->connection()->error_message() ) ); } } auto large_object::import_file( const std::shared_ptr< transaction >& transaction, const char* filename, const oid desired_id ) -> oid { const oid id = static_cast< oid >( lo_import_with_oid( transaction->connection()->underlying_raw_ptr(), filename, static_cast< Oid >( desired_id ) ) ); if( id == oid::invalid ) { throw std::runtime_error( std::format( "tao::pq::large_object::import_file() failed: {}", transaction->connection()->error_message() ) ); } return id; } void large_object::export_file( const std::shared_ptr< transaction >& transaction, const oid id, const char* filename ) { if( lo_export( transaction->connection()->underlying_raw_ptr(), static_cast< Oid >( id ), filename ) == -1 ) { throw std::runtime_error( std::format( "tao::pq::large_object::export_file() failed: {}", transaction->connection()->error_message() ) ); } } large_object::large_object( const std::shared_ptr< transaction >& transaction, const oid id, const std::ios_base::openmode m ) : m_transaction( transaction ), m_fd( lo_open( transaction->connection()->underlying_raw_ptr(), static_cast< Oid >( id ), to_mode( m ) ) ) { if( m_fd == -1 ) { throw std::runtime_error( std::format( "tao::pq::large_object::open() failed: {}", transaction->connection()->error_message() ) ); } } large_object::large_object( large_object&& other ) noexcept : m_transaction( std::move( other.m_transaction ) ), m_fd( other.m_fd ) {} large_object::~large_object() { try { close(); } // LCOV_EXCL_START catch( ... ) { // NOLINT(bugprone-empty-catch) // TODO: How to handle this case properly? } // LCOV_EXCL_STOP } auto large_object::operator=( large_object&& rhs ) -> large_object& // NOLINT { close(); m_transaction = std::move( rhs.m_transaction ); m_fd = rhs.m_fd; return *this; } void large_object::close() { if( m_transaction ) { if( lo_close( m_transaction->connection()->underlying_raw_ptr(), m_fd ) == -1 ) { throw std::runtime_error( std::format( "tao::pq::large_object::close() failed: {}", m_transaction->connection()->error_message() ) ); } m_transaction.reset(); } } auto large_object::read( char* data, const std::size_t size ) -> std::size_t { assert( m_transaction ); const auto result = lo_read( m_transaction->connection()->underlying_raw_ptr(), m_fd, data, size ); if( result == -1 ) { throw std::runtime_error( std::format( "tao::pq::large_object::read() failed: {}", m_transaction->connection()->error_message() ) ); } return result; } void large_object::write( const char* data, const std::size_t size ) { assert( m_transaction ); if( lo_write( m_transaction->connection()->underlying_raw_ptr(), m_fd, data, size ) == -1 ) { throw std::runtime_error( std::format( "tao::pq::large_object::write() failed: {}", m_transaction->connection()->error_message() ) ); } } void large_object::resize( const std::int64_t size ) { assert( m_transaction ); if( lo_truncate64( m_transaction->connection()->underlying_raw_ptr(), m_fd, size ) == -1 ) { throw std::runtime_error( std::format( "tao::pq::large_object::resize() failed: {}", m_transaction->connection()->error_message() ) ); } } auto large_object::seek( const std::int64_t offset, const std::ios_base::seekdir whence ) -> std::int64_t { static_assert( std::ios_base::beg == SEEK_SET ); static_assert( std::ios_base::cur == SEEK_CUR ); static_assert( std::ios_base::end == SEEK_END ); assert( m_transaction ); const auto result = lo_lseek64( m_transaction->connection()->underlying_raw_ptr(), m_fd, offset, whence ); if( result == -1 ) { throw std::runtime_error( std::format( "tao::pq::large_object::seek() failed: {}", m_transaction->connection()->error_message() ) ); } return result; } auto large_object::tell() const -> std::int64_t { assert( m_transaction ); const auto pos = lo_tell64( m_transaction->connection()->underlying_raw_ptr(), m_fd ); if( pos == -1 ) { throw std::runtime_error( std::format( "tao::pq::large_object::tell() failed: {}", m_transaction->connection()->error_message() ) ); } return pos; } template<> auto large_object::read< std::string >( const std::size_t size ) -> std::string { std::string nrv; internal::resize_uninitialized( nrv, size ); nrv.resize( read( nrv.data(), size ) ); return nrv; } template<> auto large_object::read< binary >( const std::size_t size ) -> binary { binary nrv; internal::resize_uninitialized( nrv, size ); nrv.resize( read( nrv.data(), size ) ); return nrv; } } // namespace tao::pq ================================================ FILE: src/lib/pq/parameter_traits.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include namespace tao::pq::internal { void array_append( std::string& buffer, std::string_view data ) { if( data.empty() ) { buffer += "\"\""; } else if( data == "NULL" ) { buffer += "\"NULL\""; } else if( data.find_first_of( "\\\"{},; \t" ) != std::string_view::npos ) { buffer += '"'; while( true ) { const auto n = data.find_first_of( "\\\"" ); if( n == std::string_view::npos ) { buffer += data; break; } buffer.append( data.data(), n ); // NOLINT(bugprone-suspicious-stringview-data-usage) buffer += '\\'; buffer += data[ n ]; data.remove_prefix( n + 1 ); } buffer += '"'; } else { buffer += data; } } void table_writer_append( std::string& buffer, std::string_view data ) { while( true ) { const auto n = data.find_first_of( "\b\f\n\r\t\v\\" ); if( n == std::string_view::npos ) { buffer += data; return; } buffer.append( data.data(), n ); // NOLINT(bugprone-suspicious-stringview-data-usage) buffer += '\\'; buffer += data[ n ]; data.remove_prefix( n + 1 ); } } } // namespace tao::pq::internal ================================================ FILE: src/lib/pq/pipeline.cpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include namespace tao::pq { pipeline::pipeline( const pipeline::private_key /*unused*/, const std::shared_ptr< pq::connection >& connection ) : transaction_base( connection ), m_previous( current_transaction()->shared_from_this() ) { connection->enter_pipeline_mode(); current_transaction() = this; } void pipeline::sync() { connection()->pipeline_sync(); } void pipeline::consume_sync( const std::chrono::steady_clock::time_point start ) { current_transaction()->consume_pipeline_sync( start ); } void pipeline::finish() { if( m_previous ) { current_transaction() = m_previous.get(); const auto extend_lifetime = std::move( m_previous ); connection()->exit_pipeline_mode(); } } } // namespace tao::pq ================================================ FILE: src/lib/pq/result.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { void result::check_row( const std::size_t row ) const { assert( m_columns != 0 ); if( !( row < m_rows ) ) { if( m_rows == 0 ) { throw std::out_of_range( std::format( "row {} out of range, result is empty", row ) ); } throw std::out_of_range( std::format( "row {} out of range (0-{})", row, m_rows - 1 ) ); } } result::result( PGresult* pgresult ) : m_pgresult( pgresult, &PQclear ), m_columns( PQnfields( pgresult ) ), m_rows( PQntuples( pgresult ) ) { switch( PQresultStatus( pgresult ) ) { case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: case PGRES_SINGLE_TUPLE: #if defined( LIBPQ_HAS_CHUNK_MODE ) case PGRES_TUPLES_CHUNK: #endif case PGRES_PIPELINE_SYNC: case PGRES_PIPELINE_ABORTED: return; case PGRES_EMPTY_QUERY: throw std::runtime_error( "unexpected empty query" ); case PGRES_COPY_IN: case PGRES_COPY_OUT: TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE default: internal::throw_sqlstate( pgresult ); } } auto result::status() const noexcept -> result_status { return static_cast< pq::result_status >( PQresultStatus( m_pgresult.get() ) ); } auto result::has_rows_affected() const noexcept -> bool { const char* str = PQcmdTuples( m_pgresult.get() ); return str[ 0 ] != '\0'; } auto result::rows_affected() const -> std::size_t { const char* str = PQcmdTuples( m_pgresult.get() ); if( str[ 0 ] == '\0' ) { throw std::logic_error( "statement does not return affected rows" ); } return internal::from_chars< std::size_t >( str ); } auto result::name( const std::size_t column ) const -> std::string { if( column >= m_columns ) { throw std::out_of_range( std::format( "column {} out of range (0-{})", column, m_columns - 1 ) ); } return PQfname( m_pgresult.get(), static_cast< int >( column ) ); } auto result::index( const internal::zsv in_name ) const -> std::size_t { assert( m_columns != 0 ); const int column = PQfnumber( m_pgresult.get(), in_name ); if( column < 0 ) { assert( column == -1 ); throw std::out_of_range( std::format( "column '{}' not found", in_name.value ) ); } return column; } auto result::begin() const noexcept -> result::const_iterator { assert( m_columns != 0 ); return const_iterator( row( *this, 0, 0, m_columns ) ); } auto result::end() const noexcept -> result::const_iterator { return const_iterator( row( *this, size(), 0, m_columns ) ); } auto result::is_null( const std::size_t row, const std::size_t column ) const -> bool { check_row( row ); if( column >= m_columns ) { throw std::out_of_range( std::format( "column {} out of range (0-{})", column, m_columns - 1 ) ); } return PQgetisnull( m_pgresult.get(), static_cast< int >( row ), static_cast< int >( column ) ) != 0; } auto result::get( const std::size_t row, const std::size_t column ) const -> const char* { if( is_null( row, column ) ) { throw std::runtime_error( std::format( "unexpected NULL value in row {} column {}/'{}'", row, column, name( column ) ) ); } return PQgetvalue( m_pgresult.get(), static_cast< int >( row ), static_cast< int >( column ) ); } auto result::at( const std::size_t row ) const -> pq::row { check_row( row ); return ( *this )[ row ]; } } // namespace tao::pq ================================================ FILE: src/lib/pq/result_traits.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include namespace tao::pq { namespace { [[nodiscard]] auto unhex( const char c ) -> int { if( ( c >= '0' ) && ( c <= '9' ) ) { return c - '0'; } if( ( c >= 'a' ) && ( c <= 'f' ) ) { return c - 'a' + 10; } throw std::invalid_argument( "unhex failed" ); } [[nodiscard]] auto unescape_bytea( const char* value ) -> binary { if( ( value[ 0 ] != '\\' ) || ( value[ 1 ] != 'x' ) ) { throw std::invalid_argument( std::format( "unescape BYTEA failed: {}", value ) ); } const auto input = std::strlen( value ); if( input % 2 == 1 ) { throw std::invalid_argument( std::format( "unescape BYTEA failed: {}", value ) ); } const auto size = ( input / 2 ) - 1; binary nrv; internal::resize_uninitialized( nrv, size ); for( std::size_t pos = 0; pos < size; ++pos ) { const auto high = unhex( value[ 2 + ( 2 * pos ) ] ); const auto low = unhex( value[ 2 + ( 2 * pos ) + 1 ] ); nrv[ pos ] = static_cast< std::byte >( ( high << 4 ) | low ); } return nrv; } } // namespace auto result_traits< bool >::from( const char* value ) -> bool { if( ( value[ 0 ] != '\0' ) && ( value[ 1 ] == '\0' ) ) { if( value[ 0 ] == 't' ) { return true; } if( value[ 0 ] == 'f' ) { return false; } } throw std::runtime_error( std::format( "invalid value in tao::pq::result_traits for input: {}", value ) ); } auto result_traits< char >::from( const char* value ) -> char { if( ( value[ 0 ] == '\0' ) || ( value[ 1 ] != '\0' ) ) { throw std::runtime_error( std::format( "invalid value in tao::pq::result_traits for input: {}", value ) ); } return value[ 0 ]; } auto result_traits< signed char >::from( const char* value ) -> signed char { return internal::from_chars< signed char >( value ); } auto result_traits< unsigned char >::from( const char* value ) -> unsigned char { return internal::from_chars< unsigned char >( value ); } auto result_traits< short >::from( const char* value ) -> short { return internal::from_chars< short >( value ); } auto result_traits< unsigned short >::from( const char* value ) -> unsigned short { return internal::from_chars< unsigned short >( value ); } auto result_traits< int >::from( const char* value ) -> int { return internal::from_chars< int >( value ); } auto result_traits< unsigned >::from( const char* value ) -> unsigned { return internal::from_chars< unsigned >( value ); } auto result_traits< long >::from( const char* value ) -> long { return internal::from_chars< long >( value ); } auto result_traits< unsigned long >::from( const char* value ) -> unsigned long { return internal::from_chars< unsigned long >( value ); } auto result_traits< long long >::from( const char* value ) -> long long { return internal::from_chars< long long >( value ); } auto result_traits< unsigned long long >::from( const char* value ) -> unsigned long long { return internal::from_chars< unsigned long long >( value ); } auto result_traits< float >::from( const char* value ) -> float { return internal::strtof( value ); } auto result_traits< double >::from( const char* value ) -> double { return internal::strtod( value ); } auto result_traits< long double >::from( const char* value ) -> long double { return internal::strtold( value ); } auto result_traits< binary >::from( const char* value ) -> binary { return unescape_bytea( value ); } } // namespace tao::pq ================================================ FILE: src/lib/pq/result_traits_array.cpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include namespace tao::pq::internal { auto parse_quoted( const char*& value ) -> std::string { std::string result; while( const auto* pos = std::strpbrk( value, "\\\"" ) ) { switch( *pos ) { case '\\': result.append( value, pos++ ); result += *pos++; value = pos; break; case '"': result.append( value, pos++ ); value = pos; return result; default: TAO_PQ_INTERNAL_UNREACHABLE; } } throw std::invalid_argument( "unterminated quoted string" ); } auto parse_unquoted( const char*& value ) -> std::string { if( const auto* end = std::strpbrk( value, ",;}" ) ) { const std::string result( value, end ); value = end; return result; } throw std::invalid_argument( "unterminated unquoted string" ); } } // namespace tao::pq::internal ================================================ FILE: src/lib/pq/row.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include namespace tao::pq { void row::ensure_column( const std::size_t column ) const { if( column >= m_columns ) { throw std::out_of_range( std::format( "column {} out of range (0-{})", column, m_columns - 1 ) ); } } auto row::slice( const std::size_t offset, const std::size_t in_columns ) const -> row { assert( m_result ); if( in_columns == 0 ) { throw std::invalid_argument( "slice requires at least one column" ); } if( offset + in_columns > m_columns ) { throw std::out_of_range( std::format( "slice ({}-{}) out of range (0-{})", offset, offset + in_columns - 1, m_columns - 1 ) ); } return { *m_result, m_row, m_offset + offset, in_columns }; } auto row::name( const std::size_t column ) const -> std::string { assert( m_result ); return m_result->name( m_offset + column ); } auto row::index( const internal::zsv in_name ) const -> std::size_t { assert( m_result ); const std::size_t n = m_result->index( in_name ); if( n >= m_offset ) { if( n - m_offset < m_columns ) { return n - m_offset; } } else { const std::string adapted_name = m_result->name( n ); for( std::size_t pos = 0; pos < m_columns; ++pos ) { if( name( pos ) == adapted_name ) { return pos; } } } throw std::out_of_range( std::format( "column not found: {}", static_cast< const char* >( in_name ) ) ); } auto row::begin() const noexcept -> row::const_iterator { return const_iterator( field( *this, m_offset ) ); } auto row::end() const noexcept -> row::const_iterator { return const_iterator( field( *this, m_offset + m_columns ) ); } auto row::is_null( const std::size_t column ) const -> bool { ensure_column( column ); assert( m_result ); return m_result->is_null( m_row, m_offset + column ); } auto row::get( const std::size_t column ) const -> const char* { ensure_column( column ); assert( m_result ); return m_result->get( m_row, m_offset + column ); } auto row::at( const std::size_t column ) const -> field { ensure_column( column ); return { *this, m_offset + column }; } } // namespace tao::pq ================================================ FILE: src/lib/pq/table_field.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include namespace tao::pq { auto table_field::index() const noexcept -> std::size_t { return m_column - m_row->m_offset; } auto table_field::is_null() const -> bool { return m_row->is_null( m_column ); } auto table_field::get() const -> const char* { return m_row->get( m_column ); } } // namespace tao::pq ================================================ FILE: src/lib/pq/table_reader.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { void table_reader::check_result() { const auto start = std::chrono::steady_clock::now(); const auto end = m_transaction->connection()->timeout_end( start ); auto result = m_transaction->connection()->get_result( end ); switch( PQresultStatus( result.get() ) ) { case PGRES_COPY_OUT: m_columns = PQnfields( result.get() ); break; case PGRES_COPY_IN: std::ignore = m_transaction->get_result( start ); TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: m_transaction->connection()->consume_empty_result( end ); throw std::runtime_error( "expected COPY TO statement" ); case PGRES_EMPTY_QUERY: m_transaction->connection()->consume_empty_result( end ); throw std::runtime_error( "unexpected empty query" ); default: m_transaction->connection()->consume_empty_result( end ); internal::throw_sqlstate( result.get() ); } } auto table_reader::get_raw_data() -> std::string_view { char* buffer = nullptr; const auto size = m_transaction->connection()->get_copy_data( buffer ); m_buffer.reset( buffer ); if( size > 0 ) { return { static_cast< const char* >( buffer ), size }; } const auto end = m_transaction->connection()->timeout_end(); std::ignore = pq::result( m_transaction->connection()->get_result( end ).release() ); m_transaction.reset(); m_previous.reset(); return {}; } auto table_reader::parse_data() noexcept -> bool { m_data.clear(); char* read = m_buffer.get(); if( read == nullptr ) { return false; } char* write = read; char* begin = write; while( auto* pos = std::strpbrk( read, "\t\\\n" ) ) { if( const auto prefix_size = pos - read ) { std::memmove( write, read, prefix_size ); write += prefix_size; } switch( *pos ) { case '\t': m_data.emplace_back( begin ); *write++ = '\0'; begin = write = read = ++pos; break; case '\\': read = pos + 1; switch( *read++ ) { case 'N': assert( write == begin ); m_data.emplace_back( nullptr ); switch( *read ) { case '\t': begin = write = ++read; break; case '\n': return true; default: // LCOV_EXCL_LINE TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE } break; case 'b': *write++ = '\b'; break; case 'f': *write++ = '\f'; break; case 'n': *write++ = '\n'; break; case 'r': *write++ = '\r'; break; case 't': *write++ = '\t'; break; case 'v': *write++ = '\v'; break; case '\\': *write++ = '\\'; break; default: // LCOV_EXCL_LINE TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE } break; case '\n': m_data.emplace_back( begin ); *write++ = '\0'; assert( m_data.size() == columns() ); return true; default: // LCOV_EXCL_LINE TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE } } TAO_PQ_INTERNAL_UNREACHABLE; // LCOV_EXCL_LINE } auto table_reader::begin() -> table_reader::const_iterator { std::ignore = get_row(); return table_row( *this, 0, columns() ); } auto table_reader::end() noexcept -> table_reader::const_iterator { return table_row( *this, 0, 0 ); } } // namespace tao::pq ================================================ FILE: src/lib/pq/table_row.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include namespace tao::pq { void table_row::ensure_column( const std::size_t column ) const { if( column >= m_columns ) { throw std::out_of_range( std::format( "column {} out of range (0-{})", column, m_columns - 1 ) ); } } auto table_row::slice( const std::size_t offset, const std::size_t in_columns ) const -> table_row { if( in_columns == 0 ) { throw std::invalid_argument( "slice requires at least one column" ); } if( offset + in_columns > m_columns ) { throw std::out_of_range( std::format( "slice ({}-{}) out of range (0-{})", offset, offset + in_columns - 1, m_columns - 1 ) ); } return { *m_reader, m_offset + offset, in_columns }; } auto table_row::begin() const noexcept -> table_row::const_iterator { return const_iterator( table_field( *this, m_offset ) ); } auto table_row::end() const noexcept -> table_row::const_iterator { return const_iterator( table_field( *this, m_offset + m_columns ) ); } auto table_row::is_null( const std::size_t column ) const -> bool { return get( column ) == nullptr; } auto table_row::get( const std::size_t column ) const -> const char* { ensure_column( column ); return m_reader->raw_data()[ m_offset + column ]; } auto table_row::at( const std::size_t column ) const -> table_field { ensure_column( column ); return { *this, m_offset + column }; } } // namespace tao::pq ================================================ FILE: src/lib/pq/table_writer.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include #include namespace tao::pq { table_writer::~table_writer() { if( m_transaction ) { try { std::ignore = m_transaction->get_result(); } catch( ... ) { // NOLINT(bugprone-empty-catch) } } } void table_writer::check_result() { const auto end = m_transaction->connection()->timeout_end(); const auto result = m_transaction->connection()->get_result( end ); switch( PQresultStatus( result.get() ) ) { case PGRES_COPY_IN: break; case PGRES_COPY_OUT: m_transaction->connection()->cancel(); m_transaction->connection()->clear_copy_data( end ); std::ignore = m_transaction->connection()->get_fatal_error( end ); m_transaction->connection()->consume_empty_result( end ); throw std::runtime_error( "unexpected COPY TO statement" ); case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: m_transaction->connection()->consume_empty_result( end ); throw std::runtime_error( "expected COPY FROM statement" ); case PGRES_EMPTY_QUERY: m_transaction->connection()->consume_empty_result( end ); throw std::runtime_error( "unexpected empty query" ); default: m_transaction->connection()->consume_empty_result( end ); internal::throw_sqlstate( result.get() ); } } void table_writer::insert_raw( const std::string_view data ) { m_transaction->connection()->put_copy_data( data.data(), data.size() ); } auto table_writer::commit() -> std::size_t { m_transaction->connection()->put_copy_end(); const auto rows_affected = m_transaction->get_result().rows_affected(); m_transaction.reset(); m_previous.reset(); return rows_affected; } } // namespace tao::pq ================================================ FILE: src/lib/pq/transaction.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include namespace tao::pq { namespace internal { class top_level_subtransaction final : public subtransaction_base { public: explicit top_level_subtransaction( const std::shared_ptr< pq::connection >& connection ) : subtransaction_base( connection ) { execute( "START TRANSACTION" ); } ~top_level_subtransaction() override { if( m_connection && m_connection->attempt_rollback() ) { rollback_in_dtor(); } } top_level_subtransaction( const top_level_subtransaction& ) = delete; top_level_subtransaction( top_level_subtransaction&& ) = delete; void operator=( const top_level_subtransaction& ) = delete; void operator=( top_level_subtransaction&& ) = delete; private: void v_commit() override { execute( "COMMIT TRANSACTION" ); } void v_rollback() override { execute( "ROLLBACK TRANSACTION" ); } }; class nested_subtransaction final : public subtransaction_base { public: explicit nested_subtransaction( const std::shared_ptr< pq::connection >& connection ) : subtransaction_base( connection ) { char buffer[ 64 ]; std::snprintf( buffer, 64, "SAVEPOINT \"TAOPQ_%p\"", static_cast< void* >( this ) ); execute( buffer ); } ~nested_subtransaction() override { if( m_connection && m_connection->attempt_rollback() ) { rollback_in_dtor(); } } nested_subtransaction( const nested_subtransaction& ) = delete; nested_subtransaction( nested_subtransaction&& ) = delete; void operator=( const nested_subtransaction& ) = delete; void operator=( nested_subtransaction&& ) = delete; private: void v_commit() override { char buffer[ 64 ]; std::snprintf( buffer, 64, "RELEASE SAVEPOINT \"TAOPQ_%p\"", static_cast< void* >( this ) ); execute( buffer ); } void v_rollback() override { char buffer[ 64 ]; std::snprintf( buffer, 64, "ROLLBACK TO \"TAOPQ_%p\"", static_cast< void* >( this ) ); execute( buffer ); } }; } // namespace internal auto transaction::subtransaction() -> std::shared_ptr< transaction > { check_current_transaction(); if( v_is_direct() ) { return std::make_shared< internal::top_level_subtransaction >( m_connection ); } return std::make_shared< internal::nested_subtransaction >( m_connection ); } auto transaction::pipeline() -> std::shared_ptr< pq::pipeline > { check_current_transaction(); return std::make_shared< pq::pipeline >( pq::pipeline::private_key(), m_connection ); } void transaction::commit() { try { check_current_transaction(); v_commit(); } // LCOV_EXCL_START catch( ... ) { v_reset(); throw; } // LCOV_EXCL_STOP v_reset(); } void transaction::rollback() { try { check_current_transaction(); v_rollback(); } // LCOV_EXCL_START catch( ... ) { v_reset(); throw; } // LCOV_EXCL_STOP v_reset(); } void transaction::rollback_in_dtor() noexcept { try { check_current_transaction(); v_rollback(); } // LCOV_EXCL_START catch( ... ) { if( m_connection->m_log && m_connection->m_log->transaction.destructor_rollback_failed ) { m_connection->m_log->transaction.destructor_rollback_failed( *this ); } } // LCOV_EXCL_STOP v_reset(); } } // namespace tao::pq ================================================ FILE: src/lib/pq/transaction_base.cpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include namespace tao::pq { transaction_base::transaction_base( const std::shared_ptr< pq::connection >& connection ) noexcept // NOLINT(modernize-pass-by-value) : m_connection( connection ) {} auto transaction_base::current_transaction() const noexcept -> transaction_base*& { return m_connection->m_current_transaction; } void transaction_base::check_current_transaction() const { if( !m_connection || this != current_transaction() ) { throw std::logic_error( "invalid transaction order" ); } } void transaction_base::send_params( const char* statement, const int n_params, const Oid types[], const char* const values[], const int lengths[], const int formats[] ) { check_current_transaction(); m_connection->send_params( statement, n_params, types, values, lengths, formats ); } void transaction_base::set_single_row_mode() { check_current_transaction(); if( PQsetSingleRowMode( m_connection->underlying_raw_ptr() ) == 0 ) { throw std::runtime_error( "unable to switch to single row mode" ); } } #if defined( LIBPQ_HAS_CHUNK_MODE ) void transaction_base::set_chunk_mode( const int rows ) { check_current_transaction(); if( PQsetChunkedRowsMode( m_connection->underlying_raw_ptr(), rows ) == 0 ) { throw std::runtime_error( "unable to switch to chunk mode" ); } } #endif auto transaction_base::get_result( const std::chrono::steady_clock::time_point start ) -> result { check_current_transaction(); const auto end = m_connection->timeout_end( start ); auto result = m_connection->get_result( end ); if( !result ) { throw std::runtime_error( "unable to obtain result" ); } switch( PQresultStatus( result.get() ) ) { case PGRES_COPY_IN: m_connection->put_copy_end( "unexpected COPY FROM statement" ); result = m_connection->get_fatal_error( end ); break; case PGRES_COPY_OUT: m_connection->cancel(); m_connection->clear_copy_data( end ); std::ignore = m_connection->get_fatal_error( end ); m_connection->consume_empty_result( end ); throw std::runtime_error( "unexpected COPY TO statement" ); case PGRES_SINGLE_TUPLE: #if defined( LIBPQ_HAS_CHUNK_MODE ) case PGRES_TUPLES_CHUNK: #endif return pq::result( result.release() ); default:; } m_connection->consume_empty_result( end ); return pq::result( result.release() ); } void transaction_base::consume_pipeline_sync( const std::chrono::steady_clock::time_point start ) { check_current_transaction(); const auto end = m_connection->timeout_end( start ); const auto result = m_connection->get_result( end ); if( !result ) { throw std::runtime_error( "unable to obtain result" ); } const auto status = PQresultStatus( result.get() ); if( status != PGRES_PIPELINE_SYNC ) { throw std::runtime_error( std::format( "unexpected result status: {}", PQresStatus( status ) ) ); } } } // namespace tao::pq ================================================ FILE: test/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) option(BUILD_INTEGRATION_TESTS "Build integration tests that require a live PostgreSQL database" ON) option(BUILD_UNIT_TESTS "Build unit tests without needing a live database" ON) include(CTest) set(SOURCE_INTEGRATION_TESTS integration/aggregate.cpp integration/array.cpp integration/basic_datatypes.cpp integration/chunk_mode.cpp integration/connection.cpp integration/connection_pool.cpp integration/example.cpp integration/exception.cpp integration/large_object.cpp integration/log.cpp integration/notifications.cpp integration/parameter.cpp integration/password.cpp integration/pipeline_mode.cpp integration/result.cpp integration/row.cpp integration/single_row_mode.cpp integration/table_reader.cpp integration/table_writer.cpp integration/traits.cpp integration/transaction.cpp ) set(SOURCE_UNIT_TESTS unit/getenv.cpp unit/parameter_type.cpp unit/resize_uninitialized.cpp unit/result_type.cpp unit/strtox.cpp ) function(add_taopq_test source_file) get_filename_component(test_name "${source_file}" NAME_WE) add_executable("${test_name}" "${source_file}") target_link_libraries("${test_name}" PRIVATE taocpp::taopq) target_compile_features("${test_name}" PRIVATE cxx_std_20) target_include_directories("${test_name}" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") if(WIN32) target_link_libraries("${test_name}" PRIVATE wsock32 ws2_32) endif() add_test( NAME "${test_name}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../.." COMMAND "$" ) endfunction() foreach(cat IN ITEMS INTEGRATION UNIT) if(BUILD_${cat}_TESTS) foreach(src IN LISTS SOURCE_${cat}_TESTS) add_taopq_test("${CMAKE_CURRENT_SOURCE_DIR}/${src}") endforeach() endif() endforeach() ================================================ FILE: test/integration/aggregate.cpp ================================================ // Copyright (c) 2020-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include namespace example { struct user { std::string name; int age; std::string planet; }; } // namespace example template<> inline constexpr bool tao::pq::is_aggregate< example::user > = true; namespace { void run() { const auto connection = tao::pq::connection::create( tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ) ); connection->execute( "DROP TABLE IF EXISTS tao_aggregate_test" ); connection->execute( "CREATE TABLE tao_aggregate_test ( name TEXT PRIMARY KEY, age INTEGER, planet TEXT )" ); { const example::user u{ .name = "R. Giskard Reventlov", .age = 42, .planet = "Aurora" }; TEST_EXECUTE( connection->execute( "INSERT INTO tao_aggregate_test VALUES ( $1, $2, $3 )", u ) ); } for( const example::user u : connection->execute( "SELECT name, age, planet FROM tao_aggregate_test" ) ) { // NOLINT(performance-for-range-copy) TEST_ASSERT( u.name == "R. Giskard Reventlov" ); TEST_ASSERT( u.age == 42 ); TEST_ASSERT( u.planet == "Aurora" ); } } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/array.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include #include #include #include #include #include namespace { void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); const auto connection = tao::pq::connection::create( connection_string ); { connection->execute( "DROP TABLE IF EXISTS tao_array_test" ); connection->execute( "CREATE TABLE tao_array_test ( a INTEGER[] )" ); const std::array v{ 1, 0, 2, 4 }; connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", v ); std::vector v2 = { 42, 1701 }; connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", v2 ); v2.clear(); connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", v2 ); const auto r = connection->execute( "SELECT * FROM tao_array_test" ).vector< std::vector< int > >(); TEST_ASSERT( r.size() == 3 ); TEST_ASSERT( r[ 0 ].size() == 4 ); TEST_ASSERT( r[ 0 ][ 0 ] == 1 ); TEST_ASSERT( r[ 0 ][ 1 ] == 0 ); TEST_ASSERT( r[ 0 ][ 2 ] == 2 ); TEST_ASSERT( r[ 0 ][ 3 ] == 4 ); TEST_ASSERT( r[ 1 ].size() == 2 ); TEST_ASSERT( r[ 1 ][ 0 ] == 42 ); TEST_ASSERT( r[ 1 ][ 1 ] == 1701 ); TEST_ASSERT( r[ 2 ].empty() ); } { connection->execute( "DROP TABLE IF EXISTS tao_array_test" ); connection->execute( "CREATE TABLE tao_array_test ( a TEXT[] )" ); using type = std::vector< std::optional< std::string > >; const type v = { "FOO", "", "{BAR\\BAZ\"B,L;A}", "NULL", std::nullopt }; connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", v ); const auto r = connection->execute( "SELECT * FROM tao_array_test" ).as< type >(); TEST_ASSERT( r.size() == 5 ); TEST_ASSERT( r[ 0 ] == "FOO" ); TEST_ASSERT( r[ 1 ]->empty() ); // NOLINT(bugprone-unchecked-optional-access) TEST_ASSERT( r[ 2 ] == "{BAR\\BAZ\"B,L;A}" ); TEST_ASSERT( r[ 3 ] == "NULL" ); TEST_ASSERT( !r[ 4 ] ); } { connection->execute( "DROP TABLE IF EXISTS tao_array_test" ); connection->execute( "CREATE TABLE tao_array_test ( a TEXT[] )" ); using type = std::vector< std::string >; const type v = { "FOO", "", "{BAR\\BAZ\"B,L;A}", "NULL" }; connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", std::span{ v } ); const auto r = connection->execute( "SELECT * FROM tao_array_test" ).as< type >(); TEST_ASSERT( r.size() == 4 ); TEST_ASSERT( r[ 0 ] == "FOO" ); TEST_ASSERT( r[ 1 ].empty() ); // NOLINT(bugprone-unchecked-optional-access) TEST_ASSERT( r[ 2 ] == "{BAR\\BAZ\"B,L;A}" ); TEST_ASSERT( r[ 3 ] == "NULL" ); } { connection->execute( "DROP TABLE IF EXISTS tao_array_test" ); connection->execute( "CREATE TABLE tao_array_test ( a TEXT[][] NOT NULL )" ); using type = std::vector< std::set< std::string > >; const type v = { { "1", "F\"O\\O", "NULL" }, { "4", " XYZ ", "6" } }; connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", v ); const auto r = connection->execute( "SELECT * FROM tao_array_test" ).as< type >(); TEST_ASSERT( r == v ); } { connection->execute( "DROP TABLE IF EXISTS tao_array_test" ); connection->execute( "CREATE TABLE tao_array_test ( a TEXT[][] NOT NULL )" ); using type = std::vector< std::set< std::tuple< std::string > > >; const type v = { { "1", "F\"O\\O", "NULL" }, { "4", " XYZ ", "6" } }; connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", v ); const auto r = connection->execute( "SELECT * FROM tao_array_test" ).as< type >(); TEST_ASSERT( r == v ); } { connection->execute( "DROP TABLE IF EXISTS tao_array_test" ); connection->execute( "CREATE TABLE tao_array_test ( a BYTEA[][] NOT NULL )" ); using type = std::vector< tao::pq::binary >; const type v = { tao::pq::to_binary( "1" ), tao::pq::binary(), tao::pq::to_binary( "F\"O\\O" ), tao::pq::to_binary( "NU\0LL" ) }; connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", v ); const auto r = connection->execute( "SELECT * FROM tao_array_test" ).as< type >(); TEST_ASSERT( r == v ); } TEST_THROWS( connection->execute( "SELECT $1", "" ).as< std::vector< std::string > >() ); TEST_THROWS( connection->execute( "SELECT $1", "{" ).as< std::vector< std::string > >() ); TEST_THROWS( connection->execute( "SELECT $1", "{FOO" ).as< std::vector< std::string > >() ); TEST_THROWS( connection->execute( "SELECT $1", "{NULL}" ).as< std::vector< std::string > >() ); TEST_THROWS( connection->execute( "SELECT $1", "{\"FOO}" ).as< std::vector< std::string > >() ); TEST_THROWS( connection->execute( "SELECT $1", "{FOO}BAR" ).as< std::vector< std::string > >() ); { connection->execute( "DROP TABLE IF EXISTS tao_array_test" ); connection->execute( "CREATE TABLE tao_array_test ( a INTEGER NOT NULL )" ); connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", 1 ); connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", 2 ); connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", 3 ); connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", 4 ); connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", 5 ); connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", 6 ); connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", 7 ); connection->execute( "INSERT INTO tao_array_test VALUES ( $1 )", 8 ); const auto result = connection->execute( "SELECT * FROM tao_array_test WHERE a = ANY( $1 )", std::array{ 2, 3, 5, 6, 9 } ); TEST_ASSERT( result.vector< int >() == std::vector< int >{ 2, 3, 5, 6 } ); } } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/basic_datatypes.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include "utils/compare.hpp" #include "utils/getenv.hpp" #include "utils/macros.hpp" #include namespace { std::shared_ptr< tao::pq::connection > my_connection; auto prepare_datatype( const std::string& datatype ) -> bool { static std::string last; if( datatype == last ) { return false; } last = datatype; my_connection->execute( "DROP TABLE IF EXISTS tao_basic_datatypes_test" ); my_connection->execute( "CREATE TABLE tao_basic_datatypes_test ( a " + datatype + " )" ); return true; } void check_null( const std::string& datatype ) { std::cout << "check null: " << datatype << '\n'; if( prepare_datatype( datatype ) ) { TEST_ASSERT( my_connection->execute( "INSERT INTO tao_basic_datatypes_test VALUES ( $1 )", tao::pq::null ).rows_affected() == 1 ); } else { TEST_ASSERT( my_connection->execute( "UPDATE tao_basic_datatypes_test SET a=$1", tao::pq::null ).rows_affected() == 1 ); } const auto result = my_connection->execute( "SELECT * FROM tao_basic_datatypes_test" ); TEST_ASSERT( result[ 0 ][ 0 ].is_null() ); } template< typename T > void check( const std::string& datatype, const T& value ) { std::cout << "check: " << datatype << " value: " << value << '\n'; TEST_ASSERT( !prepare_datatype( datatype ) ); TEST_ASSERT( my_connection->execute( "UPDATE tao_basic_datatypes_test SET a=$1", value ).rows_affected() == 1 ); const auto result = my_connection->execute( "SELECT * FROM tao_basic_datatypes_test" ); if( value == value ) { // NOLINT(misc-redundant-expression) if( result[ 0 ][ 0 ].as< T >() != value ) { // LCOV_EXCL_START std::cout << "check: " << datatype << " value: " << value << " result: " << result.get( 0, 0 ) << " FAILED!\n"; TEST_ASSERT( false ); // LCOV_EXCL_STOP } } else { const auto v = result[ 0 ][ 0 ].as< T >(); if( v == v ) { // NOLINT(misc-redundant-expression) // LCOV_EXCL_START std::cout << "check: " << datatype << " value: NaN result: " << result.get( 0, 0 ) << " FAILED!\n"; TEST_ASSERT( false ); // LCOV_EXCL_STOP } } } template< typename T > requires std::is_signed_v< T > auto check( const std::string& datatype ) { check_null( datatype ); check< T >( datatype, std::numeric_limits< T >::min() ); check_null( datatype ); check< T >( datatype, -42 ); check< T >( datatype, -1 ); check< T >( datatype, 0 ); check< T >( datatype, 1 ); check< T >( datatype, 42 ); check< T >( datatype, std::numeric_limits< T >::max() ); } template< typename T > requires std::is_unsigned_v< T > auto check( const std::string& datatype ) { check_null( datatype ); check< T >( datatype, 0 ); check_null( datatype ); check< T >( datatype, 1 ); check< T >( datatype, 42 ); check< T >( datatype, std::numeric_limits< T >::max() ); } void check_bytea( const auto& t ) { TEST_ASSERT( my_connection->execute( "UPDATE tao_basic_datatypes_test SET a=$1", t ).rows_affected() == 1 ); const auto result = my_connection->execute( "SELECT * FROM tao_basic_datatypes_test" )[ 0 ][ 0 ].as< tao::pq::binary >(); TEST_ASSERT( tao::pq::internal::compare( result, t ) ); } void run() { my_connection = tao::pq::connection::create( tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ) ); my_connection->set_timeout( std::chrono::seconds( 1 ) ); check_null( "BOOLEAN" ); check< bool >( "BOOLEAN", true ); check_null( "BOOLEAN" ); check< bool >( "BOOLEAN", false ); // single characters in PostgreSQL are stored in a column of type "char", note the quotes are part of the type's name! check_null( "\"char\"" ); check< char >( "\"char\"", 'a' ); check_null( "\"char\"" ); check< char >( "\"char\"", 'z' ); check< char >( "\"char\"", 'A' ); check< char >( "\"char\"", 'Z' ); check< char >( "\"char\"", '0' ); check< char >( "\"char\"", '9' ); check< char >( "\"char\"", '$' ); check< char >( "\"char\"", '%' ); check< char >( "\"char\"", ' ' ); check< char >( "\"char\"", '"' ); check< char >( "\"char\"", '\'' ); check< char >( "\"char\"", '\\' ); check< char >( "\"char\"", '\n' ); check< char >( "\"char\"", '\t' ); check< signed char >( "NUMERIC" ); check< unsigned char >( "NUMERIC" ); check< short >( "NUMERIC" ); check< unsigned short >( "NUMERIC" ); check< int >( "NUMERIC" ); check< unsigned >( "NUMERIC" ); check< long >( "NUMERIC" ); check< unsigned long >( "NUMERIC" ); check< long long >( "NUMERIC" ); check< unsigned long long >( "NUMERIC" ); // this allows precise floating point values my_connection->execute( "SET extra_float_digits=3" ); check_null( "REAL" ); check< float >( "REAL", std::numeric_limits< float >::lowest() ); check_null( "REAL" ); check< float >( "REAL", -1e37F ); check< float >( "REAL", -1.25F ); check< float >( "REAL", -1.F ); check< float >( "REAL", -0.25F ); check< float >( "REAL", -1e-37F ); check< float >( "REAL", 0.F ); check< float >( "REAL", std::numeric_limits< float >::min() ); check< float >( "REAL", 1e-37F ); { float value = 0.123456F; for( int i = 0; i < 32; ++i ) { check< float >( "REAL", value ); value = std::nextafterf( value, 1 ); } } check< float >( "REAL", 0.25F ); check< float >( "REAL", 1.F ); check< float >( "REAL", 1.F + std::numeric_limits< float >::epsilon() ); check< float >( "REAL", 1.25F ); check< float >( "REAL", 1e37F ); check< float >( "REAL", std::numeric_limits< float >::max() ); check< float >( "REAL", INFINITY ); check< float >( "REAL", -INFINITY ); check< float >( "REAL", NAN ); check_null( "DOUBLE PRECISION" ); check< double >( "DOUBLE PRECISION", std::numeric_limits< double >::lowest() ); check_null( "DOUBLE PRECISION" ); check< double >( "DOUBLE PRECISION", -1e308 ); check< double >( "DOUBLE PRECISION", -1.25 ); check< double >( "DOUBLE PRECISION", -1 ); check< double >( "DOUBLE PRECISION", -0.25 ); check< double >( "DOUBLE PRECISION", -1e-307 ); check< double >( "DOUBLE PRECISION", 0 ); check< double >( "DOUBLE PRECISION", std::numeric_limits< double >::min() ); check< double >( "DOUBLE PRECISION", 1e-307 ); { double value = 0.123456789012345; for( int i = 0; i < 32; ++i ) { check< double >( "DOUBLE PRECISION", value ); value = std::nextafter( value, 1 ); } } check< double >( "DOUBLE PRECISION", 0.25 ); check< double >( "DOUBLE PRECISION", 1 ); check< double >( "DOUBLE PRECISION", 1 + std::numeric_limits< double >::epsilon() ); check< double >( "DOUBLE PRECISION", 1.25 ); check< double >( "DOUBLE PRECISION", 1e308 ); check< double >( "DOUBLE PRECISION", std::numeric_limits< double >::max() ); check< double >( "DOUBLE PRECISION", INFINITY ); check< double >( "DOUBLE PRECISION", -INFINITY ); // NOLINT(bugprone-narrowing-conversions) check< double >( "DOUBLE PRECISION", NAN ); check_null( "NUMERIC" ); check< float >( "NUMERIC", std::numeric_limits< float >::lowest() ); check< float >( "NUMERIC", -1e37F ); check< float >( "NUMERIC", -1.25F ); check< float >( "NUMERIC", -1.F ); check< float >( "NUMERIC", -0.25F ); check< float >( "NUMERIC", -1e-37F ); check< float >( "NUMERIC", 0.F ); // check< float >( "NUMERIC", std::numeric_limits< float >::min() ); check< float >( "NUMERIC", 1e-37F ); { float value = 0.123456F; for( int i = 0; i < 32; ++i ) { check< float >( "NUMERIC", value ); value = std::nextafterf( value, 1 ); } } check< float >( "NUMERIC", 0.25F ); check< float >( "NUMERIC", 1.F ); check< float >( "NUMERIC", 1.F + std::numeric_limits< float >::epsilon() ); check< float >( "NUMERIC", 1.25F ); check< float >( "NUMERIC", 1e37F ); check< float >( "NUMERIC", std::numeric_limits< float >::max() ); check< float >( "NUMERIC", NAN ); check< double >( "NUMERIC", std::numeric_limits< double >::lowest() ); check< double >( "NUMERIC", -1e308 ); check< double >( "NUMERIC", -1.25 ); check< double >( "NUMERIC", -1 ); check< double >( "NUMERIC", -0.25 ); check< double >( "NUMERIC", -1e-307 ); check< double >( "NUMERIC", 0 ); check< double >( "NUMERIC", std::numeric_limits< double >::min() ); check< double >( "NUMERIC", 1e-307 ); { double value = 0.123456789012345; for( int i = 0; i < 32; ++i ) { check< double >( "NUMERIC", value ); value = std::nextafter( value, 1 ); } } check< double >( "NUMERIC", 0.25 ); check< double >( "NUMERIC", 1 ); check< double >( "NUMERIC", 1 + std::numeric_limits< double >::epsilon() ); check< double >( "NUMERIC", 1.25 ); check< double >( "NUMERIC", 1e308 ); check< double >( "NUMERIC", std::numeric_limits< double >::max() ); check< double >( "NUMERIC", NAN ); // check< long double >( "NUMERIC", std::numeric_limits< long double >::lowest() ); check< long double >( "NUMERIC", -1e308L ); check< long double >( "NUMERIC", -1.25L ); check< long double >( "NUMERIC", -1L ); check< long double >( "NUMERIC", -0.25L ); check< long double >( "NUMERIC", -1e-307L ); check< long double >( "NUMERIC", 0 ); // check< long double >( "NUMERIC", std::numeric_limits< long double >::min() ); check< long double >( "NUMERIC", 1e-307L ); // { // long double value = 0.123456789012345L; // for( int i = 0; i < 32; ++i ) { // check< long double >( "NUMERIC", value ); // value = std::nextafterl( value, 1 ); // } // } check< long double >( "NUMERIC", 0.25L ); check< long double >( "NUMERIC", 1L ); check< long double >( "NUMERIC", 1e-307L ); // check< long double >( "NUMERIC", 1.L + std::numeric_limits< long double >::epsilon() ); check< long double >( "NUMERIC", 1.25L ); check< long double >( "NUMERIC", 1e308L ); // check< long double >( "NUMERIC", std::numeric_limits< long double >::max() ); check< long double >( "NUMERIC", NAN ); check_null( "TEXT" ); check< float >( "TEXT", std::numeric_limits< float >::lowest() ); check_null( "TEXT" ); check< float >( "TEXT", -1e37F ); check< float >( "TEXT", -1.25F ); check< float >( "TEXT", -1.F ); check< float >( "TEXT", -0.25F ); check< float >( "TEXT", -1e-37F ); check< float >( "TEXT", 0.F ); // check< float >( "TEXT", std::numeric_limits< float >::min() ); check< float >( "TEXT", 1e-37F ); { float value = 0.123456F; for( int i = 0; i < 32; ++i ) { check< float >( "TEXT", value ); value = std::nextafterf( value, 1 ); } } check< float >( "TEXT", 0.25F ); check< float >( "TEXT", 1.F ); check< float >( "TEXT", 1.F + std::numeric_limits< float >::epsilon() ); check< float >( "TEXT", 1.25F ); check< float >( "TEXT", 1e37F ); check< float >( "TEXT", std::numeric_limits< float >::max() ); check< float >( "TEXT", INFINITY ); check< float >( "TEXT", -INFINITY ); check< float >( "TEXT", NAN ); check< double >( "TEXT", std::numeric_limits< double >::lowest() ); check< double >( "TEXT", -1e308 ); check< double >( "TEXT", -1.25 ); check< double >( "TEXT", -1 ); check< double >( "TEXT", -0.25 ); check< double >( "TEXT", -1e-307 ); check< double >( "TEXT", 0 ); check< double >( "TEXT", std::numeric_limits< double >::min() ); check< double >( "TEXT", 1e-307 ); { double value = 0.123456789012345; for( int i = 0; i < 32; ++i ) { check< double >( "TEXT", value ); value = std::nextafter( value, 1 ); } } check< double >( "TEXT", 0.25 ); check< double >( "TEXT", 1 ); check< double >( "TEXT", 1 + std::numeric_limits< double >::epsilon() ); check< double >( "TEXT", 1.25 ); check< double >( "TEXT", 1e308 ); check< double >( "TEXT", std::numeric_limits< double >::max() ); check< double >( "TEXT", INFINITY ); check< double >( "TEXT", -INFINITY ); // NOLINT(bugprone-narrowing-conversions) check< double >( "TEXT", NAN ); // there is no data type to store 'long double' to PostgreSQL - but TEXT should do just fine... // check< long double >( "TEXT", std::numeric_limits< long double >::lowest() ); check< long double >( "TEXT", -1e308L ); check< long double >( "TEXT", -1.25L ); check< long double >( "TEXT", -1.L ); check< long double >( "TEXT", -0.25L ); check< long double >( "TEXT", -1e-307L ); check< long double >( "TEXT", 0 ); // check< long double >( "TEXT", std::numeric_limits< long double >::min() ); check< long double >( "TEXT", 1e-307L ); // { // long double value = 0.123456789012345L; // for( int i = 0; i < 32; ++i ) { // check< long double >( "TEXT", value ); // value = std::nextafterl( value, 1 ); // } // } check< long double >( "TEXT", 0.25L ); check< long double >( "TEXT", 1.L ); check< long double >( "TEXT", 1e-307L ); // check< long double >( "TEXT", 1.L + std::numeric_limits< long double >::epsilon() ); check< long double >( "TEXT", 1.25L ); check< long double >( "TEXT", 1e308L ); // check< long double >( "TEXT", std::numeric_limits< long double >::max() ); check< long double >( "TEXT", INFINITY ); check< long double >( "TEXT", -INFINITY ); // NOLINT(bugprone-narrowing-conversions) check< long double >( "TEXT", NAN ); check< std::string >( "TEXT", "" ); check< std::string >( "TEXT", " " ); check< std::string >( "TEXT", "abc" ); check< std::string >( "TEXT", "Hello, world!" ); check< std::string >( "TEXT", ";" ); check< std::string >( "TEXT", "\\" ); check< std::string >( "TEXT", "\"" ); check< std::string >( "TEXT", "'" ); check< std::string >( "TEXT", "'; DROP TABLE users; --" ); check< std::string >( "TEXT", "ä" ); check< std::string >( "TEXT", "€" ); check< std::string >( "TEXT", "𝄞" ); check< std::string >( "TEXT", "äöüÄÖÜ߀𝄞" ); check< std::string >( "TEXT", "ä\tö\nü\1Ä\"Ö;Ü'ß#€𝄞" ); check_null( "BYTEA" ); const unsigned char bdata[] = { 'v', 255, 0, 'a', 1, 'b', 0 }; check_bytea( tao::pq::to_binary( bdata ) ); check_bytea( tao::pq::to_binary_view( bdata ) ); } } // namespace auto main() -> int { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/chunk_mode.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #if !defined( LIBPQ_HAS_CHUNK_MODE ) auto main() -> int { return 0; } #else namespace { void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); // open a connection to the database const auto conn = tao::pq::connection::create( connection_string ); conn->execute( "DROP TABLE IF EXISTS tao_chunk_mode" ); conn->execute( "CREATE TABLE tao_chunk_mode ( name TEXT PRIMARY KEY, age INTEGER NOT NULL )" ); conn->prepare( "insert_user", "INSERT INTO tao_chunk_mode ( name, age ) VALUES ( $1, $2 )" ); conn->execute( "insert_user", "Daniel", 42 ); conn->execute( "insert_user", "Tom", 41 ); conn->execute( "insert_user", "Jerry", 29 ); conn->execute( "insert_user", "Alice", 32 ); conn->execute( "insert_user", "Bob", 19 ); conn->execute( "insert_user", "Charlie", 45 ); std::size_t count = 0; const auto tr = conn->transaction(); tr->send( "SELECT name, age FROM tao_chunk_mode" ); tr->set_chunk_mode( 2 ); while( true ) { const auto result = tr->get_result(); if( result.empty() ) { break; } for( const auto& row : result ) { ++count; std::cout << row[ "name" ].as< std::string >() << " is " << row[ "age" ].as< unsigned >() << " years old.\n"; } } TEST_ASSERT( count == 6 ); count = 0; tr->send( "SELECT name, age FROM tao_chunk_mode" ); tr->set_chunk_mode( 4 ); while( true ) { const auto result = tr->get_result(); if( result.empty() ) { break; } for( const auto& row : result ) { ++count; std::cout << row[ "name" ].as< std::string >() << " is " << row[ "age" ].as< unsigned >() << " years old.\n"; } } TEST_ASSERT( count == 6 ); TEST_THROWS( tr->set_single_row_mode() ); TEST_THROWS( tr->set_chunk_mode( 2 ) ); tr->send( "SELECT name, age FROM tao_chunk_mode" ); TEST_THROWS( tr->set_chunk_mode( 0 ) ); TEST_THROWS( tr->set_chunk_mode( -1 ) ); tr->set_chunk_mode( 2 ); tr->set_chunk_mode( 4 ); tr->set_single_row_mode(); tr->set_chunk_mode( 2 ); TEST_THROWS( tr->set_chunk_mode( -1 ) ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } #endif ================================================ FILE: test/integration/connection.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include #include #include namespace { // LCOV_EXCL_START auto my_poll( const int /*unused*/, const bool /*unused*/, const int /*unused*/ ) -> tao::pq::poll::status { TAO_PQ_INTERNAL_UNREACHABLE; } // LCOV_EXCL_STOP void run() { using namespace std::chrono_literals; // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); // NOLINT(clang-analyzer-deadcode.DeadStores) // connection_string must be valid TEST_THROWS( tao::pq::connection::create( "=" ) ); // connection_string must reference an existing and accessible database TEST_THROWS( tao::pq::connection::create( "dbname=DOES_NOT_EXIST" ) ); // open a connection const auto connection = tao::pq::connection::create( connection_string ); connection->set_timeout( 1s ); // open a second, independent connection (and discard it immediately) std::ignore = tao::pq::connection::create( connection_string ); // execute an SQL statement connection->execute( "DROP TABLE IF EXISTS tao_connection_test" ); TEST_THROWS( connection->direct()->get_result() ); // execution of empty statements fails TEST_THROWS( connection->execute( "" ) ); // execution of invalid statements fails TEST_THROWS( connection->execute( "FOO BAR BAZ" ) ); // a prepared statement's name must be a valid C++ identifier TEST_THROWS( connection->prepare( "", "DROP TABLE IF EXISTS tao_connection_test" ) ); TEST_THROWS( connection->prepare( "0drop_table", "DROP TABLE IF EXISTS tao_connection_test" ) ); TEST_THROWS( connection->prepare( "drop table", "DROP TABLE IF EXISTS tao_connection_test" ) ); // prepare a statement connection->prepare( "drop_table", "DROP TABLE IF EXISTS tao_connection_test" ); // execute a prepared statement // // note: the name of a prepared statement must be a valid identifier, all // actual SQL statements can be writen in a form which does not match a valid // identifier, so you can always make sure that they can not be confused. connection->execute( "drop_table" ); // statements must consume all parameters TEST_THROWS( connection->execute( "drop_table", 42 ) ); // a statement which is not a query does not return "affected rows" TEST_ASSERT( connection->execute( "drop_table" ).columns() == 0 ); // deallocate a prepared statement connection->deallocate( "drop_table" ); // no longer possible TEST_THROWS( connection->execute( "drop_table" ) ); // deallocate must refer to a prepared statement TEST_THROWS( connection->deallocate( "drop_table" ) ); // deallocate must get a valid name TEST_THROWS( connection->deallocate( "FOO BAR" ) ); // test that prepared statement names are case sensitive connection->prepare( "a", "SELECT 1" ); connection->prepare( "A", "SELECT 2" ); TEST_THROWS( connection->prepare( "a", "SELECT 2" ) ); TEST_ASSERT_MESSAGE( "checking prepared statement 'a'", connection->execute( "a" ).as< int >() == 1 ); connection->deallocate( "a" ); TEST_ASSERT_MESSAGE( "checking prepared statement 'A'", connection->execute( "A" ).as< int >() == 2 ); connection->prepare( "a", "SELECT 3" ); TEST_ASSERT_MESSAGE( "checking prepared statement 'a'", connection->execute( "a" ).as< int >() == 3 ); TEST_ASSERT_MESSAGE( "checking prepared statement 'A'", connection->execute( "A" ).as< int >() == 2 ); connection->deallocate( "A" ); TEST_ASSERT_MESSAGE( "checking prepared statement 'a'", connection->execute( "a" ).as< int >() == 3 ); connection->prepare( "A", "SELECT 4" ); TEST_ASSERT_MESSAGE( "checking prepared statement 'a'", connection->execute( "a" ).as< int >() == 3 ); TEST_ASSERT_MESSAGE( "checking prepared statement 'A'", connection->execute( "A" ).as< int >() == 4 ); // create a test table connection->execute( "CREATE TABLE tao_connection_test ( a INTEGER PRIMARY KEY, b INTEGER )" ); // a DELETE statement does not yield a result set TEST_ASSERT( connection->execute( "DELETE FROM tao_connection_test" ).columns() == 0 ); // out of range access throws TEST_THROWS( connection->execute( "SELECT * FROM tao_connection_test" ).at( 0 ) ); // insert some data connection->execute( "INSERT INTO tao_connection_test VALUES ( 1, 42 )" ); TEST_THROWS( connection->execute( "COPY tao_connection_test ( a, b ) TO STDOUT" ) ); TEST_THROWS( connection->execute( "COPY tao_connection_test ( a, b ) FROM STDIN" ) ); // read data TEST_ASSERT( connection->execute( "SELECT b FROM tao_connection_test WHERE a = 1" )[ 0 ][ 0 ].get() == std::string( "42" ) ); TEST_THROWS( connection->execute( "SELECT $1" ) ); TEST_THROWS( connection->execute( "SELECT $1", "One", "Two" ) ); TEST_THROWS( connection->execute( "SELECT $1", "" ).as< tao::pq::binary >() ); TEST_THROWS( connection->execute( "SELECT $1", "\\" ).as< tao::pq::binary >() ); TEST_THROWS( connection->execute( "SELECT $1", "\\xa" ).as< tao::pq::binary >() ); TEST_THROWS( connection->execute( "SELECT $1", "\\xa." ).as< tao::pq::binary >() ); { using callback_t = tao::pq::poll::status ( * )( int, bool, int ); const auto old_cb = *connection->poll_callback().target< callback_t >(); TEST_ASSERT( old_cb != nullptr ); TEST_ASSERT( *connection->poll_callback().target< callback_t >() != &my_poll ); connection->set_poll_callback( my_poll ); TEST_ASSERT( *connection->poll_callback().target< callback_t >() == &my_poll ); connection->reset_poll_callback(); TEST_ASSERT( *connection->poll_callback().target< callback_t >() == old_cb ); } connection->reset_timeout(); TEST_EXECUTE( connection->execute( "SELECT pg_sleep( 0.2 )" ) ); connection->set_timeout( 100ms ); TEST_THROWS( connection->execute( "SELECT pg_sleep( .2 )" ) ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/connection_pool.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include #include #include #include #include namespace { class limited_connection_pool : public tao::pq::connection_pool { struct guard { std::atomic< std::size_t >& m_counter; explicit guard( std::atomic< std::size_t >& counter ) noexcept : m_counter( counter ) { ++m_counter; } guard( const guard& ) = delete; guard( guard&& ) = delete; void operator=( const guard& ) = delete; void operator=( guard&& ) = delete; ~guard() { --m_counter; } }; mutable std::atomic< std::size_t > m_creating = 0; using tao::pq::connection_pool::connection_pool; [[nodiscard]] auto v_create() const -> std::unique_ptr< tao::pq::connection > override { if( attached() >= 4 || ( m_creating.load() > 2 ) ) { throw std::runtime_error( "connection limit reached" ); } const guard g( m_creating ); return connection_pool::v_create(); } }; // LCOV_EXCL_START auto my_poll( const int /*unused*/, const bool /*unused*/, const int /*unused*/ ) -> tao::pq::poll::status { TAO_PQ_INTERNAL_UNREACHABLE; } // LCOV_EXCL_STOP void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); const auto pool = tao::pq::connection_pool::create< limited_connection_pool >( connection_string ); TEST_ASSERT( pool->empty() ); TEST_ASSERT( pool->attached() == 0 ); TEST_ASSERT( pool->connection() ); TEST_ASSERT( !pool->empty() ); TEST_ASSERT( pool->size() == 1 ); TEST_ASSERT( pool->attached() == 0 ); TEST_ASSERT( pool->connection()->execute( "SELECT 1" ).as< int >() == 1 ); TEST_ASSERT( pool->size() == 1 ); TEST_ASSERT( pool->attached() == 0 ); { const auto conn = pool->connection(); TEST_ASSERT( pool->connection() ); TEST_ASSERT( conn->execute( "SELECT 2" ).as< int >() == 2 ); TEST_ASSERT( pool->size() == 1 ); TEST_ASSERT( pool->attached() == 1 ); const auto pool2 = tao::pq::connection_pool::create( connection_string ); TEST_ASSERT( pool->connection()->execute( "SELECT 3" ).as< int >() == 3 ); TEST_ASSERT( pool2->connection()->execute( "SELECT 4" ).as< int >() == 4 ); TEST_ASSERT( conn->execute( "SELECT 5" ).as< int >() == 5 ); TEST_ASSERT( pool2->connection()->execute( "SELECT 6" ).as< int >() == 6 ); } TEST_ASSERT( pool->size() == 2 ); TEST_ASSERT( pool->attached() == 0 ); { [[maybe_unused]] const auto c0 = pool->connection(); [[maybe_unused]] const auto c1 = pool->connection(); TEST_ASSERT( pool->empty() ); TEST_ASSERT( pool->attached() == 2 ); { [[maybe_unused]] const auto c2 = pool->connection(); [[maybe_unused]] const auto c3 = pool->connection(); TEST_ASSERT( pool->empty() ); TEST_ASSERT( pool->attached() == 4 ); TEST_THROWS( pool->connection() ); TEST_ASSERT( pool->empty() ); TEST_ASSERT( pool->attached() == 4 ); } TEST_ASSERT( pool->size() == 2 ); TEST_ASSERT( pool->attached() == 2 ); } TEST_ASSERT( pool->size() == 4 ); TEST_ASSERT( pool->attached() == 0 ); { using callback_t = tao::pq::poll::status ( * )( int, bool, int ); const auto old_cb = *pool->poll_callback().target< callback_t >(); TEST_ASSERT( old_cb != nullptr ); TEST_ASSERT( *pool->poll_callback().target< callback_t >() != &my_poll ); TEST_ASSERT( *pool->connection()->poll_callback().target< callback_t >() != &my_poll ); pool->set_poll_callback( my_poll ); TEST_ASSERT( *pool->poll_callback().target< callback_t >() == &my_poll ); TEST_ASSERT( *pool->connection()->poll_callback().target< callback_t >() == &my_poll ); pool->reset_poll_callback(); TEST_ASSERT( *pool->poll_callback().target< callback_t >() == old_cb ); TEST_ASSERT( *pool->connection()->poll_callback().target< callback_t >() == old_cb ); } using namespace std::chrono_literals; pool->set_timeout( 100ms ); TEST_THROWS( pool->execute( "SELECT pg_sleep( .5 )" ) ); pool->reset_timeout(); TEST_EXECUTE( pool->execute( "SELECT pg_sleep( .5 )" ) ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/example.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include namespace { void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); // open a connection to the database const auto conn = tao::pq::connection::create( connection_string ); // execute statements conn->execute( "DROP TABLE IF EXISTS tao_example" ); conn->execute( "CREATE TABLE tao_example ( name TEXT PRIMARY KEY, age INTEGER NOT NULL )" ); // prepare statements conn->prepare( "insert_user", "INSERT INTO tao_example ( name, age ) VALUES ( $1, $2 )" ); { // begin transaction const auto tr = conn->transaction(); // execute previously prepared statements tr->execute( "insert_user", "Daniel", 42 ); tr->execute( "insert_user", "Tom", 41 ); tr->execute( "insert_user", "Jerry", 29 ); // commit transaction tr->commit(); } // query data const auto users = conn->execute( "SELECT name, age FROM tao_example WHERE age >= $1", 40 ); // iterate and convert results for( const auto& row : users ) { std::cout << row[ "name" ].as< std::string >() << " is " << row[ "age" ].as< unsigned >() << " years old.\n"; } } } // namespace auto main() -> int { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/exception.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include "utils/getenv.hpp" #include "utils/macros.hpp" #include namespace { void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); const auto connection = tao::pq::connection::create( connection_string ); connection->execute( "DROP TABLE IF EXISTS tao_exception_test" ); TEST_THROWS( connection->execute( "SELECT a FROM tao_exception_test" ) ); connection->execute( "CREATE TABLE tao_exception_test ( a TEXT PRIMARY KEY, b TEXT NOT NULL )" ); TEST_THROWS( connection->execute( "SELECT c FROM tao_exception_test" ) ); TEST_THROWS( connection->execute( "FOO BAR BAZ" ) ); TEST_THROWS( connection->execute( "SELECT 1/0" ) ); TEST_THROWS( connection->execute( "SELECT * FROM tao_exception_test WHERE a = 42" ) ); TEST_THROWS( connection->execute( "SELECT * FROM tao_exception_test WHERE a[0] = 'FOO'" ) ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/large_object.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/compare.hpp" #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include #include #include #include namespace { template< typename R, typename T > void test( const std::shared_ptr< tao::pq::connection >& connection, const T& data ) { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); tao::pq::large_object lo( transaction, oid, std::ios_base::in | std::ios_base::out ); lo.write( data ); lo.seek( 0, std::ios_base::beg ); const auto result = lo.read< R >( 10 ); TEST_ASSERT( tao::pq::internal::compare( result, data ) ); } void run() { const auto connection = tao::pq::connection::create( tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ) ); { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); tao::pq::large_object lo( transaction, oid, std::ios_base::in | std::ios_base::out ); tao::pq::large_object lo2 = std::move( lo ); lo = std::move( lo2 ); TEST_ASSERT( lo.tell() == 0 ); lo.resize( 42 ); TEST_ASSERT( lo.seek( 20, std::ios_base::beg ) == 20 ); TEST_ASSERT( lo.tell() == 20 ); TEST_ASSERT( lo.seek( -20, std::ios_base::end ) == 22 ); TEST_ASSERT( lo.tell() == 22 ); TEST_ASSERT( lo.seek( 5, std::ios_base::cur ) == 27 ); TEST_ASSERT( lo.tell() == 27 ); TEST_ASSERT( lo.seek( -7, std::ios_base::cur ) == 20 ); TEST_ASSERT( lo.tell() == 20 ); TEST_THROWS( lo.seek( -60, std::ios_base::end ) ); } test< std::string >( connection, std::string( "hello" ) ); test< std::string >( connection, std::string_view( "hello" ) ); test< tao::pq::binary >( connection, tao::pq::to_binary( "nice!" ) ); test< tao::pq::binary >( connection, tao::pq::to_binary_view( "nice!" ) ); { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); tao::pq::large_object lo( transaction, oid, std::ios_base::in ); TEST_THROWS( lo.write( "abc" ) ); } { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); tao::pq::large_object lo( transaction, oid, std::ios_base::out ); TEST_THROWS( lo.read( std::numeric_limits< unsigned >::max() ) ); } { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); tao::pq::large_object lo( transaction, oid, std::ios_base::in | std::ios_base::out ); lo.close(); } { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); tao::pq::large_object lo( transaction, oid, std::ios_base::in | std::ios_base::out ); TEST_THROWS( lo.resize( -5 ) ); } { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); const tao::pq::large_object lo( transaction, oid, std::ios_base::in | std::ios_base::out ); tao::pq::large_object::remove( transaction, oid ); TEST_THROWS( lo.tell() ); } { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); TEST_THROWS( tao::pq::large_object::create( transaction, oid ) ); } { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); tao::pq::large_object::remove( transaction, oid ); TEST_THROWS( tao::pq::large_object::remove( transaction, oid ) ); } { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); tao::pq::large_object::remove( transaction, oid ); TEST_THROWS( tao::pq::large_object( transaction, oid, std::ios_base::in | std::ios_base::out ) ); } { const auto transaction = connection->transaction(); const auto oid = tao::pq::large_object::create( transaction ); const char* filename = "dummy.txt"; tao::pq::large_object::export_file( transaction, oid, filename ); const auto oid2 = tao::pq::large_object::import_file( transaction, filename ); TEST_ASSERT( oid != oid2 ); TEST_THROWS( tao::pq::large_object::export_file( transaction, oid, "" ) ); } { const auto transaction = connection->transaction(); TEST_THROWS( tao::pq::large_object::import_file( transaction, "" ) ); } } } // namespace auto main() -> int { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/log.cpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include #include namespace { [[nodiscard]] auto to_millis( std::chrono::steady_clock::time_point end ) noexcept { return std::chrono::duration_cast< std::chrono::milliseconds >( end - std::chrono::steady_clock::now() ).count(); } void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); // open a connection to the database const auto conn = tao::pq::connection::create( connection_string ); // attach a log receiver const auto log = std::make_shared< tao::pq::log >(); log->connection.send_query = []( tao::pq::connection& c, const char* s, int n, const Oid types[], const char* const values[], const int lengths[], const int formats[] ) { std::ignore = types; std::ignore = lengths; std::ignore = formats; std::cout << std::format( "send_query(connection={}, statement={}, n_params={})", static_cast< const void* >( &c ), s, n ) << '\n'; for( int i = 0; i != n; ++i ) { std::cout << std::format( " parameter[{}]={}", i, values[ i ] ) << '\n'; } }; log->connection.send_query.result = []( tao::pq::connection& c, int r ) { std::cout << std::format( "send_query(connection={}) -> {}", static_cast< const void* >( &c ), r ) << '\n'; }; log->connection.send_query_prepared = []( tao::pq::connection& c, const char* s, int n, const char* const values[], const int lengths[], const int formats[] ) { std::ignore = lengths; std::ignore = formats; std::cout << std::format( "send_query_prepared(connection={}, statement={}, n_params={})", static_cast< const void* >( &c ), s, n ) << '\n'; for( int i = 0; i != n; ++i ) { std::cout << std::format( " parameter[{}]={}", i, values[ i ] ) << '\n'; } }; log->connection.send_query_prepared.result = []( tao::pq::connection& c, int r ) { std::cout << std::format( "send_query_prepared(connection={}) -> {}", static_cast< const void* >( &c ), r ) << '\n'; }; log->connection.wait = []( tao::pq::connection& c, bool w, std::chrono::steady_clock::time_point e ) { std::cout << std::format( "wait(connection={}, wait_for_write={}, timeout={} ms)", static_cast< const void* >( &c ), w, to_millis( e ) ) << '\n'; }; log->connection.poll = []( tao::pq::connection& c, int s, bool w, int t ) { std::cout << std::format( "poll(connection={},socket={}, wait_for_write={}, timeout={} ms)", static_cast< const void* >( &c ), s, w, t ) << '\n'; }; log->connection.poll.result = []( tao::pq::connection& c, int s, tao::pq::poll::status r ) { std::cout << std::format( "poll(connection={},socket={}) -> {}", static_cast< const void* >( &c ), s, r ) << '\n'; }; log->connection.is_busy.result = []( const tao::pq::connection& c, int r ) { std::cout << std::format( "is_busy(connection={}) -> {}", static_cast< const void* >( &c ), r ) << '\n'; }; log->connection.consume_input = []( tao::pq::connection& c ) { std::cout << std::format( "consume_input(connection={})", static_cast< const void* >( &c ) ) << '\n'; }; log->connection.consume_input.result = []( tao::pq::connection& c, int r ) { std::cout << std::format( "consume_input(connection={}) -> {}", static_cast< const void* >( &c ), r ) << '\n'; }; log->connection.flush = []( tao::pq::connection& c ) { std::cout << std::format( "flush(connection={})", static_cast< const void* >( &c ) ) << '\n'; }; log->connection.flush.result = []( tao::pq::connection& c, int r ) { std::cout << std::format( "flush(connection={}) -> {}", static_cast< const void* >( &c ), r ) << '\n'; }; log->connection.get_result = []( tao::pq::connection& c, std::chrono::steady_clock::time_point e ) { std::cout << std::format( "get_result(connection={}, timeout={} ms)", static_cast< const void* >( &c ), to_millis( e ) ) << '\n'; }; log->connection.get_result.result = []( tao::pq::connection& c, PGresult* r ) { std::cout << std::format( "get_result(connection={}) -> {}", static_cast< const void* >( &c ), static_cast< const void* >( r ) ) << '\n'; if( r != nullptr ) { const auto st = PQresultStatus( r ); std::cout << std::format( " status={}", PQresStatus( st ) ) << '\n'; const auto cols = PQnfields( r ); if( cols != 0 ) { std::cout << std::format( " columns={}, rows={}", cols, PQntuples( r ) ) << '\n'; } else { const char* str = PQcmdTuples( r ); if( str[ 0 ] != '\0' ) { std::cout << std::format( " rows_affected={}", str ) << '\n'; } } } }; conn->set_log_handler( log ); // execute statements conn->execute( "DROP TABLE IF EXISTS tao_example" ); conn->execute( "CREATE TABLE tao_example ( name TEXT PRIMARY KEY, age INTEGER NOT NULL )" ); // prepare statements conn->prepare( "insert_user", "INSERT INTO tao_example ( name, age ) VALUES ( $1, $2 )" ); { // begin transaction const auto tr = conn->transaction(); // execute previously prepared statements tr->execute( "insert_user", "Daniel", 42 ); tr->execute( "insert_user", "Tom", 41 ); tr->execute( "insert_user", "Jerry", 29 ); // commit transaction tr->commit(); } // query data const auto users = conn->execute( "SELECT name, age FROM tao_example WHERE age >= $1", 40 ); // iterate and convert results for( const auto& row : users ) { std::cout << row[ "name" ].as< std::string >() << " is " << row[ "age" ].as< unsigned >() << " years old.\n"; } } } // namespace auto main() -> int { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/notifications.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #if defined( _WIN32 ) #include #else #include #endif namespace { std::size_t counter = 0; void handle_notification( const tao::pq::notification& n ) { std::cout << "channel '" << n.channel() << "' received '" << n.payload() << "'\n"; ++counter; } std::size_t foo_counter = 0; void handle_foo_notification( const char* payload ) { std::cout << "foo handler received '" << payload << "'\n"; ++foo_counter; } void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); const auto connection = tao::pq::connection::create( connection_string ); TEST_EXECUTE( connection->set_notification_handler( handle_notification ) ); TEST_EXECUTE( connection->listen( "FOO", handle_foo_notification ) ); TEST_ASSERT( counter == 0 ); TEST_ASSERT( foo_counter == 0 ); TEST_EXECUTE( connection->notify( "FOO" ) ); TEST_ASSERT( counter == 1 ); TEST_ASSERT( foo_counter == 1 ); TEST_ASSERT( connection->notification_handler( "FOO" ) ); TEST_ASSERT( !connection->notification_handler( "BAR" ) ); TEST_EXECUTE( connection->reset_notification_handler( "FOO" ) ); TEST_ASSERT( !connection->notification_handler( "FOO" ) ); TEST_EXECUTE( connection->notify( "FOO", "with payload" ) ); TEST_ASSERT( counter == 2 ); TEST_ASSERT( foo_counter == 1 ); TEST_EXECUTE( connection->unlisten( "FOO" ) ); TEST_EXECUTE( connection->notify( "FOO" ) ); TEST_EXECUTE( connection->get_notifications() ); TEST_ASSERT( counter == 2 ); TEST_ASSERT( connection->notification_handler() ); TEST_EXECUTE( connection->reset_notification_handler() ); TEST_ASSERT( !connection->notification_handler() ); #if defined( _WIN32 ) closesocket( connection->socket() ); #else close( connection->socket() ); #endif TEST_THROWS( connection->get_notifications() ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/parameter.cpp ================================================ // Copyright (c) 2023-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include namespace { void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); // open a connection to the database const auto conn = tao::pq::connection::create( connection_string ); // execute statements conn->execute( "DROP TABLE IF EXISTS tao_parameter_test" ); conn->execute( "CREATE TABLE tao_parameter_test ( name TEXT PRIMARY KEY, age INTEGER NOT NULL )" ); // prepare statements conn->prepare( "insert_user", "INSERT INTO tao_parameter_test ( name, age ) VALUES ( $1, $2 )" ); { // begin transaction const auto tr = conn->transaction(); // execute previously prepared statements { tao::pq::parameter p( "Daniel", 42 ); tr->execute( "insert_user", p ); p.reset(); p.bind( "Tom" ); tao::pq::parameter p2( 41 ); tr->execute( "insert_user", tao::pq::parameter( p, p2 ) ); } { tao::pq::parameter< 2 > p; std::string s = "Alice"; p.bind( s ); p.bind( std::move( s ) ); p.reset( std::string( "Jerry" ), 42 + 7 ); p.bind(); tao::pq::parameter<> p2( p ); tr->execute( "insert_user", p2 ); } { tao::pq::parameter< 1 > p; std::string s = "Alice"; p.bind( std::move( s ) ); tr->execute( "insert_user", p, 44 ); } // commit transaction tr->commit(); } // query data const auto users = conn->execute( "SELECT name, age FROM tao_parameter_test WHERE age >= $1", 40 ); // iterate and convert results for( const auto& row : users ) { std::cout << row[ "name" ].as< std::string >() << " is " << row[ "age" ].as< unsigned >() << " years old.\n"; } { tao::pq::parameter< 1 > p; p.bind( 1 ); TEST_THROWS( p.bind( 2 ) ); } { tao::pq::parameter< 1 > p; tao::pq::parameter< 1 > p2; p.bind( 1 ); p2.bind( 1 ); TEST_THROWS( p.bind( p2 ) ); } } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/password.cpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include namespace { void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); // open a connection to the database const auto conn = tao::pq::connection::create( connection_string ); // prevent cleartext passwords from showing up in logs, traces, etc. const std::string cleartext_password = "secret123"; const auto encrypted_password = conn->password( cleartext_password, "tao_test_role" ); // execute commands conn->execute( "DROP ROLE IF EXISTS tao_test_role" ); conn->execute( std::format( "CREATE ROLE tao_test_role PASSWORD '{}'", encrypted_password ) ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/pipeline_mode.cpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include namespace { void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); // NOLINT(clang-analyzer-deadcode.DeadStores) // open a connection const auto connection = tao::pq::connection::create( connection_string ); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::off ); TEST_THROWS( connection->pipeline_sync() ); connection->exit_pipeline_mode(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::off ); connection->enter_pipeline_mode(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::on ); connection->exit_pipeline_mode(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::off ); { auto tr = connection->direct(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::off ); connection->enter_pipeline_mode(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::on ); tr->send( "SELECT 42" ); tr->send( "SELECT 1234" ); connection->pipeline_sync(); TEST_ASSERT( tr->get_result().as< int >() == 42 ); tr->send( "SELECT 1701" ); connection->pipeline_sync(); TEST_ASSERT( tr->get_result().as< int >() == 1234 ); tr->consume_pipeline_sync(); TEST_ASSERT( tr->get_result().as< int >() == 1701 ); tr->consume_pipeline_sync(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::on ); connection->exit_pipeline_mode(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::off ); tr->commit(); } { auto tr = connection->transaction(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::off ); connection->enter_pipeline_mode(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::on ); tr->send( "SELECT 42" ); tr->send( "SELECT 1234" ); connection->pipeline_sync(); TEST_ASSERT( tr->get_result().as< int >() == 42 ); tr->send( "SELECT 1701" ); connection->pipeline_sync(); TEST_ASSERT( tr->get_result().as< int >() == 1234 ); tr->consume_pipeline_sync(); TEST_ASSERT( tr->get_result().as< int >() == 1701 ); tr->consume_pipeline_sync(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::on ); connection->exit_pipeline_mode(); TEST_ASSERT( connection->pipeline_status() == tao::pq::pipeline_status::off ); tr->commit(); } { auto pl = connection->pipeline(); pl->send( "SELECT 42" ); pl->send( "SELECT 1234" ); pl->sync(); TEST_ASSERT( pl->get_result().as< int >() == 42 ); pl->send( "SELECT 1701" ); pl->sync(); TEST_ASSERT( pl->get_result().as< int >() == 1234 ); pl->consume_sync(); TEST_ASSERT( pl->get_result().as< int >() == 1701 ); pl->consume_sync(); pl->finish(); } { auto pl = connection->transaction()->pipeline(); pl->send( "SELECT 42" ); pl->send( "SELECT 1234" ); pl->sync(); TEST_ASSERT( pl->get_result().as< int >() == 42 ); pl->send( "SELECT 1701" ); pl->sync(); TEST_ASSERT( pl->get_result().as< int >() == 1234 ); pl->consume_sync(); TEST_ASSERT( pl->get_result().as< int >() == 1701 ); pl->consume_sync(); pl->finish(); } { connection->execute( "DROP TABLE IF EXISTS tao_pipeline_mode" ); connection->execute( "CREATE TABLE tao_pipeline_mode ( name TEXT PRIMARY KEY, age INTEGER NOT NULL )" ); connection->prepare( "insert_user", "INSERT INTO tao_pipeline_mode ( name, age ) VALUES ( $1, $2 )" ); auto pl = connection->pipeline(); pl->send( "insert_user", "Daniel", 42 ); pl->send( "insert_user", "Tom", 41 ); pl->send( "insert_user", "Jerry", 29 ); pl->sync(); TEST_ASSERT( pl->get_result().rows_affected() == 1 ); // daniel TEST_ASSERT( pl->get_result().rows_affected() == 1 ); // tom TEST_ASSERT( pl->get_result().rows_affected() == 1 ); // jerry TEST_EXECUTE( pl->consume_sync() ); pl->send( "SELECT name, age FROM tao_pipeline_mode" ); pl->send( "SELECT name, age FROM tao_pipeline_mode" ); pl->sync(); pl->set_single_row_mode(); TEST_ASSERT( pl->get_result().size() == 1 ); TEST_ASSERT( pl->get_result().size() == 1 ); TEST_ASSERT( pl->get_result().size() == 1 ); TEST_ASSERT( pl->get_result().empty() ); TEST_ASSERT( pl->get_result().size() == 3 ); TEST_EXECUTE( pl->consume_sync() ); pl->finish(); } } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/result.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include #include #include #include namespace { void run() // NOLINT(readability-function-size) { static_assert( std::ranges::range< tao::pq::result > ); static_assert( std::ranges::bidirectional_range< tao::pq::result > ); static_assert( std::ranges::sized_range< tao::pq::result > ); // Even though we are *almost* a random access range, // we can't fully satisfy the requirements. // static_assert( std::ranges::random_access_range< tao::pq::result > ); const auto connection = tao::pq::connection::create( tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ) ); TEST_EXECUTE( connection->execute( "SELECT NULL" ) ); TEST_ASSERT( connection->execute( "SELECT NULL" ).is_null( 0, 0 ) ); TEST_THROWS( connection->execute( "SELECT NULL" ).is_null( 0, 1 ) ); TEST_THROWS( connection->execute( "SELECT NULL" ).is_null( 1, 0 ) ); TEST_THROWS( connection->execute( "SELECT NULL" ).get( 0, 0 ) ); TEST_THROWS( connection->execute( "SELECT NULL" ).get( 0, 1 ) ); TEST_THROWS( connection->execute( "SELECT NULL" ).get( 1, 0 ) ); TEST_ASSERT( connection->execute( "SELECT NULL" )[ 0 ].is_null( 0 ) ); TEST_THROWS( connection->execute( "SELECT NULL" )[ 0 ].is_null( 1 ) ); TEST_ASSERT( connection->execute( "SELECT NULL" )[ 0 ][ 0 ].is_null() ); TEST_ASSERT( connection->execute( "SELECT NULL" )[ 0 ][ 0 ] == tao::pq::null ); TEST_ASSERT( tao::pq::null == connection->execute( "SELECT NULL" )[ 0 ][ 0 ] ); TEST_ASSERT( !( connection->execute( "SELECT NULL" )[ 0 ][ 0 ] != tao::pq::null ) ); TEST_ASSERT( !( tao::pq::null != connection->execute( "SELECT NULL" )[ 0 ][ 0 ] ) ); TEST_THROWS( connection->execute( "SELECT NULL" )[ 0 ].at( 1 ) ); TEST_THROWS( connection->execute( "SELECT NULL" ).at( 1 )[ 1 ] ); TEST_EXECUTE( connection->execute( "SELECT 42" ) ); TEST_ASSERT( !connection->execute( "SELECT 42" ).is_null( 0, 0 ) ); TEST_THROWS( connection->execute( "SELECT 42" ).is_null( 0, 1 ) ); TEST_THROWS( connection->execute( "SELECT 42" ).is_null( 1, 0 ) ); TEST_ASSERT( connection->execute( "SELECT 42" ).get( 0, 0 ) == std::string( "42" ) ); TEST_THROWS( connection->execute( "SELECT 42" ).get( 0, 1 ) ); TEST_THROWS( connection->execute( "SELECT 42" ).get( 1, 0 ) ); TEST_ASSERT( !connection->execute( "SELECT 42" )[ 0 ].is_null( 0 ) ); TEST_THROWS( connection->execute( "SELECT 42" )[ 0 ].is_null( 1 ) ); TEST_ASSERT( !connection->execute( "SELECT 42" )[ 0 ][ 0 ].is_null() ); TEST_ASSERT( connection->execute( "SELECT 42" )[ 0 ][ 0 ] != tao::pq::null ); TEST_ASSERT( tao::pq::null != connection->execute( "SELECT 42" )[ 0 ][ 0 ] ); TEST_ASSERT( !( connection->execute( "SELECT 42" )[ 0 ][ 0 ] == tao::pq::null ) ); TEST_ASSERT( !( tao::pq::null == connection->execute( "SELECT 42" )[ 0 ][ 0 ] ) ); TEST_THROWS( connection->execute( "SELECT 42" )[ 0 ].at( 1 ) ); TEST_THROWS( connection->execute( "SELECT 42" ).at( 1 )[ 1 ] ); TEST_ASSERT( connection->execute( "SELECT 42" ).as< int >() == 42 ); TEST_ASSERT( connection->execute( "SELECT 1764" ).optional< int >() == 1764 ); TEST_ASSERT( !connection->execute( "SELECT 64 WHERE FALSE" ).optional< int >() ); TEST_ASSERT( !connection->execute( "SELECT NULL" ).as< std::optional< int > >() ); TEST_ASSERT( connection->execute( "SELECT 42" ).tuple< int >() == std::tuple< int >( 42 ) ); TEST_ASSERT( connection->execute( "SELECT 1, 2" ).pair< int, int >() == std::pair< int, int >( 1, 2 ) ); TEST_ASSERT( connection->execute( "SELECT 1, 2, 3, 4" ).tuple< int, int, int, int >() == std::tuple< int, int, int, int >( 1, 2, 3, 4 ) ); TEST_ASSERT( connection->execute( "SELECT 42" ).columns() == 1 ); TEST_ASSERT( connection->execute( "SELECT 42" ).vector< int >().size() == 1 ); TEST_ASSERT( connection->execute( "SELECT 42" ).list< int >().size() == 1 ); TEST_ASSERT( connection->execute( "SELECT 42" ).set< int >().size() == 1 ); TEST_ASSERT( connection->execute( "SELECT 42" ).multiset< int >().size() == 1 ); TEST_ASSERT( connection->execute( "SELECT 42" ).unordered_set< int >().size() == 1 ); TEST_ASSERT( connection->execute( "SELECT 42" ).unordered_multiset< int >().size() == 1 ); TEST_THROWS( connection->execute( "SELECT 42" ).as< bool >() ); TEST_ASSERT( connection->execute( "SELECT 1, 2" ).columns() == 2 ); TEST_ASSERT( connection->execute( "SELECT 1, 2" ).map< int, int >().size() == 1 ); TEST_ASSERT( connection->execute( "SELECT 1, 2" ).multimap< int, int >().size() == 1 ); TEST_ASSERT( connection->execute( "SELECT 1, 2" ).unordered_map< int, int >().size() == 1 ); TEST_ASSERT( connection->execute( "SELECT 1, 2" ).unordered_multimap< int, int >().size() == 1 ); TEST_ASSERT( connection->execute( "SELECT 1 UNION ALL SELECT 2" ).list< int >().size() == 2 ); TEST_ASSERT( connection->execute( "SELECT 1, 2, 3, 4 UNION ALL SELECT 5, 6, 7, 8" ).list< std::tuple< int, int, int, int > >().size() == 2 ); TEST_ASSERT( connection->execute( "SELECT 1, 2 UNION ALL SELECT 2, 5 UNION ALL SELECT 3, 42" ).map< int, int >().size() == 3 ); const auto result = connection->execute( "SELECT 1 AS a, 2 AS B, 3 AS \"C\"" ); TEST_ASSERT( result.has_rows_affected() ); TEST_ASSERT( !result.empty() ); TEST_ASSERT( result.size() == 1 ); TEST_ASSERT( result.columns() == 3 ); TEST_ASSERT( result.at( 0 ).columns() == 3 ); TEST_THROWS( result.at( 1 ) ); TEST_ASSERT( result.name( 0 ) == "a" ); TEST_ASSERT( result.name( 1 ) == "b" ); TEST_ASSERT( result.name( 2 ) == "C" ); TEST_ASSERT( result.index( "a" ) == 0 ); TEST_ASSERT( result.index( "A" ) == 0 ); TEST_ASSERT( result.index( "\"a\"" ) == 0 ); TEST_THROWS( result.index( "\"A\"" ) ); TEST_ASSERT( result.index( "b" ) == 1 ); TEST_ASSERT( result.index( "B" ) == 1 ); TEST_ASSERT( result.index( "\"b\"" ) == 1 ); TEST_THROWS( result.index( "\"B\"" ) ); TEST_THROWS( result.index( "c" ) ); TEST_THROWS( result.index( "C" ) ); TEST_THROWS( result.index( "\"c\"" ) ); TEST_ASSERT( result.index( "\"C\"" ) == 2 ); TEST_THROWS( connection->execute( "SELECT 42 WHERE FALSE" ).as< int >() ); TEST_THROWS( connection->execute( "SELECT 1 UNION ALL SELECT 2" ).as< int >() ); TEST_THROWS( connection->execute( "SELECT 42" ).pair< int, int >() ); TEST_THROWS( connection->execute( "SELECT 1, 2" ).as< int >() ); TEST_THROWS( connection->execute( "SELECT ''" ).as< char >() ); TEST_THROWS( connection->execute( "SELECT 'Hallo'" ).as< char >() ); TEST_THROWS( connection->execute( "SELECT ''" ).as< signed char >() ); TEST_THROWS( connection->execute( "SELECT 'Hallo'" ).as< signed char >() ); TEST_THROWS( connection->execute( "SELECT -129" ).as< signed char >() ); TEST_ASSERT( connection->execute( "SELECT -128" ).as< signed char >() == -128 ); TEST_ASSERT( connection->execute( "SELECT 127" ).as< signed char >() == 127 ); TEST_THROWS( connection->execute( "SELECT 128" ).as< signed char >() ); TEST_THROWS( connection->execute( "SELECT ''" ).as< unsigned char >() ); TEST_THROWS( connection->execute( "SELECT 'Hallo'" ).as< unsigned char >() ); TEST_ASSERT( connection->execute( "SELECT 255" ).as< unsigned char >() == 255 ); TEST_THROWS( connection->execute( "SELECT 256" ).as< unsigned char >() ); TEST_THROWS( connection->execute( "SELECT ''" ).as< short >() ); TEST_THROWS( connection->execute( "SELECT 'Hallo'" ).as< short >() ); TEST_THROWS( connection->execute( "SELECT -32769" ).as< short >() ); TEST_ASSERT( connection->execute( "SELECT -32768" ).as< short >() == -32768 ); TEST_ASSERT( connection->execute( "SELECT 32767" ).as< short >() == 32767 ); TEST_THROWS( connection->execute( "SELECT 32768" ).as< short >() ); TEST_THROWS( connection->execute( "SELECT ''" ).as< unsigned short >() ); TEST_THROWS( connection->execute( "SELECT 'Hallo'" ).as< unsigned short >() ); TEST_ASSERT( connection->execute( "SELECT 65535" ).as< unsigned short >() == 65535 ); TEST_THROWS( connection->execute( "SELECT 65536" ).as< unsigned short >() ); TEST_THROWS( connection->execute( "SELECT ''" ).as< int >() ); TEST_THROWS( connection->execute( "SELECT 'Hallo'" ).as< int >() ); TEST_THROWS( connection->execute( "SELECT -2147483649" ).as< int >() ); TEST_ASSERT( connection->execute( "SELECT -2147483648" ).as< int >() == -2147483648LL ); TEST_ASSERT( connection->execute( "SELECT 2147483647" ).as< int >() == 2147483647 ); TEST_THROWS( connection->execute( "SELECT 2147483648" ).as< int >() ); TEST_THROWS( connection->execute( "SELECT ''" ).as< unsigned >() ); TEST_THROWS( connection->execute( "SELECT 'Hallo'" ).as< unsigned >() ); TEST_ASSERT( connection->execute( "SELECT 4294967295" ).as< unsigned >() == 4294967295 ); TEST_THROWS( connection->execute( "SELECT 4294967296" ).as< unsigned >() ); TEST_THROWS( connection->execute( "SELECT '42 FOO'" ).as< unsigned >() ); TEST_THROWS( connection->execute( "SELECT '42BAR'" ).as< unsigned >() ); int count = 0; for( const auto& row : connection->execute( "SELECT 1 UNION ALL SELECT 2" ) ) { for( const auto& field : row ) { TEST_ASSERT( field.as< int >() == ++count ); } } TEST_ASSERT( count == 2 ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/row.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include #include #include namespace { void run() { static_assert( std::ranges::range< tao::pq::row > ); static_assert( std::ranges::bidirectional_range< tao::pq::row > ); static_assert( std::ranges::sized_range< tao::pq::row > ); // Even though we are *almost* a random access range, // we can't fully satisfy the requirements. // static_assert( std::ranges::random_access_range< tao::pq::row > ); const auto connection = tao::pq::connection::create( tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ) ); TEST_ASSERT( connection->execute( "SELECT 42" )[ 0 ].as< int >() == 42 ); TEST_ASSERT( connection->execute( "SELECT 1764" ).at( 0 ).optional< int >() == 1764 ); TEST_ASSERT( !connection->execute( "SELECT NULL" )[ 0 ].as< std::optional< int > >() ); TEST_ASSERT( connection->execute( "SELECT 42" )[ 0 ][ 0 ].get() == std::string( "42" ) ); TEST_ASSERT( connection->execute( "SELECT 42" )[ 0 ][ 0 ].as< int >() == 42 ); TEST_ASSERT( connection->execute( "SELECT 42" )[ 0 ][ 0 ].optional< int >() == 42 ); TEST_ASSERT( connection->execute( "SELECT 1, 2" )[ 0 ].pair< int, int >() == std::pair< int, int >( 1, 2 ) ); TEST_ASSERT( connection->execute( "SELECT 1, 2, 3, 4" )[ 0 ].tuple< int, int, int, int >() == std::tuple< int, int, int, int >( 1, 2, 3, 4 ) ); TEST_THROWS( connection->execute( "SELECT 42" )[ 0 ].as< bool >() ); const auto result = connection->execute( "SELECT 1 AS a, 2 AS B, 3 AS \"C\", 4 as \"A\"" ); const auto& row = result[ 0 ]; TEST_ASSERT( row.columns() == 4 ); TEST_ASSERT( row.name( 0 ) == "a" ); TEST_ASSERT( row.name( 1 ) == "b" ); TEST_ASSERT( row.name( 2 ) == "C" ); TEST_ASSERT( row.name( 3 ) == "A" ); TEST_THROWS( row.name( 4 ) ); TEST_ASSERT( row.at( 0 ).name() == "a" ); TEST_ASSERT( row[ 1 ].name() == "b" ); TEST_ASSERT( row.at( 2 ).name() == "C" ); TEST_ASSERT( row[ 3 ].name() == "A" ); TEST_THROWS( row.at( 4 ) ); TEST_ASSERT( row.index( "a" ) == 0 ); TEST_ASSERT( row.index( "A" ) == 0 ); TEST_ASSERT( row.index( "\"a\"" ) == 0 ); TEST_ASSERT( row.index( "\"A\"" ) == 3 ); TEST_ASSERT( row.index( "b" ) == 1 ); TEST_ASSERT( row.index( "B" ) == 1 ); TEST_ASSERT( row.index( "\"b\"" ) == 1 ); TEST_THROWS( row.index( "\"B\"" ) ); TEST_THROWS( row.index( "c" ) ); TEST_THROWS( row.index( "C" ) ); TEST_THROWS( row.index( "\"c\"" ) ); TEST_ASSERT( row.index( "\"C\"" ) == 2 ); TEST_THROWS( row.get< std::string >( 4 ) ); TEST_THROWS( row.get< std::optional< std::string > >( 4 ) ); TEST_THROWS( row.get< std::pair< std::string, std::string > >( 3 ) ); const auto result2 = connection->execute( "SELECT 1 AS a, 2 AS b, 3 AS a" ); const auto& row2 = result2[ 0 ]; TEST_ASSERT( row2.index( "a" ) == 0 ); TEST_ASSERT( row2.index( "A" ) == 0 ); TEST_ASSERT( row2.slice( 1, 2 ).index( "a" ) == 1 ); TEST_ASSERT( row2.slice( 1, 2 ).index( "A" ) == 1 ); TEST_THROWS( row2.slice( 1, 1 ).index( "a" ) ); TEST_THROWS( row2.slice( 1, 1 ).index( "A" ) ); TEST_THROWS( row2.slice( 2, 1 ).index( "b" ) ); TEST_THROWS( row2.slice( 2, 1 ).index( "B" ) ); TEST_THROWS( row2.slice( 0, 0 ) ); TEST_THROWS( row2.slice( 1, 0 ) ); TEST_THROWS( row2.slice( 2, 0 ) ); TEST_THROWS( row2.slice( 0, 4 ) ); TEST_THROWS( row2.slice( 1, 3 ) ); TEST_THROWS( row2.slice( 2, 2 ) ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/single_row_mode.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include namespace { void run() { // overwrite the default with an environment variable if needed const auto connection_string = tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ); // open a connection to the database const auto conn = tao::pq::connection::create( connection_string ); conn->execute( "DROP TABLE IF EXISTS tao_single_row_mode" ); conn->execute( "CREATE TABLE tao_single_row_mode ( name TEXT PRIMARY KEY, age INTEGER NOT NULL )" ); conn->prepare( "insert_user", "INSERT INTO tao_single_row_mode ( name, age ) VALUES ( $1, $2 )" ); conn->execute( "insert_user", "Daniel", 42 ); conn->execute( "insert_user", "Tom", 41 ); conn->execute( "insert_user", "Jerry", 29 ); const auto tr = conn->transaction(); tr->send( "SELECT name, age FROM tao_single_row_mode" ); tr->set_single_row_mode(); while( true ) { // in single row mode, each result contains either // a single row or no row when the end is reached. const auto result = tr->get_result(); if( result.empty() ) { break; } // the loop is unnecessary for single row mode, // but in chunked mode multiple rows per result are possible. for( const auto& row : result ) { std::cout << row[ "name" ].as< std::string >() << " is " << row[ "age" ].as< unsigned >() << " years old.\n"; } } TEST_THROWS( tr->set_single_row_mode() ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/table_reader.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/compare.hpp" #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include #include namespace { void run() { const auto connection = tao::pq::connection::create( tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ) ); connection->execute( "DROP TABLE IF EXISTS tao_table_reader_test" ); connection->execute( "CREATE TABLE tao_table_reader_test ( a INTEGER NOT NULL, b DOUBLE PRECISION, c TEXT )" ); // we use a table_writer to fill the table with 100.000 rows. { tao::pq::table_writer tw( connection->direct(), "COPY tao_table_reader_test ( a, b, c ) FROM STDIN" ); for( unsigned n = 0; n < 100000; ++n ) { tw.insert( n, n / 100.0, "EUR" ); } TEST_ASSERT_MESSAGE( "validate reported result size", tw.commit() == 100000 ); TEST_ASSERT_MESSAGE( "validate actual result size", connection->execute( "SELECT COUNT(*) FROM tao_table_reader_test" ).as< std::size_t >() == 100000 ); } { tao::pq::table_reader tr( connection->direct(), "COPY tao_table_reader_test ( a, b, c ) TO STDOUT" ); TEST_THROWS( connection->direct() ); std::size_t count = 0; for( const auto& row : tr ) { for( const auto& field : row ) { if( !field.is_null() ) { ++count; } } } TEST_ASSERT_MESSAGE( "validate count", count == 300000 ); } TEST_THROWS( tao::pq::table_reader( connection->direct(), "SELECT 42" ) ); TEST_THROWS( tao::pq::table_reader( connection->direct(), "" ) ); TEST_THROWS( tao::pq::table_reader( connection->direct(), "COPY tao_table_reader_test ( a, b, c, d ) TO STDOUT" ) ); TEST_THROWS( tao::pq::table_reader( connection->direct(), "COPY tao_table_reader_test ( a, b, c ) FROM STDIN" ) ); TEST_THROWS( connection->execute( "COPY tao_table_reader_test ( a, b, c ) TO STDOUT" ) ); connection->execute( "DROP TABLE IF EXISTS tao_table_reader_test" ); connection->execute( "CREATE TABLE tao_table_reader_test ( a BYTEA )" ); { tao::pq::table_writer tw( connection->direct(), "COPY tao_table_reader_test ( a ) FROM STDIN" ); tw.insert( tao::pq::to_binary_view( "1" ) ); tw.insert( tao::pq::binary_view() ); tw.insert( tao::pq::null ); tw.insert( tao::pq::to_binary_view( "F\"O\\O" ) ); tw.insert( tao::pq::to_binary_view( "NU\0LL" ) ); TEST_ASSERT( tw.commit() == 5 ); tao::pq::table_reader tr( connection->direct(), "COPY tao_table_reader_test ( a ) TO STDOUT" ); const auto result = tr.vector< std::optional< tao::pq::binary > >(); TEST_ASSERT( result.size() == 5 ); TEST_ASSERT( tao::pq::internal::compare( result[ 0 ].value(), tao::pq::to_binary_view( "1" ) ) ); TEST_ASSERT( tao::pq::internal::compare( result[ 1 ].value(), tao::pq::binary_view() ) ); TEST_ASSERT( !result[ 2 ] ); TEST_ASSERT( tao::pq::internal::compare( result[ 3 ].value(), tao::pq::to_binary_view( "F\"O\\O" ) ) ); TEST_ASSERT( tao::pq::internal::compare( result[ 4 ].value(), tao::pq::to_binary_view( "NU\0LL" ) ) ); } connection->execute( "DROP TABLE IF EXISTS tao_table_reader_test" ); connection->execute( "CREATE TABLE tao_table_reader_test ( a INTEGER NOT NULL, b DOUBLE PRECISION, c TEXT )" ); connection->execute( "INSERT INTO tao_table_reader_test VALUES( $1, $2, $3 )", 1, 1.234567, "A\bB\fC\"D'E\n\rF\tGH\vI\\J" ); connection->execute( "INSERT INTO tao_table_reader_test VALUES( $1, $2, $3 )", 2, tao::pq::null, tao::pq::null ); connection->execute( "INSERT INTO tao_table_reader_test VALUES( $1, $2, $3 )", 3, 42, "FOO" ); { tao::pq::table_reader tr( connection->direct(), "COPY tao_table_reader_test ( a, b, c ) TO STDOUT" ); TEST_ASSERT( tr.columns() == 3 ); { TEST_ASSERT( tr.get_row() ); const auto& row = tr.row(); auto [ a, b, c ] = row.tuple< int, std::optional< double >, std::optional< std::string_view > >(); TEST_ASSERT( a == 1 ); TEST_ASSERT( b == 1.234567 ); TEST_ASSERT( c == "A\bB\fC\"D'E\n\rF\tGH\vI\\J" ); TEST_ASSERT( row.at( 0 ).as< int >() == 1 ); TEST_ASSERT( !row[ 1 ].is_null() ); TEST_ASSERT( row[ 1 ].get() == std::string_view( "1.234567" ) ); TEST_ASSERT( row[ 1 ].optional< double >() == 1.234567 ); TEST_THROWS( row.at( 3 ) ); TEST_THROWS( row.slice( 0, 0 ) ); TEST_THROWS( row.slice( 1, 0 ) ); TEST_THROWS( row.slice( 0, 4 ) ); TEST_THROWS( row.tuple< int, std::optional< double > >() ); TEST_THROWS( row.tuple< int, std::optional< double >, std::optional< std::string_view >, std::optional< std::string_view > >() ); } { TEST_ASSERT( tr.get_row() ); auto [ a, b, c ] = tr.row().tuple< int, std::optional< double >, std::optional< std::string_view > >(); TEST_ASSERT( a == 2 ); TEST_ASSERT( !b ); TEST_ASSERT( !c ); TEST_THROWS( tr.row().tuple< int, double, std::string_view >() ); } PQclear( PQexec( connection->underlying_raw_ptr(), "SELECT 42" ) ); TEST_THROWS( tr.get_row() ); } } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/table_writer.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include "utils/getenv.hpp" #include "utils/macros.hpp" #include namespace { void run() { const auto connection = tao::pq::connection::create( tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ) ); connection->execute( "DROP TABLE IF EXISTS tao_table_writer_test" ); connection->execute( "CREATE TABLE tao_table_writer_test ( a INTEGER NOT NULL, b DOUBLE PRECISION, c TEXT )" ); tao::pq::table_writer tw( connection->direct(), "COPY tao_table_writer_test ( a, b, c ) FROM STDIN" ); TEST_THROWS( connection->direct() ); for( unsigned n = 0; n < 100000; ++n ) { tw.insert( n, n + 23.45, "EUR" ); } tw.insert( std::make_tuple( 123456, tao::pq::null, "EUR\nUSD\"FOO\\BAR" ) ); TEST_ASSERT_MESSAGE( "validate reported result size", tw.commit() == 100001 ); TEST_ASSERT_MESSAGE( "validate actual result size", connection->execute( "SELECT COUNT(*) FROM tao_table_writer_test" ).as< std::size_t >() == 100001 ); { const auto [ a, b, c ] = connection->execute( "SELECT a, b, c FROM tao_table_writer_test WHERE b IS NULL" ).tuple< unsigned, std::optional< double >, std::optional< std::string > >(); TEST_ASSERT_MESSAGE( "checking 'a' value", a == 123456 ); TEST_ASSERT_MESSAGE( "checking 'b' value", !b ); TEST_ASSERT_MESSAGE( "checking 'c' value", c == "EUR\nUSD\"FOO\\BAR" ); } TEST_THROWS( tao::pq::table_writer( connection->direct(), "SELECT 42" ) ); TEST_THROWS( tao::pq::table_writer( connection->direct(), "" ) ); TEST_THROWS( tao::pq::table_writer( connection->direct(), "COPY tao_table_writer_test ( a, b, c, d ) FROM STDIN" ) ); TEST_THROWS( tao::pq::table_writer( connection->direct(), "COPY tao_table_writer_test ( a, b, c ) TO STDOUT" ) ); TEST_THROWS( connection->execute( "COPY tao_table_writer_test ( a, b, c ) FROM STDIN" ) ); TEST_THROWS_MESSAGE( "mixed usage test #1", { const auto tr = connection->direct(); const tao::pq::table_writer tw2( tr, "COPY tao_table_writer_test ( a, b, c ) FROM STDIN" ); tr->execute( "SELECT 42" ); } ); TEST_THROWS_MESSAGE( "mixed usage test #2", { const auto tr = connection->transaction(); const tao::pq::table_writer tw2( tr, "COPY tao_table_writer_test ( a, b, c ) FROM STDIN" ); tr->execute( "SELECT 42" ); } ); connection->execute( "DROP TABLE tao_table_writer_test" ); connection->execute( "CREATE TABLE tao_table_writer_test ( a INTEGER NOT NULL, b DOUBLE PRECISION, c TEXT )" ); { tao::pq::table_writer tw2( connection->direct(), "COPY tao_table_writer_test ( a, b, c ) FROM STDIN" ); tw2.insert_raw( "1\t0\tXXX\n" ); tw2.commit(); } TEST_ASSERT( connection->execute( "SELECT COUNT(*) FROM tao_table_writer_test" ).as< std::size_t >() == 1 ); { tao::pq::table_writer tw2( connection->direct(), "COPY tao_table_writer_test ( a, b, c ) FROM STDIN" ); tw2.insert_raw( "2\t0\tXXX\n" ); } TEST_ASSERT( connection->execute( "SELECT COUNT(*) FROM tao_table_writer_test" ).as< std::size_t >() == 1 ); connection->execute( "DROP TABLE tao_table_writer_test" ); connection->execute( "CREATE TABLE tao_table_writer_test ( a INTEGER NOT NULL, b DOUBLE PRECISION, c TEXT )" ); { tao::pq::table_writer tw2( connection->direct(), "COPY tao_table_writer_test ( a, b, c ) FROM STDIN" ); tw2.insert_raw( "3\t0\tXXX\n" ); PQclear( PQexec( connection->underlying_raw_ptr(), "SELECT 42" ) ); TEST_THROWS( tw2.commit() ); } { tao::pq::table_writer tw2( connection->direct(), "COPY tao_table_writer_test ( a, b, c ) FROM STDIN" ); tw2.insert_raw( "4\t0\tXXX\n" ); PQclear( PQexec( connection->underlying_raw_ptr(), "SELECT 42" ) ); TEST_THROWS( tw2.insert_raw( "5\t0\tXXX\n" ) ); } connection->execute( "DROP TABLE tao_table_writer_test" ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/traits.cpp ================================================ // Copyright (c) 2020-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include namespace example { struct user { int a, b, c, d; explicit user( const int i ) noexcept : a( i ), b( i + 1 ), c( i + 2 ), d( i + 3 ) {} [[nodiscard]] auto to_taopq() const noexcept { return std::tie( a, b, c, d ); } private: user( const int in_a, const int in_b, const int in_c, const int in_d ) noexcept : a( in_a ), b( in_b ), c( in_c ), d( in_d ) {} public: [[nodiscard]] static auto from_taopq( const int in_a, const int in_b, const int in_c, const int in_d ) noexcept { return user( in_a, in_b, in_c, in_d ); } }; struct user2 { int a, b, c, d; explicit user2( int i ) noexcept : a( i ), b( i + 1 ), c( i + 2 ), d( i + 3 ) {} user2( const int in_a, const int in_b, const int in_c, const int in_d ) noexcept : a( in_a ), b( in_b ), c( in_c ), d( in_d ) {} }; struct user3 { int a, b, c, d; explicit user3( int i ) noexcept : a( i ), b( i + 1 ), c( i + 2 ), d( i + 3 ) {} user3( const int in_a, const int in_b, const int in_c, const int in_d ) noexcept : a( in_a ), b( in_b ), c( in_c ), d( in_d ) {} }; [[nodiscard]] auto to_taopq( const user3& v ) noexcept // NOLINT(misc-use-internal-linkage) { return std::tie( v.a, v.b, v.c, v.d ); } } // namespace example template<> struct tao::pq::bind< example::user2 > { [[nodiscard]] static auto to_taopq( const example::user2& v ) noexcept { return std::tie( v.a, v.b, v.c, v.d ); } [[nodiscard]] static auto from_taopq( const int a, const int b, const int c, const int d ) noexcept { return example::user2( a, b, c, d ); } }; namespace { void run() { const auto connection = tao::pq::connection::create( tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ) ); connection->execute( "DROP TABLE IF EXISTS tao_traits_test" ); connection->execute( "CREATE TABLE tao_traits_test ( a INTEGER PRIMARY KEY, b INTEGER, c INTEGER, d INTEGER )" ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_traits_test VALUES ( 1, 2, 3, 4 )" ) ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_traits_test VALUES ( $1, $2, $3, $4 )", 2, 3, 4, 5 ) ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_traits_test VALUES ( $1, $2, $3, $4 )", 3, std::make_pair( 4, 5 ), 6 ) ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_traits_test VALUES ( $1, $2, $3, $4 )", std::make_tuple( 4, 5 ), std::make_tuple( 6, 7 ) ) ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_traits_test VALUES ( $1, $2, $3, $4 )", std::make_tuple( 5, std::make_pair( 6, 7 ), 8 ) ) ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_traits_test VALUES ( $1, $2, $3, $4 )", example::user( 6 ) ) ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_traits_test VALUES ( $1, $2, $3, $4 )", example::user2( 7 ) ) ); const auto result = connection->execute( "SELECT * FROM tao_traits_test" ); TEST_ASSERT( result.size() == 7 ); for( const auto& row : result ) { { const auto v = row.tuple< int, int, int, int >(); TEST_ASSERT( std::get< 1 >( v ) == std::get< 0 >( v ) + 1 ); TEST_ASSERT( std::get< 2 >( v ) == std::get< 1 >( v ) + 1 ); TEST_ASSERT( std::get< 3 >( v ) == std::get< 2 >( v ) + 1 ); } { const auto v = row.tuple< std::tuple< int, int >, std::tuple< int, int > >(); TEST_ASSERT( std::get< 1 >( std::get< 0 >( v ) ) == std::get< 0 >( std::get< 0 >( v ) ) + 1 ); TEST_ASSERT( std::get< 0 >( std::get< 1 >( v ) ) == std::get< 1 >( std::get< 0 >( v ) ) + 1 ); TEST_ASSERT( std::get< 1 >( std::get< 1 >( v ) ) == std::get< 0 >( std::get< 1 >( v ) ) + 1 ); } { const auto v = row.tuple< int, std::tuple< int, int >, int >(); TEST_ASSERT( std::get< 0 >( std::get< 1 >( v ) ) == std::get< 0 >( v ) + 1 ); TEST_ASSERT( std::get< 1 >( std::get< 1 >( v ) ) == std::get< 0 >( std::get< 1 >( v ) ) + 1 ); TEST_ASSERT( std::get< 2 >( v ) == std::get< 1 >( std::get< 1 >( v ) ) + 1 ); } { const auto [ a, b, c, d ] = row.tuple< int, int, int, int >(); TEST_ASSERT( b == a + 1 ); TEST_ASSERT( c == b + 1 ); TEST_ASSERT( d == c + 1 ); } } TEST_EXECUTE( connection->execute( "DELETE FROM tao_traits_test" ) ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_traits_test VALUES ( $1, $2, $3, $4 )", example::user( 8 ) ) ); { const auto user = connection->execute( "SELECT * FROM tao_traits_test" ).as< example::user >(); TEST_ASSERT( user.a == 8 ); TEST_ASSERT( user.b == 9 ); TEST_ASSERT( user.c == 10 ); TEST_ASSERT( user.d == 11 ); } TEST_EXECUTE( connection->execute( "DELETE FROM tao_traits_test" ) ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_traits_test VALUES ( $1, $2, $3, $4 )", example::user2( 9 ) ) ); { const auto user = connection->execute( "SELECT * FROM tao_traits_test" ).as< example::user2 >(); TEST_ASSERT( user.a == 9 ); TEST_ASSERT( user.b == 10 ); TEST_ASSERT( user.c == 11 ); TEST_ASSERT( user.d == 12 ); } TEST_EXECUTE( connection->execute( "DELETE FROM tao_traits_test" ) ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_traits_test VALUES ( $1, $2, $3, $4 )", example::user3( 10 ) ) ); { const auto user = connection->execute( "SELECT * FROM tao_traits_test" ).as< example::user >(); TEST_ASSERT( user.a == 10 ); TEST_ASSERT( user.b == 11 ); TEST_ASSERT( user.c == 12 ); TEST_ASSERT( user.d == 13 ); } } } // namespace auto main() -> int //NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/integration/transaction.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include #include #include #include namespace { template< typename Connection, typename Transaction > void check_nested( const std::shared_ptr< Connection >& connection, const std::shared_ptr< Transaction >& tr ) { TEST_THROWS( connection->direct() ); TEST_THROWS( connection->transaction() ); TEST_EXECUTE( tr->execute( "SELECT 42" ) ); { const auto tr2 = tr->subtransaction(); TEST_THROWS( tr->subtransaction() ); TEST_EXECUTE( tr2->execute( "SELECT 42" ) ); { const auto tr3 = tr2->subtransaction(); TEST_THROWS( tr2->subtransaction() ); TEST_EXECUTE( tr3->execute( "SELECT 42" ) ); TEST_THROWS( tr2->execute( "SELECT 42" ) ); TEST_EXECUTE( tr3->commit() ); TEST_THROWS( tr3->execute( "SELECT 42" ) ); } TEST_THROWS( tr->execute( "SELECT 42" ) ); TEST_EXECUTE( tr2->commit() ); TEST_THROWS( tr2->execute( "SELECT 42" ) ); TEST_THROWS( tr2->subtransaction() ); TEST_EXECUTE( std::ignore = tr->subtransaction() ); } tr->execute( "SELECT 42" ); { const auto tr2 = tr->subtransaction(); TEST_EXECUTE( tr2->execute( "SELECT 42" ) ); TEST_THROWS( tr->execute( "SELECT 42" ) ); TEST_EXECUTE( tr2->rollback() ); TEST_THROWS( tr2->execute( "SELECT 42" ) ); } TEST_EXECUTE( tr->execute( "SELECT 42" ) ); TEST_EXECUTE( tr->commit() ); TEST_THROWS( tr->execute( "SELECT 42" ) ); TEST_EXECUTE( std::ignore = connection->direct() ); TEST_EXECUTE( std::ignore = connection->transaction() ); } void run() { const auto connection = tao::pq::connection::create( tao::pq::internal::getenv( "TAOPQ_TEST_DATABASE", "dbname=template1" ) ); connection->execute( "DROP TABLE IF EXISTS tao_transaction_test" ); connection->execute( "CREATE TABLE tao_transaction_test ( a INTEGER PRIMARY KEY )" ); TEST_ASSERT( connection->execute( "SELECT * FROM tao_transaction_test" ).empty() ); TEST_EXECUTE( connection->execute( "INSERT INTO tao_transaction_test VALUES ( 1 )" ) ); // auto-commit TEST_ASSERT( connection->execute( "SELECT * FROM tao_transaction_test" ).size() == 1 ); TEST_EXECUTE( connection->direct()->execute( "INSERT INTO tao_transaction_test VALUES ( 2 )" ) ); // auto-commit TEST_ASSERT( connection->execute( "SELECT * FROM tao_transaction_test" ).size() == 2 ); TEST_EXECUTE( connection->transaction()->execute( "INSERT INTO tao_transaction_test VALUES ( 3 )" ) ); // not committed TEST_ASSERT( connection->execute( "SELECT * FROM tao_transaction_test" ).size() == 2 ); TEST_EXECUTE( connection->direct()->subtransaction()->execute( "INSERT INTO tao_transaction_test VALUES ( 3 )" ) ); // not committed TEST_ASSERT( connection->execute( "SELECT * FROM tao_transaction_test" ).size() == 2 ); TEST_EXECUTE( connection->transaction()->subtransaction()->execute( "INSERT INTO tao_transaction_test VALUES ( 3 )" ) ); // not committed TEST_ASSERT( connection->execute( "SELECT * FROM tao_transaction_test" ).size() == 2 ); TEST_THROWS( connection->transaction( tao::pq::access_mode::read_only )->execute( "INSERT INTO tao_transaction_test VALUES ( 3 )" ) ); TEST_ASSERT( connection->transaction( tao::pq::access_mode::read_only )->execute( "SELECT * FROM tao_transaction_test" ).size() == 2 ); TEST_THROWS_MESSAGE( "THROWS connection->transaction()", const auto tr = connection->transaction(); std::ignore = connection->transaction() ); TEST_THROWS_MESSAGE( "THROWS connection->direct()", const auto tr = connection->transaction(); std::ignore = connection->direct() ); TEST_THROWS_MESSAGE( "THROWS connection->transaction()", const auto tr = connection->direct(); std::ignore = connection->transaction() ); TEST_THROWS_MESSAGE( "THROWS connection->direct()", const auto tr = connection->direct(); std::ignore = connection->direct() ); TEST_THROWS_MESSAGE( "THROWS tr->subtransaction()", const auto tr = connection->transaction(); const auto st = tr->subtransaction(); std::ignore = tr->subtransaction() ); TEST_THROWS_MESSAGE( "THROWS tr->subtransaction()", const auto tr = connection->direct(); const auto st = tr->subtransaction(); std::ignore = tr->subtransaction() ); TEST_EXECUTE( std::ignore = connection->direct() ); TEST_EXECUTE( connection->direct()->commit() ); TEST_EXECUTE( connection->direct()->rollback() ); TEST_EXECUTE( std::ignore = connection->direct()->subtransaction() ); TEST_EXECUTE( connection->direct()->subtransaction()->commit() ); TEST_EXECUTE( connection->direct()->subtransaction()->rollback() ); TEST_EXECUTE( std::ignore = connection->direct()->subtransaction()->subtransaction() ); TEST_EXECUTE( connection->direct()->subtransaction()->subtransaction()->commit() ); TEST_EXECUTE( connection->direct()->subtransaction()->subtransaction()->rollback() ); TEST_EXECUTE( std::ignore = connection->transaction() ); TEST_EXECUTE( connection->transaction()->commit() ); TEST_EXECUTE( connection->transaction()->rollback() ); TEST_EXECUTE( std::ignore = connection->transaction()->subtransaction() ); TEST_EXECUTE( connection->transaction()->subtransaction()->commit() ); TEST_EXECUTE( connection->transaction()->subtransaction()->rollback() ); TEST_EXECUTE( std::ignore = connection->transaction()->subtransaction()->subtransaction() ); TEST_EXECUTE( connection->transaction()->subtransaction()->subtransaction()->commit() ); TEST_EXECUTE( connection->transaction()->subtransaction()->subtransaction()->rollback() ); TEST_EXECUTE( std::ignore = connection->transaction( tao::pq::isolation_level::serializable ) ); TEST_EXECUTE( std::ignore = connection->transaction( tao::pq::isolation_level::repeatable_read ) ); TEST_EXECUTE( std::ignore = connection->transaction( tao::pq::isolation_level::read_committed ) ); TEST_EXECUTE( std::ignore = connection->transaction( tao::pq::isolation_level::read_uncommitted ) ); TEST_EXECUTE( std::ignore = connection->transaction( tao::pq::access_mode::read_write ) ); TEST_EXECUTE( std::ignore = connection->transaction( tao::pq::access_mode::read_only ) ); TEST_EXECUTE( check_nested( connection, connection->direct() ) ); TEST_EXECUTE( check_nested( connection, connection->transaction() ) ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/unit/getenv.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/getenv.hpp" #include "utils/macros.hpp" #include #include namespace { void run() { TEST_ASSERT( !tao::pq::internal::getenv( "PATH" ).empty() ); TEST_THROWS( tao::pq::internal::getenv( "TAOPQ_DOESNOTEXIST" ) ); TEST_ASSERT( !tao::pq::internal::getenv( "PATH", "" ).empty() ); TEST_ASSERT( tao::pq::internal::getenv( "TAOPQ_DOESNOTEXIST", "" ).empty() ); TEST_ASSERT( tao::pq::internal::getenv( "TAOPQ_DOESNOTEXIST", "DEFAULT VALUE" ) == "DEFAULT VALUE" ); } } // namespace auto main() -> int { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/unit/parameter_type.cpp ================================================ // Copyright (c) 2023-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include #include static_assert( !tao::pq::parameter_type< void > ); static_assert( tao::pq::parameter_type< decltype( tao::pq::null ) > ); static_assert( tao::pq::parameter_type< bool > ); static_assert( tao::pq::parameter_type< char > ); static_assert( tao::pq::parameter_type< signed char > ); static_assert( tao::pq::parameter_type< unsigned char > ); static_assert( tao::pq::parameter_type< short > ); static_assert( tao::pq::parameter_type< unsigned short > ); static_assert( tao::pq::parameter_type< int > ); static_assert( tao::pq::parameter_type< unsigned > ); static_assert( tao::pq::parameter_type< long > ); static_assert( tao::pq::parameter_type< unsigned long > ); static_assert( tao::pq::parameter_type< long long > ); static_assert( tao::pq::parameter_type< unsigned long long > ); static_assert( tao::pq::parameter_type< float > ); static_assert( tao::pq::parameter_type< double > ); static_assert( tao::pq::parameter_type< long double > ); static_assert( tao::pq::parameter_type< const char* > ); static_assert( tao::pq::parameter_type< std::string > ); static_assert( tao::pq::parameter_type< std::string_view > ); static_assert( tao::pq::parameter_type< tao::pq::binary > ); static_assert( tao::pq::parameter_type< tao::pq::binary_view > ); static_assert( tao::pq::parameter_type< std::span< std::byte > > ); static_assert( tao::pq::parameter_type< std::span< std::byte, 42 > > ); static_assert( tao::pq::parameter_type< std::span< const std::byte > > ); static_assert( tao::pq::parameter_type< std::span< const std::byte, 42 > > ); static_assert( tao::pq::parameter_type< std::vector< std::byte > > ); // optional static_assert( tao::pq::parameter_type< std::optional< int > > ); static_assert( tao::pq::parameter_type< std::optional< std::string > > ); // pair static_assert( tao::pq::parameter_type< std::pair< bool, int > > ); static_assert( tao::pq::parameter_type< std::pair< std::string, tao::pq::binary > > ); // tuple static_assert( !tao::pq::parameter_type< std::tuple<> > ); static_assert( tao::pq::parameter_type< std::tuple< int > > ); static_assert( tao::pq::parameter_type< std::tuple< bool, int, float > > ); static_assert( tao::pq::parameter_type< std::tuple< std::string, tao::pq::binary, unsigned > > ); // array static_assert( tao::pq::parameter_type< std::array< int, 42 > > ); static_assert( tao::pq::parameter_type< std::array< std::string, 42 > > ); static_assert( tao::pq::parameter_type< std::list< std::string_view > > ); static_assert( tao::pq::parameter_type< std::set< double > > ); static_assert( tao::pq::parameter_type< std::unordered_set< char > > ); static_assert( !tao::pq::parameter_type< std::set< std::pair< int, double > > > ); static_assert( !tao::pq::parameter_type< std::set< std::tuple<> > > ); static_assert( tao::pq::parameter_type< std::set< std::tuple< int > > > ); static_assert( !tao::pq::parameter_type< std::set< std::tuple< bool, int, double > > > ); // note: vector except for T == std::byte are registered as arrays by default static_assert( tao::pq::parameter_type< std::vector< bool > > ); static_assert( tao::pq::parameter_type< std::vector< unsigned long long > > ); static_assert( tao::pq::parameter_type< std::vector< std::set< double > > > ); static_assert( tao::pq::parameter_type< std::set< std::vector< double > > > ); // aggregate namespace example { struct user { std::string name; int age; std::string planet; }; struct user2 { std::string name; int age; std::string planet; }; } // namespace example template<> inline constexpr bool tao::pq::is_aggregate< example::user > = true; static_assert( tao::pq::parameter_type< example::user > ); static_assert( !tao::pq::parameter_type< example::user2 > ); // not registered auto main() -> int { return 0; } ================================================ FILE: test/unit/resize_uninitialized.cpp ================================================ // Copyright (c) 2021-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include "utils/macros.hpp" #include #include #include #include #include #include namespace { void test( std::string& s, const std::size_t size ) { tao::pq::internal::resize_uninitialized( s, size ); TEST_ASSERT( s.size() == size ); TEST_ASSERT( s[ size ] == '\0' ); } void run() { std::string s = "hello"; test( s, 2 ); TEST_ASSERT( s[ 0 ] == 'h' ); TEST_ASSERT( s[ 1 ] == 'e' ); test( s, 5 ); TEST_ASSERT( s[ 0 ] == 'h' ); TEST_ASSERT( s[ 1 ] == 'e' ); test( s, 32 ); TEST_ASSERT( s[ 0 ] == 'h' ); TEST_ASSERT( s[ 1 ] == 'e' ); test( s, 1000000000 ); TEST_ASSERT( s[ 0 ] == 'h' ); TEST_ASSERT( s[ 1 ] == 'e' ); test( s, 2 ); TEST_ASSERT( s[ 0 ] == 'h' ); TEST_ASSERT( s[ 1 ] == 'e' ); std::vector< std::byte > v = { static_cast< std::byte >( 42 ), static_cast< std::byte >( 69 ) }; TEST_ASSERT( v.size() == 2 ); TEST_ASSERT( v[ 0 ] == static_cast< std::byte >( 42 ) ); TEST_ASSERT( v[ 1 ] == static_cast< std::byte >( 69 ) ); tao::pq::internal::resize_uninitialized( v, 5 ); TEST_ASSERT( v.size() == 5 ); TEST_ASSERT( v[ 0 ] == static_cast< std::byte >( 42 ) ); TEST_ASSERT( v[ 1 ] == static_cast< std::byte >( 69 ) ); tao::pq::internal::resize_uninitialized( v, 1000000000 ); TEST_ASSERT( v.size() == 1000000000 ); TEST_ASSERT( v[ 0 ] == static_cast< std::byte >( 42 ) ); TEST_ASSERT( v[ 1 ] == static_cast< std::byte >( 69 ) ); tao::pq::internal::resize_uninitialized( v, 2 ); TEST_ASSERT( v.size() == 2 ); TEST_ASSERT( v[ 0 ] == static_cast< std::byte >( 42 ) ); TEST_ASSERT( v[ 1 ] == static_cast< std::byte >( 69 ) ); } } // namespace auto main() -> int { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } ================================================ FILE: test/unit/result_type.cpp ================================================ // Copyright (c) 2023-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include #include static_assert( !tao::pq::result_type< void > ); static_assert( tao::pq::result_type< bool > ); static_assert( tao::pq::result_type< char > ); static_assert( tao::pq::result_type< signed char > ); static_assert( tao::pq::result_type< unsigned char > ); static_assert( tao::pq::result_type< short > ); static_assert( tao::pq::result_type< unsigned short > ); static_assert( tao::pq::result_type< int > ); static_assert( tao::pq::result_type< unsigned > ); static_assert( tao::pq::result_type< long > ); static_assert( tao::pq::result_type< unsigned long > ); static_assert( tao::pq::result_type< long long > ); static_assert( tao::pq::result_type< unsigned long long > ); static_assert( tao::pq::result_type< float > ); static_assert( tao::pq::result_type< double > ); static_assert( tao::pq::result_type< long double > ); static_assert( tao::pq::result_type< const char* > ); static_assert( tao::pq::result_type< std::string > ); static_assert( tao::pq::result_type< std::string_view > ); static_assert( tao::pq::result_type< tao::pq::binary > ); static_assert( !tao::pq::result_type< tao::pq::binary_view > ); static_assert( !tao::pq::result_type< std::span< std::byte > > ); static_assert( !tao::pq::result_type< std::span< std::byte, 42 > > ); static_assert( !tao::pq::result_type< std::span< const std::byte > > ); static_assert( !tao::pq::result_type< std::span< const std::byte, 42 > > ); static_assert( tao::pq::result_type< std::vector< std::byte > > ); // optional static_assert( tao::pq::result_type< std::optional< int > > ); static_assert( tao::pq::result_type< std::optional< std::string > > ); // pair static_assert( tao::pq::result_type< std::pair< bool, int > > ); static_assert( tao::pq::result_type< std::pair< std::string, tao::pq::binary > > ); // tuple static_assert( !tao::pq::result_type< std::tuple<> > ); static_assert( tao::pq::result_type< std::tuple< int > > ); static_assert( tao::pq::result_type< std::tuple< bool, int, float > > ); static_assert( tao::pq::result_type< std::tuple< std::string, tao::pq::binary, unsigned > > ); // array static_assert( !tao::pq::result_type< std::array< int, 42 > > ); static_assert( !tao::pq::result_type< std::array< std::string, 42 > > ); static_assert( tao::pq::result_type< std::list< std::string_view > > ); static_assert( tao::pq::result_type< std::set< double > > ); static_assert( tao::pq::result_type< std::unordered_set< char > > ); static_assert( !tao::pq::result_type< std::set< std::pair< int, double > > > ); static_assert( !tao::pq::result_type< std::set< std::tuple<> > > ); static_assert( tao::pq::result_type< std::set< std::tuple< int > > > ); static_assert( !tao::pq::result_type< std::set< std::tuple< bool, int, double > > > ); // note: vector except for T == std::byte are registered as arrays by default static_assert( tao::pq::result_type< std::vector< bool > > ); static_assert( tao::pq::result_type< std::vector< unsigned long long > > ); static_assert( tao::pq::result_type< std::vector< std::set< double > > > ); static_assert( tao::pq::result_type< std::set< std::vector< double > > > ); // aggregate namespace example { struct user { std::string name; int age; std::string planet; }; struct user2 { std::string name; int age; std::string planet; }; } // namespace example template<> inline constexpr bool tao::pq::is_aggregate< example::user > = true; static_assert( tao::pq::result_type< example::user > ); static_assert( !tao::pq::result_type< example::user2 > ); // not registered auto main() -> int { return 0; } ================================================ FILE: test/unit/strtox.cpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #if defined( _WIN32 ) auto main() -> int {} #else #include "utils/macros.hpp" #include #include #include #include #include #include #include #include namespace { template< typename T > void reject_floating_point( const char* input ) { try { std::ignore = tao::pq::internal::strtof( input ); throw std::runtime_error( std::format( "strtof(): {}", input ) ); // LCOV_EXCL_LINE } catch( const T& e ) { if( e.what() != "tao::pq::internal::strtof() failed for input: " + std::string( input ) ) { throw; // LCOV_EXCL_LINE } } try { std::ignore = tao::pq::internal::strtod( input ); throw std::runtime_error( std::format( "strtod(): {}", input ) ); // LCOV_EXCL_LINE } catch( const T& e ) { if( e.what() != "tao::pq::internal::strtod() failed for input: " + std::string( input ) ) { throw; // LCOV_EXCL_LINE } } try { std::ignore = tao::pq::internal::strtold( input ); throw std::runtime_error( std::format( "strtold(): {}", input ) ); // LCOV_EXCL_LINE } catch( const T& e ) { if( e.what() != "tao::pq::internal::strtold() failed for input: " + std::string( input ) ) { throw; // LCOV_EXCL_LINE } } } void run() { TEST_ASSERT( tao::pq::internal::strtof( "0" ) == 0 ); TEST_ASSERT( tao::pq::internal::strtof( "1" ) == 1 ); TEST_ASSERT( tao::pq::internal::strtof( "00" ) == 0 ); TEST_ASSERT( tao::pq::internal::strtof( "01" ) == 1 ); TEST_ASSERT( tao::pq::internal::strtof( "0." ) == 0 ); TEST_ASSERT( tao::pq::internal::strtof( "1." ) == 1 ); TEST_ASSERT( tao::pq::internal::strtof( "0.0" ) == 0 ); TEST_ASSERT( tao::pq::internal::strtof( "1.0" ) == 1 ); TEST_ASSERT( tao::pq::internal::strtof( "0.5" ) == .5 ); TEST_ASSERT( tao::pq::internal::strtof( ".5" ) == .5 ); TEST_ASSERT( tao::pq::internal::strtof( ".25" ) == .25 ); TEST_ASSERT( tao::pq::internal::strtof( ".125" ) == .125 ); TEST_ASSERT( tao::pq::internal::strtof( ".0625" ) == .0625 ); TEST_ASSERT( tao::pq::internal::strtof( ".4375" ) == .4375 ); TEST_ASSERT( tao::pq::internal::strtof( "-0" ) == 0 ); TEST_ASSERT( tao::pq::internal::strtof( "-1" ) == -1 ); TEST_ASSERT( tao::pq::internal::strtof( "-00" ) == 0 ); TEST_ASSERT( tao::pq::internal::strtof( "-01" ) == -1 ); TEST_ASSERT( tao::pq::internal::strtof( "-0." ) == 0 ); TEST_ASSERT( tao::pq::internal::strtof( "-1." ) == -1 ); TEST_ASSERT( tao::pq::internal::strtof( "-0.0" ) == 0 ); TEST_ASSERT( tao::pq::internal::strtof( "-1.0" ) == -1 ); TEST_ASSERT( tao::pq::internal::strtof( "-0.5" ) == -.5 ); TEST_ASSERT( tao::pq::internal::strtof( "-.5" ) == -.5 ); TEST_ASSERT( tao::pq::internal::strtof( "-.25" ) == -.25 ); TEST_ASSERT( tao::pq::internal::strtof( "-.125" ) == -.125 ); TEST_ASSERT( tao::pq::internal::strtof( "-.0625" ) == -.0625 ); TEST_ASSERT( tao::pq::internal::strtof( "-.4375" ) == -.4375 ); TEST_ASSERT( tao::pq::internal::strtof( "3.1415927410125732421875" ) == 3.1415927410125732421875 ); TEST_ASSERT( tao::pq::internal::strtod( "3.1415927410125732421875" ) == 3.1415927410125732421875 ); TEST_ASSERT( tao::pq::internal::strtold( "3.1415927410125732421875" ) == 3.1415927410125732421875 ); TEST_ASSERT( tao::pq::internal::strtof( "0000000000000000000000000000000000000.0000000000000000000000000000000000000" ) == 0 ); TEST_ASSERT( tao::pq::internal::strtof( "0000000000000000000000000000000000001.0000000000000000000000000000000000000" ) == 1 ); TEST_ASSERT( std::isinf( tao::pq::internal::strtof( "inf" ) ) ); TEST_ASSERT( std::isinf( tao::pq::internal::strtof( "INF" ) ) ); TEST_ASSERT( std::isinf( tao::pq::internal::strtof( "infinity" ) ) ); TEST_ASSERT( std::isinf( tao::pq::internal::strtof( "INFINITY" ) ) ); TEST_ASSERT( std::isinf( tao::pq::internal::strtof( "-inf" ) ) ); TEST_ASSERT( std::isinf( tao::pq::internal::strtof( "-INF" ) ) ); TEST_ASSERT( std::isinf( tao::pq::internal::strtof( "-infinity" ) ) ); TEST_ASSERT( std::isinf( tao::pq::internal::strtof( "-INFINITY" ) ) ); TEST_ASSERT( std::isnan( tao::pq::internal::strtof( "nan" ) ) ); TEST_ASSERT( std::isnan( tao::pq::internal::strtof( "NaN" ) ) ); TEST_ASSERT( std::isnan( tao::pq::internal::strtof( "NAN" ) ) ); TEST_ASSERT( tao::pq::internal::strtof( "inf" ) > 0 ); TEST_ASSERT( tao::pq::internal::strtof( "-inf" ) < 0 ); reject_floating_point< std::runtime_error >( "" ); reject_floating_point< std::runtime_error >( " " ); reject_floating_point< std::runtime_error >( "+" ); reject_floating_point< std::runtime_error >( "-" ); reject_floating_point< std::runtime_error >( " 0" ); reject_floating_point< std::runtime_error >( "0 " ); reject_floating_point< std::runtime_error >( "0x" ); reject_floating_point< std::runtime_error >( " 1" ); reject_floating_point< std::runtime_error >( "1 " ); reject_floating_point< std::overflow_error >( "1e10000" ); reject_floating_point< std::overflow_error >( "-1e10000" ); reject_floating_point< std::underflow_error >( "1e-10000" ); reject_floating_point< std::underflow_error >( "-1e-10000" ); } } // namespace auto main() -> int // NOLINT(bugprone-exception-escape) { try { run(); } // LCOV_EXCL_START catch( const std::exception& e ) { std::cerr << "exception: " << e.what() << '\n'; throw; } catch( ... ) { std::cerr << "unknown exception\n"; throw; } // LCOV_EXCL_STOP } #endif ================================================ FILE: test/utils/compare.hpp ================================================ // Copyright (c) 2024-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef SRC_TEST_COMPARE_HPP // NOLINT(llvm-header-guard) #define SRC_TEST_COMPARE_HPP // This is an internal header used for unit-tests. #include namespace tao::pq::internal { template< typename T, typename U > [[nodiscard]] auto compare( const T& lhs, const U& rhs ) noexcept -> bool { if( lhs.size() != rhs.size() ) { return false; } for( std::size_t i = 0; i != lhs.size(); ++i ) { if( lhs[ i ] != rhs[ i ] ) { return false; } } return true; } } // namespace tao::pq::internal #endif ================================================ FILE: test/utils/getenv.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef SRC_TEST_GETENV_HPP // NOLINT(llvm-header-guard) #define SRC_TEST_GETENV_HPP // This is an internal header used for unit-tests. #include #include #include #include #if defined( _MSC_VER ) #include #endif namespace tao::pq::internal { #if defined( _MSC_VER ) [[nodiscard]] inline auto getenv( const std::string& name ) -> std::string { char* buf = nullptr; std::size_t sz = 0; if( _dupenv_s( &buf, &sz, name.c_str() ) == 0 && buf != nullptr ) { const std::unique_ptr< char, decltype( &std::free ) > up( buf, &std::free ); return std::string( up.get(), sz ); } throw std::runtime_error( std::format( "environment variable not found: {}", name ) ); } [[nodiscard]] inline auto getenv( const std::string& name, const std::string& default_value ) -> std::string { char* buf = nullptr; std::size_t sz = 0; if( _dupenv_s( &buf, &sz, name.c_str() ) == 0 && buf != nullptr ) { const std::unique_ptr< char, decltype( &std::free ) > up( buf, &std::free ); return std::string( up.get(), sz ); } return default_value; } #else [[nodiscard]] inline auto getenv( const std::string& name ) -> std::string { const char* result = std::getenv( name.c_str() ); return ( result != nullptr ) ? result : throw std::runtime_error( std::format( "environment variable not found: {}", name ) ); } [[nodiscard]] inline auto getenv( const std::string& name, const std::string& default_value ) -> std::string { const char* result = std::getenv( name.c_str() ); return ( result != nullptr ) ? result : default_value; } #endif } // namespace tao::pq::internal #endif ================================================ FILE: test/utils/macros.hpp ================================================ // Copyright (c) 2016-2026 Daniel Frey and Dr. Colin Hirsch // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) #ifndef SRC_TEST_MACROS_HPP // NOLINT(llvm-header-guard) #define SRC_TEST_MACROS_HPP // This is an internal header used for unit-tests. #include #include #include #include #define STRINGIFY_INTERNAL( ... ) #__VA_ARGS__ #define STRINGIFY( ... ) STRINGIFY_INTERNAL( __VA_ARGS__ ) #define FILE_AND_LINE __FILE__ ":" STRINGIFY( __LINE__ ) #define TEST_EXECUTE_MESSAGE( MeSSaGe, ... ) \ do { \ std::cout << "TEST [ " MeSSaGe << " ] in [ " FILE_AND_LINE " ]\n"; \ __VA_ARGS__; \ } while( false ) #define TEST_FAILED \ do { \ std::cerr << "TEST FAILED in [ " FILE_AND_LINE " ]\n"; \ std::exit( 1 ); \ } while( false ) #define TEST_ASSERT_MESSAGE( MeSSaGe, ... ) \ TEST_EXECUTE_MESSAGE( \ MeSSaGe, \ if( !static_cast< bool >( __VA_ARGS__ ) ) { \ TEST_FAILED; \ } ) #define TEST_THROWS_MESSAGE( MeSSaGe, ... ) \ TEST_EXECUTE_MESSAGE( \ MeSSaGe, \ try { \ __VA_ARGS__; \ TEST_FAILED; \ } catch( const tao::pq::sql_error& e ) { \ std::cout << "TEST caught [ " << tao::pq::internal::demangle( typeid( e ) ) << " ] " \ << "with SQLSTATE [ " << e.sqlstate << " ] " \ << "and [ " << e.what() << " ] in [ " FILE_AND_LINE " ]\n"; \ } catch( const std::exception& e ) { \ std::cout << "TEST caught [ " << tao::pq::internal::demangle( typeid( e ) ) << " ] " \ << "with [ " << e.what() << " ] in [ " FILE_AND_LINE " ]\n"; \ } catch( ... ) { \ std::cout << "TEST caught unknown exception in [ " FILE_AND_LINE " ]\n"; \ } ) #define TEST_EXECUTE( ... ) TEST_EXECUTE_MESSAGE( "EXECUTE " #__VA_ARGS__, __VA_ARGS__ ) #define TEST_ASSERT( ... ) TEST_ASSERT_MESSAGE( "ASSERT " #__VA_ARGS__, __VA_ARGS__ ) #define TEST_THROWS( ... ) TEST_THROWS_MESSAGE( "THROWS " #__VA_ARGS__, (void)__VA_ARGS__ ) #endif