summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--subprojects/boost-sqlite/.drone.star34
-rwxr-xr-xsubprojects/boost-sqlite/.drone/drone.bat35
-rwxr-xr-xsubprojects/boost-sqlite/.drone/drone.sh211
-rw-r--r--subprojects/boost-sqlite/CMakeLists.txt112
-rw-r--r--subprojects/boost-sqlite/build/Jamfile36
-rw-r--r--subprojects/boost-sqlite/doc/Jamfile18
-rw-r--r--subprojects/boost-sqlite/doc/extensions.adoc41
-rw-r--r--subprojects/boost-sqlite/doc/functions.adoc88
-rw-r--r--subprojects/boost-sqlite/doc/index.adoc28
-rw-r--r--subprojects/boost-sqlite/doc/reference.adoc28
-rw-r--r--subprojects/boost-sqlite/doc/reference/allocator.adoc31
-rw-r--r--subprojects/boost-sqlite/doc/reference/backup.adoc43
-rw-r--r--subprojects/boost-sqlite/doc/reference/blob.adoc141
-rw-r--r--subprojects/boost-sqlite/doc/reference/collation.adoc45
-rw-r--r--subprojects/boost-sqlite/doc/reference/connection.adoc100
-rw-r--r--subprojects/boost-sqlite/doc/reference/cstring_ref.adoc16
-rw-r--r--subprojects/boost-sqlite/doc/reference/error.adoc91
-rw-r--r--subprojects/boost-sqlite/doc/reference/extension.adoc36
-rw-r--r--subprojects/boost-sqlite/doc/reference/field.adoc42
-rw-r--r--subprojects/boost-sqlite/doc/reference/function.adoc263
-rw-r--r--subprojects/boost-sqlite/doc/reference/hooks.adoc105
-rw-r--r--subprojects/boost-sqlite/doc/reference/json.adoc28
-rw-r--r--subprojects/boost-sqlite/doc/reference/memory.adoc29
-rw-r--r--subprojects/boost-sqlite/doc/reference/meta_data.adoc36
-rw-r--r--subprojects/boost-sqlite/doc/reference/mutex.adoc16
-rw-r--r--subprojects/boost-sqlite/doc/reference/result.adoc40
-rw-r--r--subprojects/boost-sqlite/doc/reference/resultset.adoc72
-rw-r--r--subprojects/boost-sqlite/doc/reference/row.adoc56
-rw-r--r--subprojects/boost-sqlite/doc/reference/statement.adoc165
-rw-r--r--subprojects/boost-sqlite/doc/reference/static_resultset.adoc98
-rw-r--r--subprojects/boost-sqlite/doc/reference/string.adoc18
-rw-r--r--subprojects/boost-sqlite/doc/reference/transaction.adoc104
-rw-r--r--subprojects/boost-sqlite/doc/reference/value.adoc85
-rw-r--r--subprojects/boost-sqlite/doc/reference/vtable.adoc275
-rw-r--r--subprojects/boost-sqlite/doc/tutorial.adoc139
-rw-r--r--subprojects/boost-sqlite/doc/vtable.adoc167
-rw-r--r--subprojects/boost-sqlite/example/CMakeLists.txt20
-rw-r--r--subprojects/boost-sqlite/example/csv.cpp273
-rw-r--r--subprojects/boost-sqlite/example/describe.cpp202
-rw-r--r--subprojects/boost-sqlite/example/multi_index.cpp433
-rw-r--r--subprojects/boost-sqlite/example/ordered_map.cpp304
-rw-r--r--subprojects/boost-sqlite/example/tutorial.cpp136
-rw-r--r--subprojects/boost-sqlite/example/tutorial_ec.cpp197
-rw-r--r--subprojects/boost-sqlite/example/url.cpp315
-rw-r--r--subprojects/boost-sqlite/example/url.sql19
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite.hpp38
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/allocator.hpp48
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/backup.hpp74
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/blob.hpp156
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/collation.hpp140
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/connection.hpp210
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/cstring_ref.hpp223
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/detail/aggregate_function.hpp166
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/detail/catch.hpp169
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/detail/config.hpp83
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/detail/exception.hpp48
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/detail/scalar_function.hpp172
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/detail/vtable.hpp611
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/detail/window_function.hpp238
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/error.hpp178
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/extension.hpp67
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/field.hpp85
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/function.hpp452
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/hooks.hpp370
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/json.hpp140
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/memory.hpp105
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/meta_data.hpp58
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/mutex.hpp55
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/result.hpp147
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/resultset.hpp150
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/row.hpp167
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/statement.hpp651
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/static_resultset.hpp497
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/string.hpp49
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/transaction.hpp198
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/value.hpp130
-rw-r--r--subprojects/boost-sqlite/include/boost/sqlite/vtable.hpp615
-rw-r--r--subprojects/boost-sqlite/readme.md277
-rw-r--r--subprojects/boost-sqlite/src/backup.cpp61
-rw-r--r--subprojects/boost-sqlite/src/blob.cpp94
-rw-r--r--subprojects/boost-sqlite/src/connection.cpp158
-rw-r--r--subprojects/boost-sqlite/src/detail/exception.cpp46
-rw-r--r--subprojects/boost-sqlite/src/error.cpp83
-rw-r--r--subprojects/boost-sqlite/src/ext.cpp14
-rw-r--r--subprojects/boost-sqlite/src/field.cpp39
-rw-r--r--subprojects/boost-sqlite/src/meta_data.cpp83
-rw-r--r--subprojects/boost-sqlite/src/resultset.cpp61
-rw-r--r--subprojects/boost-sqlite/src/row.cpp24
-rw-r--r--subprojects/boost-sqlite/src/value.cpp39
-rw-r--r--subprojects/boost-sqlite/test/CMakeLists.txt11
-rw-r--r--subprojects/boost-sqlite/test/Jamfile25
-rw-r--r--subprojects/boost-sqlite/test/allocator.cpp23
-rw-r--r--subprojects/boost-sqlite/test/backup.cpp46
-rw-r--r--subprojects/boost-sqlite/test/blob.cpp56
-rw-r--r--subprojects/boost-sqlite/test/catch.cpp18
-rw-r--r--subprojects/boost-sqlite/test/collation.cpp45
-rw-r--r--subprojects/boost-sqlite/test/connection.cpp32
-rw-r--r--subprojects/boost-sqlite/test/extension/CMakeLists.txt19
-rw-r--r--subprojects/boost-sqlite/test/extension/simple_scalar.cpp27
-rw-r--r--subprojects/boost-sqlite/test/extension/simple_scalar.sql4
-rw-r--r--subprojects/boost-sqlite/test/field.cpp46
-rw-r--r--subprojects/boost-sqlite/test/function.cpp306
-rw-r--r--subprojects/boost-sqlite/test/hooks.cpp52
-rw-r--r--subprojects/boost-sqlite/test/json.cpp104
-rw-r--r--subprojects/boost-sqlite/test/main_test.cpp9
-rw-r--r--subprojects/boost-sqlite/test/meta_data.cpp45
-rw-r--r--subprojects/boost-sqlite/test/mutex.cpp31
-rw-r--r--subprojects/boost-sqlite/test/statement.cpp74
-rw-r--r--subprojects/boost-sqlite/test/static_resultset.cpp95
-rw-r--r--subprojects/boost-sqlite/test/test-db.sql29
-rw-r--r--subprojects/boost-sqlite/test/test.hpp22
-rw-r--r--subprojects/boost-sqlite/test/transaction.cpp89
-rw-r--r--subprojects/boost-sqlite/test/vtable.cpp256
-rwxr-xr-xsubprojects/boost-sqlite/tools/get-boost.sh50
-rw-r--r--subprojects/boost-sqlite/tools/user-config.jam0
115 files changed, 13754 insertions, 0 deletions
diff --git a/subprojects/boost-sqlite/.drone.star b/subprojects/boost-sqlite/.drone.star
new file mode 100644
index 0000000..174896e
--- /dev/null
+++ b/subprojects/boost-sqlite/.drone.star
@@ -0,0 +1,34 @@
+# Use, modification, and distribution are
+# subject to the Boost Software License, Version 1.0. (See accompanying
+# file LICENSE.txt)
+#
+# Copyright Rene Rivera 2020.
+
+# For Drone CI we use the Starlark scripting language to reduce duplication.
+# As the yaml syntax for Drone CI is rather limited.
+#
+#
+globalenv={'B2_CI_VERSION': '1', 'B2_VARIANT': 'release'}
+linuxglobalimage="cppalliance/droneubuntu1804:1"
+windowsglobalimage="cppalliance/dronevs2019"
+
+def main(ctx):
+ return [
+ linux_cxx("docs", "", packages="docbook docbook-xml docbook-xsl xsltproc libsaxonhe-java default-jre-headless flex libfl-dev bison unzip rsync mlocate", image="cppalliance/droneubuntu1804:1", buildtype="docs", buildscript="drone", environment={ "COMMENT": "docs"}, globalenv=globalenv),
+# linux_cxx("asan", "g++-8", packages="g++-8 sqlite3 libsqlite3-dev", buildtype="boost", buildscript="drone", image=linuxglobalimage, environment={ 'COMMENT': 'asan', 'B2_VARIANT': 'debug', 'B2_TOOLSET': 'gcc-8', 'B2_CXXSTD': '14', 'B2_ASAN': '1', 'B2_DEFINES': 'BOOST_NO_STRESS_TEST=1', 'DRONE_EXTRA_PRIVILEGED': 'True', 'DRONE_JOB_UUID': '356a192b79'}, globalenv=globalenv, privileged=True),
+ linux_cxx("ubsan", "g++-8", packages="g++-8 sqlite3 libsqlite3-dev", buildtype="boost", buildscript="drone", image=linuxglobalimage, environment={ 'COMMENT': 'ubsan', 'B2_VARIANT': 'debug', 'B2_TOOLSET': 'gcc-8', 'B2_CXXSTD': '14', 'B2_UBSAN': '1', 'B2_DEFINES': 'BOOST_NO_STRESS_TEST=1', 'B2_LINKFLAGS': '-fuse-ld=gold', 'DRONE_JOB_UUID': '77de68daec'}, globalenv=globalenv),
+ linux_cxx("gcc 11 arm64", "g++-11", packages="g++-11 sqlite3 libsqlite3-dev", buildtype="boost", buildscript="drone", image="cppalliance/droneubuntu2004:multiarch", environment={ 'B2_TOOLSET': 'gcc-11', 'B2_CXXSTD': '14', 'DRONE_JOB_UUID': '17ba079169m'}, arch="arm64", globalenv=globalenv),
+# linux_cxx("GCC 10, Debug + Coverage", "g++-10", packages="g++-10 sqlite3 libsqlite3-dev libffi-dev binutils-gold gdb mlocate", image="cppalliance/droneubuntu2004:1", buildtype="boost", buildscript="drone", environment={ "GCOV": "gcov-10", "LCOV_VERSION": "1.15", "VARIANT": "process_coverage", "TOOLSET": "gcc", "COMPILER": "g++-10", "CXXSTD": "14", "DRONE_BEFORE_INSTALL" : "process_coverage", "CODECOV_TOKEN": {"from_secret": "codecov_token"}}, globalenv=globalenv, privileged=True),
+ # A set of jobs based on the earlier .travis.yml configuration:
+ linux_cxx("Default clang++ with libc++", "clang++-libc++", packages="libc++-dev sqlite3 libsqlite3-dev mlocate", image="cppalliance/droneubuntu1604:1", buildtype="buildtype", buildscript="drone", environment={ "B2_TOOLSET": "clang-7", "B2_CXXSTD": "14", "VARIANT": "debug", "TOOLSET": "clang", "COMPILER": "clang++-libc++", "CXXSTD": "14", "CXX_FLAGS": "<cxxflags>-stdlib=libc++ <linkflags>-stdlib=libc++", "TRAVISCLANG" : "yes" }, globalenv=globalenv),
+ linux_cxx("Default g++", "g++", packages="mlocate sqlite3 libsqlite3-dev", image="cppalliance/droneubuntu1604:1", buildtype="buildtype", buildscript="drone", environment={ "VARIANT": "release", "TOOLSET": "gcc", "COMPILER": "g++", "CXXSTD": "14" }, globalenv=globalenv),
+ linux_cxx("Clang 3.8, UBasan", "clang++-3.8", packages="clang-3.8 sqlite3 libsqlite3-dev mlocate", llvm_os="precise", llvm_ver="3.8", image="cppalliance/droneubuntu1604:1", buildtype="boost", buildscript="drone", environment={ "VARIANT": "process_ubasan", "TOOLSET": "clang", "COMPILER": "clang++-3.8", "CXXSTD": "14", "UBSAN_OPTIONS": 'print_stacktrace=1', "DRONE_BEFORE_INSTALL": "UBasan" }, globalenv=globalenv),
+ linux_cxx("gcc 6", "g++-6", packages="g++-6 sqlite3 libsqlite3-dev", buildtype="boost", buildscript="drone", image=linuxglobalimage, environment={ 'B2_TOOLSET': 'gcc-6', 'B2_CXXSTD': '14', 'DRONE_JOB_UUID': '902ba3cda1'}, globalenv=globalenv),
+ linux_cxx("clang 3.8", "clang++-3.8", packages="clang-3.8 sqlite3 libsqlite3-dev", buildtype="boost", buildscript="drone", image="cppalliance/droneubuntu1604:1", environment={ 'B2_TOOLSET': 'clang', 'COMPILER': 'clang++-3.8', 'B2_CXXSTD': '14', 'DRONE_JOB_UUID': '7b52009b64'}, globalenv=globalenv),
+ osx_cxx("clang", "g++", packages="", buildtype="boost", buildscript="drone", environment={ 'B2_TOOLSET': 'clang', 'B2_CXXSTD': '14,17', 'DRONE_JOB_UUID': '91032ad7bb'}, globalenv=globalenv),
+ linux_cxx("coverity", "g++", packages="", buildtype="coverity", buildscript="drone", image=linuxglobalimage, environment={ 'COMMENT': 'Coverity Scan', 'B2_TOOLSET': 'clang', 'DRONE_JOB_UUID': '472b07b9fc'}, globalenv=globalenv),
+ windows_cxx("msvc-14.3", "", image="cppalliance/dronevs2022:1", buildtype="boost", buildscript="drone", environment={ "VARIANT": "release", "TOOLSET": "msvc-14.3", "CXXSTD": "17", "ADDRESS_MODEL": "64"}),
+ ]
+
+# from https://github.com/boostorg/boost-ci
+load("@boost_ci//ci/drone/:functions.star", "linux_cxx","windows_cxx","osx_cxx","freebsd_cxx")
diff --git a/subprojects/boost-sqlite/.drone/drone.bat b/subprojects/boost-sqlite/.drone/drone.bat
new file mode 100755
index 0000000..8aea39c
--- /dev/null
+++ b/subprojects/boost-sqlite/.drone/drone.bat
@@ -0,0 +1,35 @@
+
+@ECHO ON
+setlocal enabledelayedexpansion
+
+if "%DRONE_JOB_BUILDTYPE%" == "boost" (
+
+echo "============> INSTALL"
+
+SET DRONE_BUILD_DIR=%CD: =%
+choco install --no-progress -y sqlite --x64
+SET BOOST_BRANCH=develop
+IF "%DRONE_BRANCH%" == "master" SET BOOST_BRANCH=master
+cp tools\user-config.jam %USERPROFILE%\user-config.jam
+cd ..
+SET GET_BOOST=!DRONE_BUILD_DIR!\tools\get-boost.sh
+bash -c "$GET_BOOST $DRONE_BRANCH $DRONE_BUILD_DIR"
+cd boost-root
+call bootstrap.bat
+b2 headers
+
+echo "============> SCRIPT"
+
+IF DEFINED DEFINE SET B2_DEFINE="define=%DEFINE%"
+
+echo "Running tests"
+b2 --debug-configuration variant=%VARIANT% cxxstd=%CXXSTD% %B2_DEFINE% address-model=%ADDRESS_MODEL% toolset=%TOOLSET% --verbose-test libs/sqlite/test -j3
+if !errorlevel! neq 0 exit /b !errorlevel!
+
+echo "Running libs/sqlite/example"
+b2 --debug-configuration variant=%VARIANT% cxxstd=%CXXSTD% %B2_DEFINE% address-model=%ADDRESS_MODEL% toolset=%TOOLSET% libs/sqlite/example -j3
+if !errorlevel! neq 0 exit /b !errorlevel!
+
+echo "============> COMPLETED"
+
+) \ No newline at end of file
diff --git a/subprojects/boost-sqlite/.drone/drone.sh b/subprojects/boost-sqlite/.drone/drone.sh
new file mode 100755
index 0000000..c97a210
--- /dev/null
+++ b/subprojects/boost-sqlite/.drone/drone.sh
@@ -0,0 +1,211 @@
+#!/bin/bash
+
+# Copyright 2020 Rene Rivera, Sam Darwin
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE.txt or copy at http://boost.org/LICENSE_1_0.txt)
+
+set -xe
+
+export TRAVIS_BUILD_DIR=$(pwd)
+export DRONE_BUILD_DIR=$(pwd)
+export TRAVIS_BRANCH=$DRONE_BRANCH
+export TRAVIS_EVENT_TYPE=$DRONE_BUILD_EVENT
+export VCS_COMMIT_ID=$DRONE_COMMIT
+export GIT_COMMIT=$DRONE_COMMIT
+export REPO_NAME=$DRONE_REPO
+export USER=$(whoami)
+export CC=${CC:-gcc}
+export PATH=~/.local/bin:/usr/local/bin:$PATH
+
+common_install () {
+ git clone https://github.com/boostorg/boost-ci.git boost-ci-cloned --depth 1
+ cp -prf boost-ci-cloned/ci .
+ rm -rf boost-ci-cloned
+
+ if [ "$TRAVIS_OS_NAME" == "osx" ]; then
+ unset -f cd
+ echo "macos - set up homebrew sqlite3"
+
+ cat > ~/user-config.jam <<EOF
+import os ;
+project
+ : requirements
+
+ ;
+EOF
+ fi
+
+ export SELF=`basename $REPO_NAME`
+ export BOOST_CI_TARGET_BRANCH="$TRAVIS_BRANCH"
+ export BOOST_CI_SRC_FOLDER=$(pwd)
+
+ . ./ci/common_install.sh
+}
+
+if [ "$DRONE_JOB_BUILDTYPE" == "boost" ]; then
+
+echo '==================================> INSTALL'
+
+common_install
+
+echo '==================================> SCRIPT'
+
+$BOOST_ROOT/libs/$SELF/ci/travis/build.sh
+
+elif [ "$DRONE_JOB_BUILDTYPE" == "docs" ]; then
+
+echo '==================================> INSTALL'
+
+export SELF=`basename $REPO_NAME`
+
+pwd
+cd ..
+mkdir -p $HOME/cache && cd $HOME/cache
+if [ ! -d doxygen ]; then git clone -b 'Release_1_8_15' --depth 1 https://github.com/doxygen/doxygen.git && echo "not-cached" ; else echo "cached" ; fi
+cd doxygen
+cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release
+cd build
+sudo make install
+cd ../..
+if [ ! -f saxonhe.zip ]; then wget -O saxonhe.zip https://sourceforge.net/projects/saxon/files/Saxon-HE/9.9/SaxonHE9-9-1-4J.zip/download && echo "not-cached" ; else echo "cached" ; fi
+unzip -o saxonhe.zip
+sudo rm /usr/share/java/Saxon-HE.jar
+sudo cp saxon9he.jar /usr/share/java/Saxon-HE.jar
+cd ..
+BOOST_BRANCH=develop && [ "$TRAVIS_BRANCH" == "master" ] && BOOST_BRANCH=master || true
+git clone -b $BOOST_BRANCH https://github.com/boostorg/boost.git boost-root --depth 1
+cd boost-root
+export BOOST_ROOT=$(pwd)
+git submodule update --init libs/context
+git submodule update --init tools/boostbook
+git submodule update --init tools/boostdep
+git submodule update --init tools/docca
+git submodule update --init tools/quickbook
+rsync -av $TRAVIS_BUILD_DIR/ libs/$SELF
+python tools/boostdep/depinst/depinst.py ../tools/quickbook
+./bootstrap.sh
+./b2 headers
+
+#cp libs/sqlite/tools/user-config.jam ~/user-config.jam
+echo "using $TOOLSET : : $COMPILER : $CXX_FLAGS ;" > ~/user-config.jam
+
+echo '==================================> SCRIPT'
+
+echo "using doxygen ; using boostbook ; using saxonhe ;" >> tools/build/src/user-config.jam
+./b2 -j3 libs/$SELF/doc//boostrelease
+
+elif [ "$DRONE_JOB_BUILDTYPE" == "codecov" ]; then
+
+echo '==================================> INSTALL'
+
+common_install
+
+echo '==================================> SCRIPT'
+
+cd $BOOST_ROOT/libs/$SELF
+ci/travis/codecov.sh
+
+elif [ "$DRONE_JOB_BUILDTYPE" == "valgrind" ]; then
+
+echo '==================================> INSTALL'
+
+common_install
+
+echo '==================================> SCRIPT'
+
+cd $BOOST_ROOT/libs/$SELF
+ci/travis/valgrind.sh
+
+elif [ "$DRONE_JOB_BUILDTYPE" == "standalone" ]; then
+
+echo '==================================> INSTALL'
+
+# Installing cmake with apt-get, so not required here:
+# pip install --user cmake
+
+echo '==================================> SCRIPT'
+
+export CXXFLAGS="-Wall -Wextra -Werror -std=c++17"
+mkdir __build_17
+cd __build_17
+cmake -DBOOST_JSON_STANDALONE=1 ..
+cmake --build .
+ctest -V .
+export CXXFLAGS="-Wall -Wextra -Werror -std=c++2a"
+mkdir ../__build_2a
+cd ../__build_2a
+cmake -DBOOST_JSON_STANDALONE=1 ..
+cmake --build .
+ctest -V .
+
+elif [ "$DRONE_JOB_BUILDTYPE" == "coverity" ]; then
+
+echo '==================================> INSTALL'
+
+common_install
+
+echo '==================================> SCRIPT'
+
+if [ $VARIANT = "process_valgrind" ];
+then export USE_VALGRIND="testing.launcher=valgrind valgrind=on";
+fi ;
+
+if [ -n "${COVERITY_SCAN_NOTIFICATION_EMAIL}" -a \( "$TRAVIS_BRANCH" = "develop" -o "$TRAVIS_BRANCH" = "master" \) -a \( "$DRONE_BUILD_EVENT" = "push" -o "$DRONE_BUILD_EVENT" = "cron" \) ] ; then
+cd $BOOST_ROOT/libs/$SELF
+ci/travis/coverity.sh
+fi
+
+elif [ "$DRONE_JOB_BUILDTYPE" == "cmake-superproject" ]; then
+
+echo '==================================> INSTALL'
+
+common_install
+
+echo '==================================> COMPILE'
+
+export CXXFLAGS="-Wall -Wextra -Werror"
+
+mkdir __build_static
+cd __build_static
+cmake -DBOOST_ENABLE_CMAKE=1 -DBUILD_TESTING=ON -DBoost_VERBOSE=1 \
+ -DBOOST_INCLUDE_LIBRARIES=$SELF ..
+cmake --build .
+ctest --output-on-failure -R boost_$SELF
+
+cd ..
+
+mkdir __build_shared
+cd __build_shared
+cmake -DBOOST_ENABLE_CMAKE=1 -DBUILD_TESTING=ON -DBoost_VERBOSE=1 \
+ -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=ON ..
+cmake --build .
+ctest --output-on-failure -R boost_$SELF
+
+elif [ "$DRONE_JOB_BUILDTYPE" == "cmake1" ]; then
+
+echo '==================================> INSTALL'
+
+pip install --user cmake
+
+echo '==================================> SCRIPT'
+
+export SELF=`basename $REPO_NAME`
+BOOST_BRANCH=develop && [ "$DRONE_BRANCH" == "master" ] && BOOST_BRANCH=master || true
+echo BOOST_BRANCH: $BOOST_BRANCH
+cd ..
+git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root
+cd boost-root
+mkdir -p libs/$SELF
+cp -r $DRONE_BUILD_DIR/* libs/$SELF
+# git submodule update --init tools/boostdep
+git submodule update --init --recursive
+
+cd libs/$SELF
+
+../../../b2 -sBOOST_BUILD_PATH=.
+../../../b2 $MULTITHREAD with-valgrind address-model=64 architecture=x86 $USE_VALGRIND toolset=$TOOLSET cxxflags="--coverage -DBOOST_TRAVISCI_BUILD -std=$CXX_STANDARD" linkflags="--coverage" -sBOOST_BUILD_PATH=. $REPORT_CI
+../../../b2 $MULTITHREAD without-valgrind address-model=64 architecture=x86 toolset=$TOOLSET cxxflags="--coverage -DBOOST_TRAVISCI_BUILD -std=$CXX_STANDARD" linkflags="--coverage" -sBOOST_BUILD_PATH=. $REPORT_CI
+
+
+
+fi \ No newline at end of file
diff --git a/subprojects/boost-sqlite/CMakeLists.txt b/subprojects/boost-sqlite/CMakeLists.txt
new file mode 100644
index 0000000..34a7463
--- /dev/null
+++ b/subprojects/boost-sqlite/CMakeLists.txt
@@ -0,0 +1,112 @@
+cmake_minimum_required(VERSION 3.12...3.20)
+
+set(BOOST_SQLITE_VERSION 1)
+if(BOOST_SUPERPROJECT_VERSION)
+ set(BOOST_SQLITE_VERSION ${BOOST_SUPERPROJECT_VERSION})
+endif()
+
+project(boost_sqlite
+ VERSION "${BOOST_SQLITE_VERSION}"
+ DESCRIPTION "A sqlite C++ library"
+ LANGUAGES CXX)
+
+set(BOOST_SQLITE_IS_ROOT OFF)
+if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+ set(BOOST_SQLITE_IS_ROOT ON)
+endif()
+
+set(BOOST_SQLITE_SOURCES
+ src/detail/exception.cpp
+ src/backup.cpp
+ src/blob.cpp
+ src/connection.cpp
+ src/error.cpp
+ src/field.cpp
+ src/meta_data.cpp
+ src/resultset.cpp
+ src/row.cpp
+ src/value.cpp
+)
+
+find_package(SQLite3)
+if(NOT SQLite3_FOUND)
+ message(STATUS "Boost.sqlite has been disabled, because the required package Sqlite3 hasn't been found")
+ return()
+endif()
+
+if (BOOST_SQLITE_IS_ROOT)
+ if(NOT BOOST_SUPERPROJECT_VERSION)
+ option(BOOST_SQLITE_INSTALL "Install boost::sqlite files" ON)
+ option(BOOST_SQLITE_BUILD_TESTS "Build boost::sqlite tests" ON)
+ option(BOOST_SQLITE_BUILD_EXAMPLES "Build boost::sqlite examples" ON)
+ else()
+ set(BOOST_SQLITE_BUILD_TESTS ${BUILD_TESTING})
+ endif()
+
+ find_package(Threads REQUIRED)
+ find_package(Boost REQUIRED json OPTIONAL_COMPONENTS url unit_test_framework)
+
+endif()
+
+add_library(boost_sqlite ${BOOST_SQLITE_SOURCES})
+target_include_directories(boost_sqlite PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
+target_link_libraries(boost_sqlite PUBLIC Boost::headers SQLite::SQLite3)
+add_library(Boost::sqlite ALIAS boost_sqlite)
+
+add_library(boost_sqlite_ext ${BOOST_SQLITE_SOURCES} src/ext.cpp)
+target_include_directories(boost_sqlite_ext PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
+target_link_libraries(boost_sqlite_ext PUBLIC Boost::headers SQLite::SQLite3)
+target_compile_definitions(boost_sqlite_ext PUBLIC BOOST_SQLITE_COMPILE_EXTENSION=1)
+set_property(TARGET boost_sqlite_ext PROPERTY POSITION_INDEPENDENT_CODE ON)
+add_library(Boost::sqlite_ext ALIAS boost_sqlite_ext)
+
+if (NOT BOOST_SQLITE_IS_ROOT)
+ if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt")
+ set(BOOST_SQLITE_BUILD_TESTS ON)
+ endif()
+ target_link_libraries(boost_sqlite PUBLIC Boost::callable_traits Boost::config Boost::core Boost::describe Boost::pfr Boost::system)
+ target_link_libraries(boost_sqlite_ext PUBLIC Boost::callable_traits Boost::config Boost::core Boost::describe Boost::pfr Boost::system)
+else()
+ if(NOT BOOST_SUPERPROJECT_VERSION)
+ option(BOOST_SQLITE_INSTALL "Install boost::sqlite files" ON)
+ option(BOOST_SQLITE_BUILD_TESTS "Build boost::sqlite tests" ON)
+ option(BOOST_SQLITE_BUILD_EXAMPLES "Build boost::sqlite examples" ON)
+ else()
+ set(BOOST_SQLITE_BUILD_TESTS ${BUILD_TESTING})
+ endif()
+
+ file(GLOB_RECURSE ADOC_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.adoc)
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/doc/index.html
+ COMMAND asciidoctor ${CMAKE_CURRENT_SOURCE_DIR}/doc/index.adoc -b html5 -a generate-diagram -o ${CMAKE_CURRENT_BINARY_DIR}/doc/index.html
+ DEPENDS ${ADOC_FILES})
+
+ add_custom_target(boost_sqlite_doc DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/doc/index.html)
+
+endif()
+
+
+if(BUILD_SHARED_LIBS)
+ target_compile_definitions(boost_sqlite PUBLIC BOOST_SQLITE_DYN_LINK=1)
+ target_compile_definitions(boost_sqlite_ext PUBLIC BOOST_SQLITE_DYN_LINK=1)
+else()
+ target_compile_definitions(boost_sqlite PUBLIC BOOST_SQLITE_STATIC_LINK=1)
+ target_compile_definitions(boost_sqlite_ext PUBLIC BOOST_SQLITE_STATIC_LINK=1)
+endif()
+
+if(BOOST_SQLITE_INSTALL AND NOT BOOST_SUPERPROJECT_VERSION)
+ install(TARGETS boost_sqlite
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ )
+endif()
+
+if(BUILD_TESTING)
+ enable_testing()
+ add_subdirectory(test)
+endif()
+
+
+if(BOOST_SQLITE_BUILD_EXAMPLES)
+ add_subdirectory(example)
+endif()
diff --git a/subprojects/boost-sqlite/build/Jamfile b/subprojects/boost-sqlite/build/Jamfile
new file mode 100644
index 0000000..3fae7e4
--- /dev/null
+++ b/subprojects/boost-sqlite/build/Jamfile
@@ -0,0 +1,36 @@
+
+project boost/sqlite
+ : requirements
+ <define>BOOST_FILESYSTEM_NO_DEPRECATED=1
+ <define>BOOST_SQLITE_SEPARATE_COMPILATION=1
+ <link>shared:<define>BOOST_SQLITE_DYN_LINK=1
+ <link>static:<define>BOOST_SQLITE_STATIC_LINK=1
+ <define>BOOST_SQLITE_SOURCE=1
+ : usage-requirements
+ <link>shared:<define>BOOST_SQLITE_DYN_LINK=1
+ <link>static:<define>BOOST_SQLITE_STATIC_LINK=1
+ : source-location ../src
+ ;
+
+lib sqlite3 ;
+
+local SOURCES =
+ detail/exception.cpp
+ backup.cpp
+ blob.cpp
+ connection.cpp
+ error.cpp
+ ext.cpp
+ field.cpp
+ meta_data.cpp
+ resultset.cpp
+ row.cpp
+ value.cpp ;
+
+
+lib boost_sqlite : $(SOURCES) sqlite3 /boost//json ;
+lib boost_sqlite_ext : $(SOURCES) ext.cpp sqlite3 /boost//json : <define>BOOST_SQLITE_COMPILE_EXTENSION=1 ;
+alias extension : boost_sqlite_ext ;
+
+boost-install boost_sqlite boost_sqlite_ext ;
+
diff --git a/subprojects/boost-sqlite/doc/Jamfile b/subprojects/boost-sqlite/doc/Jamfile
new file mode 100644
index 0000000..9a9f9d3
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/Jamfile
@@ -0,0 +1,18 @@
+import asciidoctor ;
+
+html index.html : index.adoc ;
+
+install html_ : index.html : <location>html ;
+
+
+pdf sqlite.pdf : index.adoc ;
+explicit sqlite.pdf ;
+
+install pdf_ : sqlite.pdf : <location>pdf ;
+explicit pdf_ ;
+
+alias boostdoc ;
+explicit boostdoc ;
+alias boostrelease : html_ ;
+explicit boostrelease ;
+
diff --git a/subprojects/boost-sqlite/doc/extensions.adoc b/subprojects/boost-sqlite/doc/extensions.adoc
new file mode 100644
index 0000000..528fb39
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/extensions.adoc
@@ -0,0 +1,41 @@
+= Extension Modules
+
+This library can also be used to https://www.sqlite.org/loadext.html::[run-time loadable extensions]
+that can be used by other applications, e.g. the `sqlite3` CLI.
+
+In order to write this, you'll need to include `boost/sqlite/extension.hpp`,
+and write a named module like so:
+
+[source,cpp]
+----
+BOOST_SQLITE_EXTENSION(testlibrary, conn)
+{
+ // create a function that can be used in the plugin
+ create_scalar_function(
+ conn, "assert",
+ [](boost::sqlite::context<>, boost::span<boost::sqlite::value, 1u> sp)
+ {
+ if (sp.front().get_int() == 0)
+ throw std::logic_error("assertion failed");
+ });
+}
+----
+
+[source,sqlite]
+----
+SELECT load_extension('./test_library');
+
+select assert((3 * 4) = 12);
+----
+
+In order to build this, you'll to link against `Boost::sqlite_ext`
+instead of `Boost::sqlite`.
+
+Including the `extension.hpp` header will also define
+`BOOST_SQLITE_COMPILE_EXTENSION`, which will include `sqlite3ext.h`
+instead of `sqlite3.h` and create an inline namespace `ext` inside
+`boost::sqlite`.
+
+This prevents linker issues, but will not allow you to mix extension
+and non-extension code in one translation unit.
+
diff --git a/subprojects/boost-sqlite/doc/functions.adoc b/subprojects/boost-sqlite/doc/functions.adoc
new file mode 100644
index 0000000..9b58b62
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/functions.adoc
@@ -0,0 +1,88 @@
+= Adding functions
+
+== Scalar Functions
+
+Scalar functions are data transformers.
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=to_upper]
+----
+<1> The context is optional, and can be used to share between invocations, within the same query or to set errors/return values.
+<2> Let this function take 1 parameter. If this is `span::extent` it will be any size. Functions can be overloaded by the number of parameters.
+<3> Any exception will be turned into an sqlite-error code.
+<4> The function is determistic, i.e. it has no side effects (see https://www.sqlite.org/c3ref/c_deterministic.html[Function Flags])
+
+The return type `T` of the function gets transformed into an sqlite value by using a `tag_invoke` overload.
+This interface is public and meant to be extended.
+
+[source,cpp]
+----
+void tag_invoke(set_result_tag, sqlite3_context * ctx, T);
+----
+
+The builtin types are:
+
+- `blob`
+- `zero_blob`
+- `double`
+- `std::int64_t`
+- `nullptr`
+- `string_view`
+- `std::string`
+- `sqlite::value`
+- `variant2::monostate`
+- `error`
+- `std::unique_ptr<T>` (see the https://www.sqlite.org/bindptr.html[pointer passing interface])
+- `variant2::variant<Ts...>` if all `Ts` are supported
+- `result<T>`
+- `boost::json::value` //when including boost/sqlite/json.hpp
+
+The translation of the exceptions happens as follows:
+
+[cols="1,1"]
+|===
+| Type | Sqlite error code
+
+| `boost::system::system_error` | `.code()`
+| `std::bad_alloc` | SQLITE_NOMEM
+| `std::length_error` | SQLITE_TOOBIG
+| `std::out_of_range` | SQLITE_RANGE
+| `std::logic_error` | SQLITE_MISUSE
+| `...` | SQLITE_ERROR
+
+|===
+
+NOTE: You can also return an <<`result`,sqlite::result>> type instead of throwing exceptions.
+
+== A ggregate functions
+
+An aggregate function builds a value from multiple values of the same column, e.g:
+
+[source,sqlite]
+----
+select avg(age) from users ;
+----
+
+`avg` is a built-in function, but
+Below is a toy example. We count how many retirees are among the users, based on a retirement age.
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=oldest]
+----
+
+The `retirees` object will be constructed for every query it's used in, and the parameters passed witht he `make_tuple`
+will be passed to the constructor.
+The `step` will be called for every value and `final` at the end when the query has ended and a value is required.
+
+The value types & exceptions are the same as for the scalar function.
+
+== Window functions
+
+Window functions look similar to aggregate functions, they only need an `inverse` function, that shares the signature with `step`.
+
+It is recommended to consult the https://www.sqlite.org/windowfunctions.html[sqlite window function documentation].
+
+NOTE: Window functions are only available for sqlite 3.25.0 and higher.
+
diff --git a/subprojects/boost-sqlite/doc/index.adoc b/subprojects/boost-sqlite/doc/index.adoc
new file mode 100644
index 0000000..1ec5ec7
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/index.adoc
@@ -0,0 +1,28 @@
+= Documentation boost.sqlite
+Klemens Morgenstern <klemens.morgenstern@gmx.net>
+Version 1.0, 23.11.2024
+:source-highlighter: rouge
+:toc: left
+:toclevels: 4
+:icons: font
+:idprefix:
+:docinfo: private-footer
+:source-highlighter: rouge
+:source-language: c++
+:example-caption: Example
+:coderay-linenums-mode: inline
+
+:leveloffset: +1
+
+= Introduction
+
+This small C+\+-14 library, extending the excellent sqlite API for C++.
+It sticks as close as possible to the sqlite API, but adopts thing like errors or ranges.
+
+It is therefore highly recommended to study the https://www.sqlite.org/docs.html[sqlite documentation].
+
+include::tutorial.adoc[]
+include::functions.adoc[]
+include::extensions.adoc[]
+include::vtable.adoc[]
+include::reference.adoc[]
diff --git a/subprojects/boost-sqlite/doc/reference.adoc b/subprojects/boost-sqlite/doc/reference.adoc
new file mode 100644
index 0000000..17de91c
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference.adoc
@@ -0,0 +1,28 @@
+= Reference
+
+include::reference/allocator.adoc[]
+include::reference/backup.adoc[]
+include::reference/blob.adoc[]
+include::reference/collation.adoc[]
+include::reference/connection.adoc[]
+include::reference/cstring_ref.adoc[]
+include::reference/error.adoc[]
+include::reference/extension.adoc[]
+include::reference/field.adoc[]
+include::reference/function.adoc[]
+include::reference/hooks.adoc[]
+include::reference/json.adoc[]
+include::reference/memory.adoc[]
+include::reference/meta_data.adoc[]
+include::reference/mutex.adoc[]
+include::reference/result.adoc[]
+include::reference/resultset.adoc[]
+include::reference/row.adoc[]
+include::reference/statement.adoc[]
+include::reference/static_resultset.adoc[]
+include::reference/string.adoc[]
+include::reference/transaction.adoc[]
+include::reference/value.adoc[]
+include::reference/vtable.adoc[]
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/allocator.adoc b/subprojects/boost-sqlite/doc/reference/allocator.adoc
new file mode 100644
index 0000000..ab9a4e9
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/allocator.adoc
@@ -0,0 +1,31 @@
+== `sqlite/allocator.hpp`
+[#allocator]
+
+The sqlite allocator wraps sqlite's malloc & free functions in a similar way that std::allocator wraps `new`/`delete`.
+
+This can be used for sqlite-related code (e.g. vtables or custom functions) that should use memory from the sqlite3 pool.
+
+[source,cpp,subs=+quotes]
+----
+template<typename T>
+struct allocator
+{
+ constexpr allocator() noexcept {}
+ constexpr allocator( const allocator& other ) noexcept {}
+ template< class U >
+ constexpr allocator( const allocator<U>& other ) noexcept {}
+
+ constexpr static std::size_t alignment = __implementation_defined__;
+
+
+ static_assert(alignof(T) <= alignment, "T alignment can't be fulfilled by sqlite");
+
+ [[nodiscard]] T* allocate( std::size_t n ); // <1>
+ void deallocate( T* p, std::size_t); // <2>
+};
+----
+<1> Invokes `sqlite3_malloc64` and throws `std::bad_alloc` if it fails.
+<2> Invokes `sqlite3_free`
+
+NOTE: Sqlite provides extensive https://www.sqlite.org/malloc.html[customizability for its dynamic memory allocation].
+
diff --git a/subprojects/boost-sqlite/doc/reference/backup.adoc b/subprojects/boost-sqlite/doc/reference/backup.adoc
new file mode 100644
index 0000000..bc74619
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/backup.adoc
@@ -0,0 +1,43 @@
+== `sqlite/backup.hpp`
+[#backup]
+
+Backup is a small wrapper function to create a backup of one database into another.
+This can be useful to write an in memory database to disk et vice versa.
+
+[source,cpp]
+----
+void
+backup(connection & source,
+ connection & target,
+ cstring_ref source_name = "main",
+ cstring_ref target_name = "main");
+
+void
+backup(connection & source,
+ connection & target,
+ cstring_ref source_name,
+ cstring_ref target_name,
+ system::error_code & ec,
+ error_info & ei);
+----
+
+
+source:: The source database to backup
+
+target:: The target of the backup
+
+source_name:: The source database to read the backup from. Default is 'main'.
+target_name:: The target database to write the backup to. Default is 'main'.
+
+
+.Example
+[source,cpp]
+----
+sqlite::connection conn{sqlite::in_memory};
+{
+ sqlite::connection read{"./read_only_db.db", SQLITE_READONLY};
+ // read peristed data into memory.
+ backup(read, target);
+}
+----
+
diff --git a/subprojects/boost-sqlite/doc/reference/blob.adoc b/subprojects/boost-sqlite/doc/reference/blob.adoc
new file mode 100644
index 0000000..8f73105
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/blob.adoc
@@ -0,0 +1,141 @@
+== `sqlite/blob.hpp`
+
+=== `blob_view`
+
+A `blob_view` is a view type referring to https://www.sqlite.org/datatype3.html[Binary Large OBject],
+i.e. a non-owning type.
+
+.Definition
+[source,cpp]
+----
+//A view to a binary large object
+struct blob_view
+{
+ // The data in the blob
+ const void * data() const;
+ // The size of the data in the blob, in bytes
+ std::size_t size() const
+ // Construct a blob from existing data
+ blob_view(const void * data, std::size_t size);
+
+ // Construct an empty blob
+ blob_view() = default;
+ // Construct a blob from some other blob-like structure (data() is a pointer & size() returns size_t)
+ template<typename T>
+ explicit blob_view(const T & value);
+
+ // Create a blob from the
+ blob_view(const struct blob & b);
+};
+----
+
+The `blob_view` can be used to access binary data from the database without copying.
+
+=== `zero_blob`
+
+The `zero_blob` is a special type that denotes blobs full of zeros without requiring any allocations.
+It can be used as a result from a <<function>> or as a parameter for a <<statement>>.
+
+.Definition
+[source,cpp]
+----
+enum class zero_blob : sqlite3_uint64 {};
+----
+
+.Example
+[source,cpp]
+----
+extern sqlite::connection conn;
+conn.prepare("INSERT INTO example(bdata) VALUES(?)")
+ .execute(sqlite::zero_blob(1024)); // <1>
+----
+<1> Insert a blob of zeros with the size 1024
+
+=== `blob`
+
+The `blob` object owns a binary large object.
+
+[source,cpp]
+----
+// @brief An object that owns a binary large object. @ingroup reference
+struct blob
+{
+ // The data in the blob
+ void * data() const;
+ // The size of the data int he blob, in bytes
+ std::size_t size() const;
+
+ // Create a blob from a blob_view
+ explicit blob(blob_view bv);
+ // Create an empty blob with size `n`.
+ explicit blob(std::size_t n);
+
+ // Construct an empty blob
+ constexpr blob() = default;
+ // Release & take ownership of the blob.
+ void * release() &&
+};
+----
+
+
+=== `blob_handle`
+
+A `blob_handle` is readable & writable handle to a `blob` in the database.
+It allows incremental reading/writing of the raw data.
+
+[source,cpp]
+----
+// Open a blob
+blob_handle open_blob(connection & conn,
+ cstring_ref db,
+ cstring_ref table,
+ cstring_ref column,
+ sqlite3_int64 row,
+ bool read_only,
+ system::error_code &ec,
+ error_info &ei);
+blob_handle open_blob(connection & conn,
+ cstring_ref db,
+ cstring_ref table,
+ cstring_ref column,
+ sqlite3_int64 row,
+ bool read_only = false);
+
+// An object that holds a binary large object. Can be obtained by using @ref blob_handle. @ingroup reference
+struct blob_handle
+{
+ // Default constructor
+ blob_handle() = default;
+
+ // Construct from a handle. This takesowner ship of the `sqlite3_blob` handle.
+ explicit blob_handle(sqlite3_blob * blob);
+
+ // Reopen the blob on another row (i.e. the same column of the same table)
+ void reopen(sqlite3_int64 row_id);
+ void reopen(sqlite3_int64 row_id, system::error_code & ec);
+
+ // Read data from the blob
+ void read_at(void *data, int len, int offset);
+ void read_at(void *data, int len, int offset, system::error_code &ec);
+
+ // Write data to the blob
+ void write_at(const void *data, int len, int offset);
+ void write_at(const void *data, int len, int offset, system::error_code &ec);
+
+ // The size of the blob
+ std::size_t size() const;
+
+ // The handle of the blob
+ using handle_type = sqlite3_blob*;
+ // Returns the handle of the blob
+ handle_type handle();
+ // Release the owned handle.
+ handle_type release() &&;
+};
+----
+
+
+
+
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/collation.adoc b/subprojects/boost-sqlite/doc/reference/collation.adoc
new file mode 100644
index 0000000..6578f59
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/collation.adoc
@@ -0,0 +1,45 @@
+== `sqlite/collation.hpp`
+[#collation]
+
+A https://www.sqlite.org/datatype3.html#collation[collation] is a comparison operator between two string-like values,
+that allows ordering with a custom algorithm.
+
+.Definition
+[source,cpp]
+----
+
+// Create a collation
+template<typename Func>
+void create_collation(connection & conn, cstring_ref name, Func && func);
+template<typename Func>
+void create_collation(connection & conn, cstring_ref name, Func && func, system::error_code &ec);
+
+// Delete an existing collation.
+void delete_collation(connection & conn, cstring_ref name, system::error_code & ec);
+void delete_collation(connection & conn, cstring_ref name);
+----
+
+ conn:: A connection to the database in which to install the collation.
+ name:: The name of the collation.
+ func:: The function
+
+The function must be callable with two `string_view` and return an int, indicating the comparison results.
+
+.Example
+[source,cpp]
+----
+// a case insensitive string omparison, e.g. from boost.urls
+int ci_compare(string_view s0, string_view s1) noexcept;
+
+extern sqlite::connection conn;
+
+// Register the collation
+sqlite::create_collation(conn, "iequal", &ci_compare);
+
+// use the collation to get by name, case insensitively
+conn.query("select first_name, last_name from people where first_name = 'Klemens' collate iequal;");
+
+// order by names case insensitively
+conn.query("select * from people order by last_name collate iequal asc;");
+----
+
diff --git a/subprojects/boost-sqlite/doc/reference/connection.adoc b/subprojects/boost-sqlite/doc/reference/connection.adoc
new file mode 100644
index 0000000..6d62c10
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/connection.adoc
@@ -0,0 +1,100 @@
+== `sqlite/connection.hpp`
+[#connection]
+
+The `connection` object is the main object to access a database.
+
+.Definition
+[source,cpp]
+----
+// Utility constant for in-memory databases
+constexpr static cstring_ref in_memory = ":memory:";
+
+struct connection
+{
+ // The handle of the connection
+ using handle_type = sqlite3*;
+ // Returns the handle
+ handle_type handle() const;
+ // Release the owned handle.
+ handle_type release() &&;
+
+ //Default constructor
+ connection() = default;
+ // Construct the connection from a handle.
+ explicit connection(handle_type handle, bool take_ownership = true); // <1>
+ // Move constructor.
+ connection(connection && ) = default;
+ // Move assign operator.
+ connection& operator=(connection && ) = default;
+
+ // Construct a connection and connect it to `filename`. `flags` is set by `SQLITE_OPEN_*` flags.
+ connection(cstring_ref filename,
+ int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); // <2>
+ template<typename Path>
+ explicit connection(const Path & pth);
+
+
+ // Connect the database to `filename`. `flags` is set by `SQLITE_OPEN_*` flags.
+ void connect(cstring_ref filename, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); // <2>
+ void connect(cstring_ref filename, int flags, system::error_code & ec);
+
+ template<typename Path>
+ void connect(const Path & pth, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
+ template<typename Path>
+ void connect(const Path & pth, int flags, system::error_code & ec);
+
+ // Close the database connection.
+ void close();
+ void close(system::error_code & ec, error_info & ei);
+
+ // Check if the database holds a valid handle.
+ bool valid() const;
+
+ // Perform a query without parameters. Can only execute a single statement.
+ resultset query(
+ core::string_view q,
+ system::error_code & ec,
+ error_info & ei);
+ resultset query(core::string_view q);
+
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> query(core::string_view q, system::error_code & ec, error_info & ei);
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> query(core::string_view q);
+
+ // Perform a query without parametert, It execute a multiple statement.
+ void execute(cstring_ref q, system::error_code & ec, error_info & ei);
+ void execute(cstring_ref q);
+
+
+ // Preparse a a statement.
+ statement prepare(
+ core::string_view q,
+ system::error_code & ec,
+ error_info & ei);
+ statement prepare(core::string_view q);
+
+
+ // Check if the database has the table
+ bool has_table(
+ cstring_ref table,
+ cstring_ref db_name = "main") const;
+
+ // Check if the database has the table
+ bool has_column(
+ cstring_ref table,
+ cstring_ref column,
+ cstring_ref db_name = "main") const;
+};
+
+----
+<1> The `take_ownership` is usually only false when used from <<extension_modules, extension modules>>.
+<2> See https://www.sqlite.org/c3ref/c_open_autoproxy.html[the sqlite documentation for the available flags].
+
+.Example
+[source,cpp]
+----
+sqlite::connection conn;
+conn.connect("./my-database.db");
+conn.prepare("insert into log (text) values ($1)").execute(std::make_tuple("booting up"));
+---- \ No newline at end of file
diff --git a/subprojects/boost-sqlite/doc/reference/cstring_ref.adoc b/subprojects/boost-sqlite/doc/reference/cstring_ref.adoc
new file mode 100644
index 0000000..402663d
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/cstring_ref.adoc
@@ -0,0 +1,16 @@
+== `sqlite/cstring_ref.hpp`
+
+[#string_view]
+
+The `sqlite::string_view` class is a https://en.cppreference.com/w/cpp/string/basic_string_view[std::string_view]
+compatible class.
+
+[#cstring_ref]
+
+The `cstring_ref` class is a view type similar to a `string_view`, but with a guarantee that it is null-terminated.
+
+It can be constructed from a raw `const char *` or any class that has a `c_str()` function returning a `const char *`.
+
+Otherwise it is similar to a `string_view`, except that `substr(std::size_t())` will return a `cstring_ref`,
+whereas a `substr(std::size_t(), std::size_t())` returns a `string_view`.
+
diff --git a/subprojects/boost-sqlite/doc/reference/error.adoc b/subprojects/boost-sqlite/doc/reference/error.adoc
new file mode 100644
index 0000000..424c7dd
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/error.adoc
@@ -0,0 +1,91 @@
+== `sqlite/error.hpp`
+
+=== `sqlite_category`
+
+The sqlite_category is a `boost::system::error_category` to be used with sqlite errors.
+
+=== `error_info`
+
+The `error_info` class hold additional information about error conditions stored in an sqlite-allocate string.
+
+Contains an error message describing what happened. Not all error conditions are able to generate this extended information - those that
+can't will have an empty error message.
+
+The `error_info` allocates memory from the sqlite pool and holds it.
+
+[source,cpp]
+----
+struct error_info
+{
+ // Default constructor.
+ error_info() = default;
+
+ // Initialization constructor. Copies the message into a newly create buffer.
+ error_info(core::string_view msg) noexcept;
+ // set the message by copy
+ void set_message(core::string_view msg);
+
+ // Reset the buffer. If `c` is not null, its ownership is transferred into the error_info object.
+ void reset(char * c = nullptr);
+
+ // Format a message into a newly allocated buffer.
+ cstring_ref format(cstring_ref fmt, ...);
+ // Format a message into the existing buffer.
+ cstring_ref snformat(cstring_ref fmt, ...);
+ /// reserve data in the buffer i.e. allocate
+ void reserve(std::size_t sz);
+
+ // Get the allocated memory
+ std::size_t capacity() const;
+
+ // Gets the error message.
+ cstring_ref message() const noexcept;
+
+ // Release the underlying memory. It must be freed using `sqlite_free` later.
+ char * release();
+ // Restores the message to its initial state. Does not release memory.
+ void clear() noexcept;
+
+};
+----
+
+=== `error`
+
+The `error` class holds `error_info` and a `code` and can be used with https://www.boost.org/doc/libs/master/libs/system/doc/html/system.html#ref_boostsystemresult_hpp[`boost::system::result`].
+
+[source,cpp]
+----
+/**
+ * \brief An error containing both a code & optional message.
+ * \ingroup reference
+ * \details Contains an error .
+ */
+struct error
+{
+ // The code of the error.
+ int code;
+ // The additional information of the error
+ error_info info;
+
+ // Create an error with code & message
+ error(int code, error_info info) ;
+ error(int code, core::string_view info);
+ error(system::error_code code, error_info info) // <1>
+ // Create an error with only a code.
+ explicit error(int code);
+
+ error(system::error_code code);
+ // Create an empty error;
+ error() = default;
+ error(error && ) noexcept = default;
+};
+
+// For compatability with system::result;
+void throw_exception_from_error( error const & e, boost::source_location const & loc );
+
+template<typename T = void>
+using result = system::result<T, error>;
+----
+<1> If code.category() is not `sqlite_category`, the code will be set to `SQLITE_FAIL`.
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/extension.adoc b/subprojects/boost-sqlite/doc/reference/extension.adoc
new file mode 100644
index 0000000..7ee2500
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/extension.adoc
@@ -0,0 +1,36 @@
+== `sqlite/extension.hpp`
+
+=== `BOOST_SQLITE_EXTENSION`
+
+ This macro can be used to create an sqlite extension.
+
+.Definition
+[source,cpp]
+----
+#define BOOST_SQLITE_EXTENSION(Name, Conn)
+----
+
+Name:: The name of the module.
+Conn:: The parameter name of the connection.
+
+
+NOTE: When defining BOOST_SQLITE_COMPILE_EXTENSION (was is done in extension.hpp)
+sqlite will use an inline namespace to avoid symbol clashes.
+
+You must link against `Boost::sqlite_ext` and not `Boost::sqlite` and should not mix both in the same binary.
+
+.Example
+[source,cpp]
+----
+BOOST_SQLITE_EXTENSION(extension, conn)
+{
+ create_scalar_function(
+ conn, "assert",
+ [](boost::sqlite::context<>, boost::span<boost::sqlite::value, 1u> sp)
+ {
+ if (sp.front().get_int() == 0)
+ throw std::logic_error("assertion failed");
+ });
+}
+----
+
diff --git a/subprojects/boost-sqlite/doc/reference/field.adoc b/subprojects/boost-sqlite/doc/reference/field.adoc
new file mode 100644
index 0000000..572719d
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/field.adoc
@@ -0,0 +1,42 @@
+== `sqlite/field.hpp`
+
+A `field` is a type representing a <<value>> in a database. or as a result from a query.
+
+.Definition
+[source,cpp]
+----
+
+struct field
+{
+ typedef sqlite_int64 int64;
+
+ // The type of the value
+ value_type type() const;
+ // Is the held value null
+ bool is_null() const;
+ // Is the held value is not null
+ explicit operator bool () const;
+ // Returns the value as an `int64`.
+ int64 get_int() const;
+ // Returns the value as an `double`.
+ double get_double() const;
+ // Returns the value as text, i.e. a string_view. Note that this value may be invalidated
+ cstring_ref get_text() const;
+ // Returns the value as blob, i.e. raw memory. Note that this value may be invalidated
+ blob_view get_blob() const;
+ // Returns the field as a value.
+ value get_value() const;
+ // Returns the name of the column.
+ cstring_ref column_name() const;
+ // Returns the name of the table.
+ cstring_ref table_name() const;
+ // Returns the name of the original data source.
+ cstring_ref column_origin_name() const;
+}
+----
+
+NOTE: The view types can be invalidated when the database changes or the next row is read by the query.
+
+WARNING: The `field` type does not own the statement/query it was produced by. It is a merely a view into the `resultset`.
+Reading the next row will change the values returned.
+
diff --git a/subprojects/boost-sqlite/doc/reference/function.adoc b/subprojects/boost-sqlite/doc/reference/function.adoc
new file mode 100644
index 0000000..311aebc
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/function.adoc
@@ -0,0 +1,263 @@
+== `sqlite/function.hpp`
+
+=== `function_flags`
+
+[source, cpp]
+----
+enum function_flags
+{
+ deterministic = SQLITE_DETERMINISTIC,
+ directonly = SQLITE_DIRECTONLY,
+ subtype = SQLITE_SUBTYPE,
+ innocuous = SQLITE_INNOCUOUS,
+ result_subtype = SQLITE_RESULT_SUBTYPE,
+ selforder1 = SQLITE_SELFORDER1,
+ selforder1 = SQLITE_SELFORDER1
+};
+----
+
+These function flags can be used in accordance to the
+https://www.sqlite.org/c3ref/c_deterministic.html[sqlite documentation].
+
+=== `context`
+
+A context is an object share values between invocations of a scalar function.
+
+.Definition
+[source,cpp]
+----
+
+template<typename ... Args>
+struct context
+{
+ template<std::size_t N>
+ using element = mp11::mp_take_c<mp11::mp_list<Args...>, N>;
+
+ // Set the value in the context at position `Idx`
+ template<std::size_t Idx>
+ void set(element<Idx> value);
+
+ // Returns the value in the context at position `Idx`. Throws if the value isn't set.
+ template<std::size_t Idx>
+ auto get() -> element<Idx> &;
+
+ // Returns the value in the context at position `Idx`. Returns nullptr .value isn't set.
+ template<std::size_t Idx>
+ auto get_if() -> element<Idx> *;
+
+ explicit context(sqlite3_context * ctx) noexcept;
+
+ // Set the result through the context, instead of returning it.
+ template<typename T>
+ auto set_result(T && val);
+
+ // Set the an error through the context, instead of throwing it.
+ void set_error(cstring_ref message, int code = SQLITE_ERROR);
+
+ // Returns the connection of the context.
+ connection get_connection() const;
+};
+----
+
+
+.Example
+[source,cpp]
+----
+extern sqlite::connection conn;
+
+sqlite::create_scalar_function(
+conn, "my_sum",
+[](sqlite::context<std::size_t> ctx,
+ boost::span<sqlite::value, 1u> args) -> std::size_t
+{
+ auto value = args[0].get_int();
+ auto p = ctx.get_if<0>();
+ if (p != nullptr) // increment the counter
+ return (*p) += value;
+ else // set the initial value
+ ctx.set<0>(value);
+ return value;
+});
+----
+
+
+=== `create_scalar_function`
+
+
+Creates a https://www.sqlite.org/appfunc.html[scalar function].
+
+.Definition
+[source,cpp]
+----
+
+template<typename Func>
+auto create_scalar_function(
+ connection & conn,
+ cstring_ref name,
+ Func && func,
+ function_flags flags = {});
+
+template<typename Func>
+auto create_scalar_function(
+ connection & conn,
+ cstring_ref name,
+ Func && func,
+ function_flags flags,
+ system::error_code & ec,
+ error_info & ei);
+----
+
+conn:: The connection to add the function to.
+name:: The name of the function
+func:: The function to be added
+
+`func` must take `context<Args...>` as the first and a `span<value, N>` as the second value.
+If `N` is not `dynamic_extent` it will be used to deduce the number of arguments for the function.
+
+
+.Example
+[source,cpp]
+----
+extern sqlite::connection conn;
+
+sqlite::create_function(
+ conn, "to_upper",
+ [](sqlite::context<> ctx,
+ boost::span<sqlite::value, 1u> args) -> std::string
+ {
+ std::string res;
+ auto txt = val[0].get_text();
+ res.resize(txt.size());
+ std::transform(txt.begin(), txt.end(), res.begin(),
+ [](char c){return std::toupper(c);});
+ return value;
+ });
+----
+
+
+=== `create_aggregate_function`
+
+An aggregrate function will create a new `Func` for a new `aggregate` from the args tuple and call `step` for every step.
+When the aggregation is done `final` is called and the result is returned to sqlite.
+
+[source,cpp]
+----
+template<typename Func, typename Args = std::tuple<>>
+void create_aggregate_function(
+connection & conn,
+cstring_ref name,
+Args && args= {},
+function_flags flags = {});
+
+template<typename Func, typename Args = std::tuple<>>
+void create_aggregate_function(
+connection & conn,
+cstring_ref name,
+Args && args,
+function_flags flags,
+system::error_code & ec,
+error_info & ei);
+----
+
+
+conn:: The connection to add the function to.
+name:: The name of the function
+
+args:: The argument tuple to construct `Func` from.
+Func:: The function to be added. It needs to be an object with two functions:
+[source,cpp]
+----
+void step(boost::span<sqlite::value, N> args);
+T final();
+----
+
+.Example
+[source,cpp]
+----
+ extern sqlite::connection conn;
+
+struct aggregate_func
+{
+ aggregate_func(std::size_t init) : counter(init) {}
+ std::int64_t counter;
+ void step(, boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ }
+
+ std::int64_t final()
+ {
+ return counter;
+ }
+};
+
+sqlite::create_function<aggregate_func>(conn, "char_counter", std::make_tuple(42));
+----
+
+
+=== `create_window_function`
+
+NOTE: This is only available starting with sqlite 3.25.0.
+
+An window function will create a new `Func` for a new `aggregate` and call `step` for every step.
+When an element is removed from the window `inverse` is called.
+When the aggregation is done `final` is called and the result is returned to sqlite.
+
+[source,cpp]
+----
+template<typename Func, typename Args = std::tuple<>>
+void create_window_function(
+ connection & conn,
+ cstring_ref name,
+ Args && args = {},
+ function_flags flags = {});
+
+template<typename Func, typename Args = std::tuple<>>
+void create_window_function(
+ connection & conn,
+ cstring_ref name,
+ Args && args,
+ function_flags flags,
+ system::error_code & ec);
+----
+
+
+conn:: The connection to add the function to.
+name:: The name of the function
+args:: The arguments to construct Func from.
+Func:: The function to be added. It needs to be an object with three functions:
+[source,cpp]
+----
+void step(boost::span<sqlite::value, N> args);
+void inverse(boost::span<sqlite::value, N> args);
+T final();
+----
+
+
+.Example
+[source,cpp]
+----
+extern sqlite::connection conn;
+
+struct window_func
+{
+ std::int64_t counter;
+ void step(boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ }
+ void inverse(boost::span<sqlite::value, 1u> val)
+ {
+ counter -= val[0].get_text().size();
+ }
+
+ std::int64_t final()
+ {
+ return counter;
+ }
+};
+
+sqlite::create_function(conn, "win_char_counter", aggregate_func{});
+----
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/hooks.adoc b/subprojects/boost-sqlite/doc/reference/hooks.adoc
new file mode 100644
index 0000000..c74d712
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/hooks.adoc
@@ -0,0 +1,105 @@
+== `sqlite/hooks.hpp`
+
+WARNING: This API might be subject change, if a better solution for ownership is found.
+
+=== `commit_hook` & `rollback_commit`
+
+The https://www.sqlite.org/c3ref/commit_hook.html[commit hook]
+gets called before a commit gets performed.
+Likewise the rollback hook gets invoked before a rollback.
+If `func` returns true, the commit goes, otherwise it gets rolled back.
+
+NOTE: If the function is not a free function pointer, this function will *NOT* take ownership.
+
+NOTE: If `func` is a `nullptr` the hook gets reset.
+
+
+[source,cpp]
+----
+template<typename Func>
+bool commit_hook(connection & conn, Func && func);
+
+template<typename Func>
+bool rollback_hook(connection & conn, Func && func);
+----
+
+return:: `true` if a hook has been replaced.
+conn:: The database connection to install the hook in
+func:: The hook function. It must be callable without any parameter, return a `bool` and be `noexcept`.
+
+
+=== `update_hook`
+
+The https://www.sqlite.org/c3ref/update_hook.html[update hook]
+The update hook gets called when an update was performed.
+
+
+NOTE: If the function is not a free function pointer, this function will *NOT* take ownership.
+
+NOTE: If `func` is a `nullptr` the hook gets reset.
+
+
+[source,cpp]
+----
+template<typename Func>
+bool update_hook(connection & conn, Func && func);
+----
+
+return:: `true` if a hook has been replaced.
+conn:: The database connection to install the hook in
+func:: The signature of the function is `void(int op, core::string_view db, core::string_view table, sqlite3_int64 id)`.
+`op` is either `SQLITE_INSERT`, `SQLITE_DELETE` and `SQLITE_UPDATE`. The function must be noexcept.
+
+=== `preupdate_hook`
+
+NOTE: The https://www.sqlite.org/c3ref/preupdate_blobwrite.html[preupdate hook] requires
+sqlite to be required with `SQLITE_ENABLE_PREUPDATE_HOOK` true.
+
+This hook gets called before an update.
+
+[source,cpp]
+----
+struct preupdate_context
+{
+ // Returns the old value, i.e. the value before the update.
+ system::result<value> old(int column) const;
+ // The count of colums to be updated
+ int count() const;
+ // The nesting depth of the update.
+ int depth() const;
+ // The new value to be written to column
+ system::result<value> new_(int column) const;
+
+ // Query the status of blob access, e.g. when using blob_handle <1>
+ int blob_write() const;
+
+ explicit preupdate_context(sqlite3 * db) noexcept;
+};
+
+
+template<typename Func>
+bool preupdate_hook(connection & conn, Func && func);
+----
+<1> See https://www.sqlite.org/c3ref/preupdate_blobwrite.html[sqlite/preupdate_blobwrite]
+
+
+
+return:: `true` if a hook has been replaced.
+conn:: The database connection to install the hook in
+func:: The signature of the function is below:
+[source,cpp]
+----
+ void preupdate_hook(sqlite::preupdate_context ctx,
+ int op,
+ const char * db_name,
+ const char * table_name,
+ sqlite3_int64 current_key,
+ sqlite3_int64 new_key);
+----
+
+
+
+
+
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/json.adoc b/subprojects/boost-sqlite/doc/reference/json.adoc
new file mode 100644
index 0000000..1c15f52
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/json.adoc
@@ -0,0 +1,28 @@
+== `sqlite/json.hpp`
+
+The json header provides integration with boost/json.
+
+[source,cpp]
+----
+
+// The subtype value used by the sqlite json extension. See the [sqlite reference](https://www.sqlite.org/json1.html)
+constexpr int json_subtype = static_cast<int>('J');
+
+// Allow json to be used as a result from functions or vtables
+void tag_invoke(const struct set_result_tag &, sqlite3_context * ctx, const json::value & value);
+
+// Check if the value or field is a json.
+bool is_json(const value & v);
+bool is_json(const field & f);
+
+//Convert the value or field to a json.
+json::value as_json(const value & v, json::storage_ptr ptr = {});
+json::value as_json(const field & f, json::storage_ptr ptr = {});
+
+// Allow conversions to boost::json::value
+void tag_invoke( const json::value_from_tag &, json::value& val, const value & f);
+void tag_invoke( const json::value_from_tag &, json::value& val, const field & f);
+void tag_invoke( const json::value_from_tag &, json::value& val, resultset && rs);
+----
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/memory.adoc b/subprojects/boost-sqlite/doc/reference/memory.adoc
new file mode 100644
index 0000000..7bf62ef
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/memory.adoc
@@ -0,0 +1,29 @@
+== `sqlite/memory.hpp`
+
+The memory header provides C++-y access to the memory facilities of sqlite.
+
+[source,cpp,subs=+quotes]
+----
+// A tag to allow `operator new`
+struct memory_tag {};
+
+// new operators <1>
+void *operator new ( std::size_t size, boost::sqlite::memory_tag) noexcept;
+void *operator new[]( std::size_t size, boost::sqlite::memory_tag) noexcept;
+void operator delete ( void* ptr, boost::sqlite::memory_tag) noexcept;
+
+// A unique ptr that uses sqlite3_malloc / sqlite3_free
+template<typename T>
+using unique_ptr = std::unique_ptr<T, __implementation_detail__>;
+template<typename T, typename ... Args>
+unique_ptr<T> make_unique(Args && ... args);
+
+// Get the size of the allocated memory.
+template<typename T>
+std::size_t msize(const unique_ptr<T> & ptr);
+
+
+---
+<1> `new (sqlite::memory_tag{}) T()` uses sqlite3_malloc
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/meta_data.adoc b/subprojects/boost-sqlite/doc/reference/meta_data.adoc
new file mode 100644
index 0000000..6375108
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/meta_data.adoc
@@ -0,0 +1,36 @@
+== `sqlite/meta_data.hpp`
+
+The meta_data header provides some meta_data for columns.
+
+[source,cpp,subs=+quotes]
+----
+// The metadata of a column
+struct column_meta_data
+{
+ // Data type fo the column
+ cstring_ref data_type;
+ // Name of default collation sequence
+ cstring_ref collation;
+ // true if column has a NOT NULL constraint
+ bool not_null;
+ // true if column is part of the PRIMARY KEY
+ bool primary_key;
+ // true if column is AUTOINCREMENT
+ bool auto_increment;
+};
+
+// get the meta-data of one colum
+
+column_meta_data table_column_meta_data(connection & conn,
+ cstring_ref db_name, cstring_ref table_name, cstring_ref column_name,
+ system::error_code & ec, error_info &ei);
+column_meta_data table_column_meta_data(connection & conn,
+ cstring_ref table_name, cstring_ref column_name,
+ system::error_code & ec, error_info &ei);
+
+column_meta_data table_column_meta_data(connection & conn,
+ cstring_ref db_name, cstring_ref table_name, cstring_ref column_name);
+column_meta_data table_column_meta_data(connection & conn,
+ cstring_ref table_name, cstring_ref column_name);
+---
+
diff --git a/subprojects/boost-sqlite/doc/reference/mutex.adoc b/subprojects/boost-sqlite/doc/reference/mutex.adoc
new file mode 100644
index 0000000..d0493d6
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/mutex.adoc
@@ -0,0 +1,16 @@
+== `sqlite/mutex.hpp`
+
+The mutex header provides to std::mutex compatible classes using the sqlite mutex implementation.
+
+This will allow C++ code to use mutex code matching the configuration of sqlite.
+This may include the mutex being a noop.
+
+[source,cpp,subs=+quotes]
+----
+// similar to std::mutex
+struct mutex;
+// similar to std::recursive_mutexx
+struct recursive_mutex;
+----
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/result.adoc b/subprojects/boost-sqlite/doc/reference/result.adoc
new file mode 100644
index 0000000..0471b95
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/result.adoc
@@ -0,0 +1,40 @@
+== `sqlite/result.hpp`
+
+The result header is used by functions and vtables to turn resulting values into
+sqlite values. The `tag_invoke` interface is public and meant to extended.
+
+That is, implementing `tag_invoke(sqlite::set_result_tag, sqlite3_context, T);`
+will enable `T` to be used as a result by sqlite.
+
+[source,cpp]
+----
+// The tag
+struct set_result_tag {};
+
+// built-in result type
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, blob b);
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, zero_blob zb);
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, double dbl) { sqlite3_result_double(ctx, dbl); }
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, sqlite3_int64 value);inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::int64_t value);
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::nullptr_t);
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, string_view str);
+template<typename String>
+inline auto tag_invoke(set_result_tag, sqlite3_context * ctx, String && str);
+inline void tag_invoke(set_result_tag, sqlite3_context * , variant2::monostate);
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, const value & val);
+template<typename ... Args>
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, const variant2::variant<Args...> & var);
+
+template<typename T>
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr<T> ptr);
+template<typename T>
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr<T, void(*)(T*)> ptr);
+template<typename T, typename Deleter>
+inline auto tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr<T> ptr);
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, error err);
+template<typename T>
+inline void tag_invoke(set_result_tag tag, sqlite3_context * ctx, result<T> res);
+inline void tag_invoke(set_result_tag tag, sqlite3_context * ctx, result<void> res):
+----
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/resultset.adoc b/subprojects/boost-sqlite/doc/reference/resultset.adoc
new file mode 100644
index 0000000..fb793a5
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/resultset.adoc
@@ -0,0 +1,72 @@
+== `sqlite/resultset.hpp`
+
+A resultset represents the results of a query.
+
+[source,cpp]
+----
+// Representation of a result from a query.
+
+struct resultset
+{
+ // Returns the current row. The row is a pure view type.
+ row current() const &;
+ // Checks if the last row has been reached.
+ bool done() const;
+
+ // Read the next row. Returns false if there's nothing more to read.
+ // Calling this will change the data of any `row` previously obtained from `current.
+ bool read_next(system::error_code & ec, error_info & ei);
+ bool read_next();
+
+ // The number of colums in the resultset
+ std::size_t column_count() const;
+ // Returns the name of the column idx.
+ string_view column_name(std::size_t idx) const;
+ // Returns the name of the source table for column idx.
+ string_view table_name(std::size_t idx) const;
+ // Returns the origin name of the column for column idx.
+ string_view column_origin_name(std::size_t idx) const;
+
+ // The input iterator can be used to read every row in a for-loop
+ struct iterator
+ {
+ using value_type = value;
+ using difference_type = int;
+ using reference = value&;
+ using iterator_category = std::forward_iterator_tag;
+
+ iterator() {}
+
+ bool operator!=(iterator rhs) const;
+ row &operator*();
+ row *operator->();
+
+ iterator operator++();
+ iterator operator++(int);
+ };
+
+ // Return an input iterator to the currently unread row
+ iterator begin();
+ // Sentinel iterator.
+ iterator end();
+};
+----
+
+
+
+
+.Example
+[source,cpp]
+----
+extern sqlite::connection conn;
+
+sqlite::resultset rs = conn.query("select * from users;");
+
+do
+{
+ handle_row(r.current());
+}
+while (rs.read_next()); // read it line by line
+----
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/row.adoc b/subprojects/boost-sqlite/doc/reference/row.adoc
new file mode 100644
index 0000000..d3af982
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/row.adoc
@@ -0,0 +1,56 @@
+== `sqlite/row.hpp`
+
+This type represents one row of data from a query. Is a random-access range.
+
+WARNING: This type is a pure view type, and will be invalidated when the next row is read.
+
+
+
+[source,cpp]
+----
+
+struct row
+{
+ // The size of the row, i.e. number of colums
+ std::size_t size() const;
+
+ // Returns the field at `idx`, @throws std::out_of_range
+ field at(std::size_t idx) const;
+ // Returns the field at `idx`.
+ field operator[](std::size_t idx) const;
+
+ // Random access iterator used to iterate over the columns.
+ struct const_iterator
+ {
+ using difference_type = int;
+ using reference = field&;
+ using iterator_category = std::random_access_iterator_tag;
+
+ const_iterator & operator++();
+ const_iterator operator++(int);
+ const_iterator & operator--();
+ const_iterator operator--(int);
+
+ field operator[](int i) const;
+
+ const_iterator operator+(int i) const;
+ const_iterator operator-(int i) const;
+ const_iterator & operator+=(int i);
+ const_iterator & operator-=(int i);
+ const field & operator*() const;
+ const field * operator->() const;
+
+ bool operator==(const const_iterator& other) const;
+ bool operator!=(const const_iterator& other) const;
+ bool operator<(const const_iterator& other) const;
+ bool operator>(const const_iterator& other) const;
+
+ const_iterator() = default;
+ };
+ // Returns the begin of the column-range.
+ const_iterator begin() const;
+
+ // Returns the end of the column-range.
+ const_iterator end() const
+};
+----
diff --git a/subprojects/boost-sqlite/doc/reference/statement.adoc b/subprojects/boost-sqlite/doc/reference/statement.adoc
new file mode 100644
index 0000000..6ce89e4
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/statement.adoc
@@ -0,0 +1,165 @@
+== `sqlite/statement.hpp`
+
+=== `param_ref`
+
+A reference to a value to temporary bind for an execute statement. Most values are captures by reference.
+
+[source,cpp]
+----
+struct param_ref
+{
+ // Default construct a parameter, gives `null`.
+ param_ref() = default;
+ // Bind null
+ param_ref(variant2::monostate);
+ // Bind null
+ param_ref(std::nullptr_t);
+ // Bind an integer.
+ template<typename I>
+ param_ref(I value);
+ // Bind a blob.
+ param_ref(blob_view blob);
+ // Bind a string.
+ param_ref(string_view text);
+
+ template<typename StringLike>
+ param_ref(StringLike && text);
+ template<typename BlobLike>
+ param_ref(BlobLike && text);
+
+ // Bind a floating point value.
+ param_ref(double value) : impl_(value) { }
+ // Bind a zero_blob value, i.e. a blob that initialized by zero.
+ param_ref(zero_blob zb) : impl_(zb) { }
+
+ // Bind pointer value to the parameter. @see https://www.sqlite.org/bindptr.html
+ // Requires sqlite 3.20
+ // Deleter must a function pointer or trivially constructible.
+ template<typename T, typename Deleter>
+ param_ref(std::unique_ptr<T, Deleter> ptr);
+
+
+
+ // Apply the param_ref to a statement.
+ int apply(sqlite3_stmt * stmt, int c) const;
+
+ // Construct param_ref from a variant
+ template<typename T>
+ param_ref(T && t);
+
+----
+
+
+=== `statement`
+
+A statement used for a prepared-statement.
+
+
+
+[source,cpp]
+----
+struct statement
+{
+ // execute the prepared statement once. This transfers ownership to the resultset
+ template <typename ArgRange = std::initializer_list<param_ref>>
+ resultset execute(ArgRange && params, system::error_code& ec, error_info& info) &&; // <1>
+ template <typename ArgRange = std::initializer_list<param_ref>
+ resultset execute(ArgRange && params) &&; // <1>
+ resultset execute(
+ std::initializer_list<std::pair<string_view, param_ref>> params,
+ system::error_code& ec,
+ error_info& info) &&; // <2>
+
+ resultset execute(std::initializer_list<std::pair<string_view, param_ref>> params) &&;
+ template<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>>
+ static_resultset<T, Strict> execute(
+ ArgRange && params,
+ system::error_code & ec,
+ error_info & ei) &&; // <1>
+
+ template<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>>
+ static_resultset<T, Strict> execute(ArgRange && params) &&; // <1>
+
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> execute(
+ std::initializer_list<std::pair<string_view, param_ref>> params,
+ system::error_code & ec,
+ error_info & ei) &&; // <2>
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> execute(std::initializer_list<std::pair<string_view, param_ref>> params) &&; // <2>
+
+
+ template <typename ArgRange = std::initializer_list<param_ref>>
+ resultset execute(
+ ArgRange && params,
+ system::error_code& ec,
+ error_info& info) &; // <1>
+
+
+ template <typename ArgRange = std::initializer_list<param_ref>>
+ resultset execute(ArgRange && params) &; // <1>
+
+
+ resultset execute(
+ std::initializer_list<std::pair<string_view, param_ref>> params,
+ system::error_code& ec,
+ error_info& info) &; // <2>
+
+ resultset execute(std::initializer_list<std::pair<string_view, param_ref>> params) &; // <2>
+
+ template<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>>
+ static_resultset<T, Strict> execute(
+ ArgRange && params,
+ system::error_code & ec,
+ error_info & ei) &; // <1>
+
+ template<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>>
+ static_resultset<T, Strict> execute(ArgRange && params) &; // <1>
+
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> execute(
+ std::initializer_list<std::pair<string_view, param_ref>> params,
+ system::error_code & ec,
+ error_info & ei) &; // <2>
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> execute(std::initializer_list<std::pair<string_view, param_ref>> params) &; // <2>
+
+
+
+ // Returns the sql used to construct the prepared statement.
+ stringe_view sql();
+
+ // Returns the expanded sql used to construct the prepared statement. Requires sqlite 3.14
+ stringe_view expanded_sql();
+
+ // Returns the expanded sql used to construct the prepared statement. requiers sqlite to be compiles with SQLITE_ENABLE_NORMALIZE.
+ stringe_view normalized_sql();
+
+ // Returns the declared type of the column
+ string_view declared_type(int id) const;
+};
+----
+<1> Executes a query with positional arguments
+<2> Executes a query with named arguments (from a map-like object)
+
+
+
+
+
+WARNING: The `&&` overloads transfer ownership to the resultset, while the `&` keep them in the statement.
+That is, this is UB:
+[source,cpp]
+----
+resultset get_users(sqlite::connection & conn)
+{
+ auto s = conn.prepare("SELECT * from users where name = ?");
+ return s.execute({"allen"}); // UB, because result set points into s
+}
+
+resultset get_users(sqlite::connection & conn)
+{
+ // correct, because resultset takes ownershipo
+ return conn.prepare("SELECT * from users where name = ?").execute({"allen"});
+}
+----
+
diff --git a/subprojects/boost-sqlite/doc/reference/static_resultset.adoc b/subprojects/boost-sqlite/doc/reference/static_resultset.adoc
new file mode 100644
index 0000000..0e15e25
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/static_resultset.adoc
@@ -0,0 +1,98 @@
+== `sqlite/static_resultset.hpp`
+
+A `static_resultset` represents the results of a query matched to a C++ type
+
+[source,cpp]
+----
+// Representation of a result from a query.
+
+struct resultset
+{
+
+template<typename T, bool Strict >
+struct static_resultset
+{
+ // Returns the current row.
+ T current() const &;
+
+ // Returns the current row.
+ T current(system::error_code & ec, error_info & ei) const &;
+ // Checks if the last row has been reached.
+ bool done() const {return result_.done();}
+
+ // Read the next row. Returns false if there's nothing more to read.
+ bool read_next(system::error_code & ec, error_info & ei);
+ bool read_next();
+
+
+ // The number of columes in the resultset
+ std::size_t column_count() const;
+ // Returns the name of the column idx.
+ string_view column_name(std::size_t idx) const;
+
+ // Returns the name of the source table for column idx.
+ string_view table_name(std::size_t idx) const;
+ // Returns the origin name of the column for column idx.
+ string_view column_origin_name(std::size_t idx) const;
+
+ static_resultset() = default;
+ static_resultset(resultset && result)
+
+ static_resultset(static_resultset<T, false> && rhs);
+
+ /// The input iterator can be used to read every row in a for-loop
+ struct iterator
+ {
+ using value_type = T;
+ using difference_type = int;
+ using reference = T&;
+ using iterator_category = std::forward_iterator_tag;
+
+ iterator();
+ explicit iterator(resultset::iterator itr);
+ bool operator!=(iterator rhs) const;
+
+ value_type &operator*();
+ value_type *operator->();
+
+ iterator& operator++();
+ iterator operator++(int);
+ };
+
+ /// Return an input iterator to the currently unread row
+ iterator begin();
+ /// Sentinel iterator.
+ iterator end();
+
+ // Convert the static_result to a strict version
+ static_resultset<T, true> strict() &&
+ {
+ return {std::move(result_)};
+ }
+};
+----
+
+
+T:: The static type of the query. This must be a tuple or pfr compatible (for C++20) or described.
+Strict:: Disables implicit conversions.
+
+
+.Example
+[source,cpp]
+----
+extern sqlite::connection conn;
+struct user { std::string first_name; std::string last_name; };
+BOOST_DESCRIBE_STRUCT(user, (), (first_name, last_name));
+
+sqlite::resultset rs = conn.query("select first_name, last_name from users;");
+
+do
+{
+user usr = r.current();
+handle_row(u);
+}
+while (rs.read_next()) // read it line by line
+
+----
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/string.adoc b/subprojects/boost-sqlite/doc/reference/string.adoc
new file mode 100644
index 0000000..21660f9
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/string.adoc
@@ -0,0 +1,18 @@
+== `sqlite/string.hpp`
+
+This string header exposes some sqlite utility functions in a C++y way.
+
+
+[source,cpp]
+----
+bool like(cstring_ref lhs, cstring_ref rhs, char escape = '\0'); // <1>
+bool glob(cstring_ref lhs, cstring_ref rhs); // <2>
+int icmp(cstring_ref lhs, cstring_ref rhs); // <3>
+int icmp(string_view lhs, string_view rhs, std::size_t n); // <4>
+----
+<1> uses https://www.sqlite.org/c3ref/strlike.html[strlike]
+<2> uses https://www.sqlite.org/c3ref/strglob.html[strglob]
+<3> used https://www.sqlite.org/c3ref/stricmp.html[stricmp]
+<4> used https://www.sqlite.org/c3ref/stricmp.html[strnicmp]
+
+
diff --git a/subprojects/boost-sqlite/doc/reference/transaction.adoc b/subprojects/boost-sqlite/doc/reference/transaction.adoc
new file mode 100644
index 0000000..b6bc449
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/transaction.adoc
@@ -0,0 +1,104 @@
+== `sqlite/transaction.hpp`
+
+=== `transaction`
+
+A simple transaction guard implementing RAAI for transactions
+
+.Definition
+[source,cpp]
+----
+struct transaction
+{
+ // The mode of the transaction
+ enum behaviour {deferred, immediate, exclusive};
+ // A tag to use, to adopt an already initiated transaction.
+ constexpr static struct adopt_transaction_t {} adopt_transaction{};
+
+ // Create transaction guard on an existing transaction
+ transaction(connection & conn, adopt_transaction_t);
+
+
+ // Create transaction guard and initiate a transaction
+ transaction(connection & conn);
+
+ // Create transaction guard and initiate a transaction with the defined behaviour
+ transaction(connection & conn, behaviour b) ;
+
+ // see https://www.sqlite.org/lang_transaction.html re noexcept
+ // rollback the transaction if not committed.
+ ~transaction() noexcept(SQLITE_VERSION_NUMBER >= 3007011);
+
+
+ // Commit the transaction.
+ void commit();
+ void commit(system::error_code & ec, error_info & ei);
+ // Rollback the transaction explicitly.
+ void rollback();
+ void rollback(system::error_code & ec, error_info & ei);
+
+};
+----
+
+
+
+.Example
+[source,cpp]
+----
+sqlite::connection conn;
+conn.connect("./my-database.db");
+
+sqlite::transaction t{conn};
+conn.prepare("insert into log (text) values ($1)").execute(std::make_tuple("booting up"));
+t.commit();
+----
+
+=== `savepoint`
+
+A simple transaction guard implementing RAAI for savepoints. Savepoints can be used recursively.
+
+.Definition
+[source,cpp]
+----
+
+struct savepoint
+{
+ // A tag to use, to adopt an already initiated transaction.
+ constexpr static transaction::adopt_transaction_t adopt_transaction{};
+
+ // Create savepoint guard on an existing savepoint
+ savepoint(connection & conn, std::string name, transaction::adopt_transaction_t);
+
+ // Create transaction guard and initiate it
+ savepoint(connection & conn, std::string name);
+
+ // rollback to the savepoint if not committed.
+ ~savepoint() noexcept(SQLITE_VERSION_NUMBER >= 3007011);
+
+ // Commit/Release the transaction.
+ void commit();
+ void commit(system::error_code & ec, error_info & ei);
+
+ void release();
+ void release(system::error_code & ec, error_info & ei);
+
+ // Rollback the transaction explicitly.
+ void rollback();
+ void rollback(system::error_code & ec, error_info & ei);
+ // The name of the savepoint.
+
+ const std::string & name() const;
+};
+----
+
+
+.Example
+[source,cpp]
+----
+sqlite::connection conn;
+conn.connect("./my-database.db");
+
+sqlite::savepoint t{conn, "my-savepoint};
+conn.prepare("insert into log (text) values ($1)").execute(std::make_tuple("booting up"));
+t.commit();
+----
+
diff --git a/subprojects/boost-sqlite/doc/reference/value.adoc b/subprojects/boost-sqlite/doc/reference/value.adoc
new file mode 100644
index 0000000..f5cb3de
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/value.adoc
@@ -0,0 +1,85 @@
+== `sqlite/value.hpp`
+
+=== `value_type`
+
+The https://www.sqlite.org/datatype3.html)[type of a value].
+
+[source,cpp]
+----
+enum class value_type
+{
+ // An integral value
+ integer = SQLITE_INTEGER,
+ // A floating piont value
+ floating = SQLITE_FLOAT,
+ // A textual value
+ text = SQLITE_TEXT,
+ // A binary value
+ blob = SQLITE_BLOB,
+ // No value
+ null = SQLITE_NULL,
+};
+
+// Get the name as a string
+const char * value_type_name(value_type vt);
+----
+
+=== `value`
+
+A holder for a sqlite values used for internal APIs.
+
+[source,cpp]
+----
+
+struct value
+{
+ // The value for integers in the database
+ typedef sqlite3_int64 int64 ;
+
+ // The type of the value
+ value_type type() const;
+ // The subtype of the value.
+ int subtype() const;
+
+ // Is the held value null
+ bool is_null() const;
+ // Is the held value is not null
+ explicit operator bool () const;
+ // Returns the value as an `integer`.
+ int64 get_int() const;
+ // Returns the value as an `double`.
+ double get_double() const;
+ // Returns the value as text, i.e. a string_view. Note that this value may be invalidated`.
+ cstring_ref get_text() const;
+ // Returns the value as blob, i.e. raw memory. Note that this value may be invalidated`.
+ blob_view get_blob() const;
+
+ // Best numeric datatype of the value
+ value_type numeric_type() const;
+
+ // True if the column is unchanged in an UPDATE against a virtual table.
+ // requires sqlite 3.32
+ bool nochange() const;
+ // True if value originated from a bound parameter
+ // requires sqlite 3.31
+ bool from_bind() const;
+
+
+ // Construct value from a handle.
+ explicit value(sqlite3_value * value_) noexcept : value_(value_) {}
+
+ // The handle of the value.
+ using handle_type = sqlite3_value *;
+ // Returns the handle.
+ handle_type handle() const;
+ handle_type & handle();
+
+ // Get a value that was passed through the pointer interface.
+ // A value can be set as a pointer by binding/returning a unique_ptr.
+ // Rquires sqlite 3.20
+ template<typename T>
+ T * get_pointer();
+
+};
+----
+
diff --git a/subprojects/boost-sqlite/doc/reference/vtable.adoc b/subprojects/boost-sqlite/doc/reference/vtable.adoc
new file mode 100644
index 0000000..5063ffc
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/reference/vtable.adoc
@@ -0,0 +1,275 @@
+== `sqlite/vtable.hpp`
+
+Please read the <<vtables, virtual tables section>> for a more detailed explanation.
+
+[source,cpp]
+----
+namespace vtab
+{
+
+
+// Helper type to set a function through the xFindFunction callback
+struct function_setter
+{
+ /** Set the function
+ *
+ * The function can either take a single argument, a `span<sqlite::value, N>`
+ * for scalar functions,
+ * or a `context<Args...>` as first, and the span as second for aggegrate functions.
+ *
+ */
+ template<typename Func>
+ void set(Func & func);
+ template<typename ... Args, std::size_t Extent>
+ void set(void(* ptr)(context<Args...>, span<value, Extent>)) noexcept;
+ template<typename T, typename ... Args, std::size_t Extent>
+ void set(T(* ptr)(context<Args...>, span<value, Extent>));
+
+ template<std::size_t Extent>
+ void set(void(* ptr)(span<value, Extent>));
+ template<typename T, std::size_t Extent>
+ void set(T(* ptr)(span<value, Extent>));
+};
+
+
+// requires Sqlite 3.38
+// Utility function that can be used in `xFilter` for the `in` operator. <1>
+
+struct in
+{
+ struct iterator
+ {
+ iterator() = default;
+
+ iterator & operator++();
+ iterator operator++(int);
+
+ const value & operator*() const;
+
+ const value * operator->() const;
+
+ bool operator==(const iterator& other) const;
+ bool operator!=(const iterator& other) const;
+ };
+
+ // Returns a forward iterator to the `in` sequence for an `in` constraint pointing to the begin.
+ iterator begin();
+ // Returns a forward iterator to the `in` sequence for an `in` constraint pointing to the end.
+ iterator end();
+explicit in(sqlite::value out);
+};
+
+
+
+// index info used by the find_index function <2>
+struct index_info
+{
+ // Returns constraints of the index.
+ span<const sqlite3_index_info::sqlite3_index_constraint> constraints() const;
+
+ // Returns ordering of the index.
+ span<const sqlite3_index_info::sqlite3_index_orderby> order_by() const;
+
+ span<sqlite3_index_info::sqlite3_index_constraint_usage> usage();
+
+
+ sqlite3_index_info::sqlite3_index_constraint_usage & usage_of(
+ const sqlite3_index_info::sqlite3_index_constraint & info);
+
+ // Receive the collation for the contrainst of the position. requires 3.22
+ const char * collation(std::size_t idx) const;
+
+ int on_conflict() const;
+
+ // Returns true if the constraint is distinct. requires sqlite 3.38
+ bool distinct() const;
+
+ // Requires sqlite 3.38
+ value * rhs_value(std::size_t idx) const;
+
+ void set_already_ordered();
+ void set_estimated_cost(double cost);
+ // requires sqlite 3.8.2
+ void set_estimated_rows(sqlite3_int64 rows);
+ // requires sqlite 3.9
+ void set_index_scan_flags(int flags);
+ // requires sqlite 3.10
+ std::bitset<64u> columns_used();
+
+ void set_index(int value);
+ void set_index_string(char * str, bool take_ownership = true);
+
+ sqlite3_index_info * info() const;
+ sqlite3 * db() const;
+};
+
+
+struct module_config
+{
+ // Can be used to set SQLITE_VTAB_INNOCUOUS. Requires sqlite 3.31
+ void set_innocuous();
+ // Can be used to set SQLITE_VTAB_DIRECTONLY. Requires sqlite 3.31
+ void set_directonly() {sqlite3_vtab_config(db_, SQLITE_VTAB_DIRECTONLY);}
+
+
+ // Can be used to set SQLITE_VTAB_CONSTRAINT_SUPPORT
+ void set_constraint_support(bool enabled = false);
+};
+
+template<typename Table>
+struct module
+{
+ using table_type = Table;
+
+ // Creates the instance
+ // The instance_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab.
+ // instance_type must have a member `declaration` that returns a `const char *` for the declaration.
+ virtual result<table_type> create(sqlite::connection db, int argc, const char * const argv[]) = 0;
+
+ // Create a table
+ // The table_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab.
+ // table_type must have a member `declaration` that returns a `const char *` for the declaration.
+ virtual result<table_type> connect(sqlite::connection db, int argc, const char * const argv[]) = 0;
+};
+
+template<typename Table>
+struct eponymous_module
+{
+ using table_type = Table;
+
+ // Creates the instance
+ // The instance_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab.
+ // instance_type must have a member `declaration` that returns a `const char *` for the declaration.
+ virtual result<table_type> connect(sqlite::connection db, int argc, const char * const argv[]) = 0;
+
+ eponymous_module(bool eponymous_only = false);
+
+ bool eponymous_only() const;'
+ protected:
+ bool eponymous_only_{false};
+
+};
+
+
+// The basis for vtable
+template<typename Cursor>
+struct table : protected sqlite3_vtab
+{
+ using cursor_type = Cursor;
+
+ virtual result<void> config(module_config &);
+
+ // The Table declaration to be used with sqlite3_declare_vtab
+ virtual const char *declaration() = 0;
+
+ // Destroy the storage = this function needs to be present for non eponymous tables
+ virtual result<void> destroy();
+
+ // Tell sqlite how to communicate with the table.
+ // Optional, this library will fill in a default function that leaves comparisons to sqlite.
+ virtual result<void> best_index(index_info & /*info*/);
+
+ // Start a search on the table.
+ // The cursor_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab_cursor.
+ virtual result<cursor_type> open() = 0;
+
+ // Get the connection of the vtable
+ sqlite::connection connection() const;
+
+ table(const sqlite::connection & conn);
+};
+
+
+// Cursor needs the following member.
+template<typename ColumnType = void>
+struct cursor : protected sqlite3_vtab_cursor
+{
+ using column_type = ColumnType;
+
+ // Apply a filter to the cursor. Required when best_index is implemented.
+ virtual result<void> filter(
+ int /*index*/, const char * /*index_data*/,
+ boost::span<sqlite::value> /*values*/)
+ {
+ return {system::in_place_error, SQLITE_OK};
+ }
+
+ // Returns the next row.
+ virtual result<void> next() = 0;
+
+ // Check if the cursor is and the end
+ virtual bool eof() = 0;
+
+ // Returns the result of a value. It will use the set_result functionality to create a an sqlite function. <3>
+ virtual result<column_type> column(int idx, bool no_change) = 0;
+ // Returns the id of the current row
+ virtual result<sqlite3_int64> row_id() = 0;
+
+ // Get the table the cursor is pointing to.
+ vtab::table<cursor> & table();
+ const vtab::table<cursor> & table() const;
+};
+
+
+// Group of functions for modifications. The table must inherit this to be modifiable.
+struct modifiable
+{
+ virtual result<void> delete_(sqlite::value key) = 0;
+ // Insert a new row
+ virtual result<sqlite_int64> insert(sqlite::value key, span<sqlite::value> values, int on_conflict) = 0;
+ // Update the row
+ virtual result<sqlite_int64> update(sqlite::value old_key, sqlite::value new_key, span<sqlite::value> values, int on_conflict) = 0;
+};
+
+// Group of functions to support transactions. The table must inherit this to support transactions.
+struct transaction
+{
+ // Begin a tranasction
+ virtual result<void> begin() = 0;
+ // synchronize the state
+ virtual result<void> sync() = 0;
+ // commit the transaction
+ virtual result<void> commit() = 0;
+ // rollback the transaction
+ virtual result<void> rollback() = 0;
+};
+
+// Base class to enable function overriding See `xFindFunction`.
+struct overload_functions
+{
+ virtual result<void> find_function(
+ function_setter fs,
+ int arg, const char * name) = 0; // <4>
+};
+
+// Support for recursive transactions. Requires sqlite 3.7.7
+struct recursive_transaction
+{
+ // Save the current state with to `i`
+ virtual result<void> savepoint(int i) = 0;
+ // Release all saves states down to `i`
+ virtual result<void> release(int i) = 0;
+ // Roll the transaction back to `i`.
+ virtual result<void> rollback_to(int i) = 0;
+};
+
+/** Register a vtable <5>
+ Returns a reference to the module as stored in the database. It's lifetime is managed by the database.
+*/
+template<typename T>
+auto create_module(connection & conn,
+ cstring_ref name,
+ T && module,
+ system::error_code & ec,
+ error_info & ei) -> typename std::decay<T>::type &;
+template<typename T>
+auto create_module(connection & conn,
+ const char * name,
+ T && module) -> typename std::decay<T>::type &;
+----
+<1> See https://www.sqlite.org/capi3ref.html#sqlite3_vtab_in_first[vtab_in_first]
+<2> See https://www.sqlite.org/vtab.html#xbestindex[best_index]
+<3> See https://www.sqlite.org/c3ref/vtab_nochange.html[vtab_no_change]
+<4> See https://www.sqlite.org/vtab.html#xfindfunction[find_function]
+<5> See https://www.sqlite.org/vtab.html[vtab]
+
diff --git a/subprojects/boost-sqlite/doc/tutorial.adoc b/subprojects/boost-sqlite/doc/tutorial.adoc
new file mode 100644
index 0000000..4ff91ff
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/tutorial.adoc
@@ -0,0 +1,139 @@
+= Accessing a database
+
+== Connecting to a database
+
+The `sqlite::connection` holds a https://www.sqlite.org/c3ref/sqlite3.html[connection handle],
+which is automatically closes on destruction.
+
+[source,cpp]
+.examples/tutorial.cpp
+----
+include::../example/tutorial.cpp[tag=conn_path]
+----
+
+Sqlite supports https://www.sqlite.org/inmemorydb.html[in memory databases],
+which are created with the special path `":memory:"`. This is provided as a constant,
+called `in_memory`.
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=conn_mem]
+----
+
+== Executing SQL
+
+Once the `conn` is created, the common first step is to create a database schema.
+This can be done with the `execute` command, which allows the execution of multiple statements with one function invocation.
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=execute]
+----
+
+Like most functions in sqlite, the `execute` function comes overloaded to allow two modes of error handling:
+- errors as exception
+- errors assigned to a `boost::system::error_code` and <<error_info>>
+
+The second version takes both types by reference after the main arguments, i.e.:
+
+.examples/tutorial_ec.cpp
+[source,cpp]
+----
+include::../example/tutorial_ec.cpp[tag=execute]
+----
+
+The `boost::system::error_code` holds the https://www.sqlite.org/rescode.html[actual integer representing the error],
+while `boost::sqlite::error_info` is a string-like type holding the https://www.sqlite.org/c3ref/errcode.html[error message].
+
+For brevity, the tutorial section will use the exception overloads. See the <<reference>> for details on the overloads.
+
+== Querying data
+
+Once the database provides data it can be queried like this:
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=query1]
+----
+
+The <<resultset>> is a forward range of the data queried from the database.
+The first row of the result is already read.
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=query2]
+----
+
+NOTE: The `resultset` has `begin()`/`end()` member functions, so that it can be used in a ranged for loop.
+
+The <<row>> type is a range of <<field>>s,
+which has all the information for the field requested.
+
+In many use-cases this is however superfluous because the structure of the table is known.
+
+To make this easy, you can call query with a type parameter (`query<T>`) which will yield a
+`static_resultset<T>`. `T` can either be a tuple of a struct.
+
+A tuple will assign the query results by order.
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=query3]
+----
+
+Using a struct will assign the query results by name, i.e. it will reorder results accordingly.
+If you're using a standard lower than C++20, the `struct` needs to be https://www.boost.org/doc/libs/develop/libs/describe/doc/html/describe.html#ref_boost_describe_struct::[described].
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=query_t]
+include::../example/tutorial.cpp[tag=query4]
+----
+
+The types allowed in the `static_resultset` are:
+
+- `std::int64_t`
+- `double`
+- <<blob, 'blob'>>, <<blob_view, `blob_view`>>
+- <<string>>, <<string_view>>
+- `std::optional<T>`/`boost::optional<T>` with any of the types above.
+
+WARNING: `blob_view` and `string_view` have a limited lifetime. They might be invalidated when the next row is read!
+
+By default, the `static_result` does not check types, following https://www.sqlite.org/datatype3.html[sqlite's general dynamic type system].
+You can eat type checks, by using strict mode:
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=query_strict]
+----
+
+== Statements
+
+To avoid https://xkcd.com/327/[sql injection], querys and execution should not be build dynamically, but be done with parameters.
+
+This is done by creating a statement and then executing it with parameters.
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=statement_insert]
+----
+
+The syntax in the sqlite provides multiple versions.
+`?` is a positional parameter, that can have an explicit index at the end (e.g. `?3`) as in the above example.
+`:`, `@`, `$` are named parameters, e.g. `$age`. Named parameters can make queries more readable and provide more checks.
+
+NOTE: See the https://www.sqlite.org/c3ref/bind_blob.html[sqlite syntax] for more details.
+
+A prepared statement can be used multiple times.
+
+[source,cpp]
+----
+include::../example/tutorial.cpp[tag=statement_multi_insert]
+----
+
+WARNING: Calling `.execute` on an lvalue of `statement` will transfer ownership to the `resultset`,
+while calling `.execute` on an rvalue will produce a `resultset` that points to the statement.
+
+NOTE: The result of an `.execute` can also be turned into a static_resultset<T> by using `.execute<T>`.
+
diff --git a/subprojects/boost-sqlite/doc/vtable.adoc b/subprojects/boost-sqlite/doc/vtable.adoc
new file mode 100644
index 0000000..d759de1
--- /dev/null
+++ b/subprojects/boost-sqlite/doc/vtable.adoc
@@ -0,0 +1,167 @@
+= Virtual tables
+[#vtables]
+
+https://www.sqlite.org/vtab.html[A Virtual Table] is an object that is registered with a <<connection>>.
+It looks like a regular table, but is actually invoking virtual methods internally.
+This allows users to expose functionality from C++ to sqlite through a table interface.
+
+NOTE: The implementation uses abstract base classes to make interfaces easy to implement.
+You can disable the use of virtual by defining `BOOST_SQLITE_VIRTUAL` which will yield linker errors instead.
+
+== Eponymous
+
+And eponymous table is a virtual table the exists without needing to be created from sqlite.
+
+Such a table can either expose global values or can be used to build https://www.sqlite.org/vtab.html#tabfunc2[table-valued functions].
+
+=== Eponymous value table
+
+The easiest example of a data structure to expose to sql, is a simple ordered map:
+
+[source,cpp]
+----
+container::flat_map<std::string, std::string> data;
+----
+
+To create a virtual table it needs to have a cursor first. The cursor is what runs over the table, akin to an iterator.
+
+[source,cpp]
+----
+include::../example/ordered_map.cpp[tag=cursor]
+----
+<1> Declare the value the cursor produces
+<2> The data the cursor operates on
+<3> Advance the cursor by one
+<4> The module is declared without ROW id, see below
+<5> Get the actual column value
+<6> Tell sqlite when the cursor reached its end
+
+The cursor above just runs over the ordered map, returning values.
+However, the map is always ordered, so the cursor can optimize the built-in filter for sqlite.
+That is, sqlite will filter values on its own, but it's more efficient to do that before returning them.
+
+This can be done in the `filter` function, which can be written as follows:
+
+[source,cpp]
+----
+include::../example/ordered_map.cpp[tag=filter]
+----
+<1> If the first idx is not null, it means the order is reversed (see best_index below)
+<2> The equal constraint can be fulfilled by setting the range to enclose the value.
+<3> A greater than/equal constraint can be fulfilled by moving the beginning of the range.
+<4> A lesser than/equal constraint can be fulfilled by moving the end of the range.
+
+NOTE: Constraints of the same can appear multiple times, so the filter must only narrow the range.
+
+After the cursor, the actual table needs to be implemented:
+
+[source,cpp]
+----
+include::../example/ordered_map.cpp[tag=table]
+----
+<1> Declare the table to not be read-only, which adds functions 4-6.
+<2> The declaration is a literal that tells sqlite the shape of the table. It includes `WITHOUT ROWID` as `cursor.row_id()` mustn't be used.
+<3> This function gets called when the table gets scanned, i.e. a cursor gets created.
+<4> Delete value from the table, inherited from `modifiable`.
+<5> Insert a value into the table, inherited from `modifiable`.
+<6> Update a value in the table, inherited from `modifiable`.
+
+Next, there also should be a `best_index` function. This function informs sqlite about the available
+filter features so that sqlite can omit the operations.
+
+[source,cpp]
+----
+include::../example/ordered_map.cpp[tag=best_index]
+----
+<1> The data being passed on as idxStr to `cursor.filter`.
+<2> These are the constraints handled by filter, so only those get encoded.
+<3> filter assumes idx != 0 means descending, i.e. inverted
+<4> Tell sqlite the data is ordered already.
+<5> Set the index information to be passed to filter.
+
+With that all defined, the only thing left is to declare the module & create it.
+
+[source,cpp]
+----
+include::../example/ordered_map.cpp[tag=module]
+----
+
+=== Function table
+
+An eponymous function can be used as a function. For this example, a `query` vtable will be used to
+parse the query part of a url and return it as a table:
+
+[source,sqlite]
+----
+include::../example/url.sql[tag=query]
+----
+
+[cols="1,1,1"]
+|===
+|0|name|boost
+|1|thingy|foo
+|2|name|sqlite
+|3|foo|
+|===
+
+To achieve this a cursor is needed:
+
+[source,cpp]
+----
+include::../example/url.cpp[tag=query_cursor]
+----
+<1> Return the parts of the query view as columns
+<2> Pick the input value here.
+
+This is the module & table declaration;
+
+[source,cpp]
+----
+include::../example/url.cpp[tag=query_boiler_plate]
+----
+<1> The hidden colum to use for the input value.
+<2> Assert only equal is used for input data.
+
+NOTE: See the https://www.sqlite.org/vtab.html#tabfunc2[sqlite documentation here].
+
+== Non-eponymous Tables
+
+A non-eponymous table is a table backed by an actual resource.
+
+The module needs to provide a `create` function in the module,
+in addition to the `connect` one.
+The table is required to have a `destroy` function that removes the resource.
+
+
+[source,cpp]
+----
+struct csv_module final : sqlite::vtab::module<csv_table>
+{
+ // create the csv file
+ sqlite::result<table_type> create(sqlite::connection /*db*/,
+ int argc, const char * const argv[]);
+
+ // connect to an existing csv file.
+ sqlite::result<table_type> connect(sqlite::connection /*db*/,
+ int argc, const char * const argv[]);
+
+};
+----
+
+The virtual table can be created with the special syntax:
+
+[source,cpp]
+----
+CREATE VIRTUAL TABLE csv_example USING csv_file(./csv-example.csv, username, first_name, last_name);
+----
+
+When the above query is executed `csv_module.create()` will be executed with `"./csv-example.csv", "username", "first_name", "last_name".
+
+If the database gets opened with a previously created virtual table,
+`csv_module.connect()` will be called with the same parameters.
+
+Executing `DROP TABLE csv_example` will call `destroy()` on the `csv_table`.
+
+NOTE: The `destroy` function will be invoked when the virtual table gets dropped,
+not when the connection gets closed.
+
diff --git a/subprojects/boost-sqlite/example/CMakeLists.txt b/subprojects/boost-sqlite/example/CMakeLists.txt
new file mode 100644
index 0000000..480ed7e
--- /dev/null
+++ b/subprojects/boost-sqlite/example/CMakeLists.txt
@@ -0,0 +1,20 @@
+
+file(GLOB_RECURSE ALL_EXAMPLES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
+file(GLOB_RECURSE ALL_EXAMPLES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
+
+foreach(SRC ${ALL_EXAMPLES})
+ get_filename_component(NAME ${SRC} NAME_WLE )
+
+ if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${NAME}.sql)
+ add_library(boost_sqlite_example_${NAME} SHARED ${SRC})
+ target_link_libraries(boost_sqlite_example_${NAME} PUBLIC Boost::sqlite_ext Boost::url)
+ target_compile_definitions(boost_sqlite_example_${NAME} PUBLIC BOOST_SQLITE_SEPARATE_COMPILATION=1)
+ set_property(TARGET boost_sqlite_example_${NAME} PROPERTY PREFIX "")
+ set_target_properties(boost_sqlite_example_${NAME} PROPERTIES OUTPUT_NAME ${NAME} POSITION_INDEPENDENT_CODE ON)
+ else()
+ add_executable(boost_sqlite_example_${NAME} ${SRC} )
+ target_link_libraries(boost_sqlite_example_${NAME} PUBLIC Boost::sqlite)
+ target_compile_definitions(boost_sqlite_example_${NAME} PUBLIC BOOST_SQLITE_SEPARATE_COMPILATION=1)
+ endif()
+endforeach()
+
diff --git a/subprojects/boost-sqlite/example/csv.cpp b/subprojects/boost-sqlite/example/csv.cpp
new file mode 100644
index 0000000..47fe288
--- /dev/null
+++ b/subprojects/boost-sqlite/example/csv.cpp
@@ -0,0 +1,273 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite.hpp>
+#include <boost/unordered_map.hpp>
+#include <boost/describe.hpp>
+#include <boost/variant2/variant.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/algorithm/string/trim.hpp>
+
+#include <fstream>
+#include <iostream>
+#include <vector>
+
+using namespace boost;
+
+// This example demonstrates how to use the vtable interface to read & write from a csv file.
+// The csv implementation is not efficient, but for demontration purposes.
+
+
+struct csv_data
+{
+ using row_type = std::vector<std::string>;
+ row_type names;
+ container::flat_map<sqlite3_int64 , row_type> rows;
+};
+
+csv_data::row_type read_line(std::string line)
+{
+ csv_data::row_type row;
+ std::string cell;
+ std::istringstream is{line};
+ while (std::getline(is, cell, ','))
+ {
+ boost::trim(cell);
+ row.push_back(std::move(cell));
+ }
+ return row;
+}
+
+
+std::istream & operator>>(std::istream & is, csv_data &cs)
+{
+ cs.names.clear();
+ cs.rows.clear();
+
+ std::string line;
+ if(std::getline(is, line))
+ cs.names = read_line(std::move(line));
+
+ sqlite3_int64 cnt = 1;
+ while(std::getline(is, line))
+ cs.rows.emplace(cnt++, read_line(std::move(line)));
+ return is;
+}
+
+
+std::ostream & operator<<(std::ostream & os, csv_data &cs)
+{
+
+ for (auto & nm : cs.names)
+ {
+ os << nm;
+ if (&nm == &cs.names.back())
+ os << '\n';
+ else
+ os << ", ";
+ }
+
+ for (auto & r : cs.rows)
+ for (auto & nm : r.second)
+ {
+ os << nm;
+ if (&nm == &r.second.back())
+ os << '\n';
+ else
+ os << ", ";
+ }
+
+ return os;
+}
+
+
+using iterator_type = typename container::flat_map<sqlite3_int64, csv_data::row_type>::const_iterator;
+
+struct csv_cursor final : sqlite::vtab::cursor<sqlite::string_view>
+{
+ iterator_type itr, end;
+
+ csv_cursor(iterator_type itr,
+ iterator_type end) : itr(itr), end(end)
+ {}
+
+ sqlite::result<void> next() {itr++; return {};}
+ sqlite::result<sqlite3_int64> row_id()
+ {
+ return itr->first;
+ }
+
+ sqlite::result<sqlite::string_view> column(int i, bool /* no_change */)
+ {
+ return itr->second.at(i);
+ }
+
+ bool eof() noexcept {return itr == end;}
+};
+
+struct csv_table final
+ : sqlite::vtab::table<csv_cursor>,
+ sqlite::vtab::modifiable,
+ sqlite::vtab::transaction
+{
+ std::string path;
+ csv_data data;
+ csv_data transaction_copy; // yeaup, inefficient, too.
+
+ csv_table(std::string path) : path(std::move(path))
+ {}
+
+ std::string decl;
+ const char * declaration()
+ {
+ if (decl.empty())
+ {
+ std::ostringstream oss;
+ decl = "create table x(";
+
+ for (auto & nm : data.names)
+ {
+ decl += nm;
+ if (&nm == &data.names.back())
+ decl += ");";
+ else
+ decl += ", ";
+ }
+ }
+ return decl.c_str();
+ }
+
+ sqlite::result<cursor_type> open()
+ {
+ return cursor_type{data.rows.cbegin(), data.rows.cend()};
+ }
+
+ sqlite::result<void> delete_(sqlite::value key)
+ {
+ data.rows.erase(key.get_int());
+ return {};
+ }
+ sqlite::result<sqlite_int64> insert(
+ sqlite::value /*key*/, span<sqlite::value> values, int /*on_conflict*/)
+ {
+ sqlite3_int64 id = 0;
+ if (!data.rows.empty())
+ id = std::prev(data.rows.end())->first + 1;
+ auto & ref = data.rows[id];
+ ref.reserve(values.size());
+ for (auto v : values)
+ ref.emplace_back(v.get_text());
+
+ return id;
+ }
+ sqlite::result<sqlite_int64> update(
+ sqlite::value update, sqlite::value new_key,
+ span<sqlite::value> values, int /*on_conflict*/)
+ {
+ if (!new_key.is_null())
+ throw std::logic_error("we can't manually set keys");
+
+ int i = 0;
+ auto & r = data.rows[update.get_int()];
+ for (auto val : values)
+ r[i].assign(val.get_text());
+
+ return 0u;
+ }
+
+ // we do not read the csv , but just dump it on
+ sqlite::result<void> begin() noexcept {transaction_copy = data; return {};}
+ sqlite::result<void> sync() noexcept {return {};}
+ sqlite::result<void> commit() noexcept
+ {
+ // ok, let's write to disk.
+ //fs.(0);
+ std::ofstream fs{path, std::fstream::trunc};
+ fs << data << std::flush;
+ return {};
+ }
+ sqlite::result<void> rollback() noexcept
+ {
+ data = std::move(transaction_copy);
+ return {};
+ }
+
+ sqlite::result<void> destroy() noexcept
+ {
+ std::remove(path.c_str());
+ return {};
+ }
+};
+
+// The implementation is very inefficient. Don't use this in production.
+struct csv_module final : sqlite::vtab::module<csv_table>
+{
+
+ sqlite::result<table_type> create(sqlite::connection /*db*/,
+ int argc, const char * const argv[])
+ {
+ if (argc < 4)
+ throw std::invalid_argument("Need filename as first parameter");
+
+ table_type tt{argv[3]};
+ tt.data.names.reserve(argc - 4);
+ for (int i = 4; i < argc; i++)
+ tt.data.names.emplace_back(argv[i]);
+ std::ofstream fs(tt.path, std::fstream::trunc);
+ fs << tt.data << std::flush;
+ return tt;
+ }
+
+ sqlite::result<table_type> connect(sqlite::connection /*db*/,
+ int argc, const char * const argv[])
+ {
+ if (argc < 4)
+ throw std::invalid_argument("Need filename as first parameter");
+
+ table_type tt{argv[3]};
+ tt.data.names.reserve(argc - 4);
+ for (int i = 4; i < argc; i++)
+ tt.data.names.emplace_back(argv[i]);
+ std::ifstream fs(tt.path, std::fstream::in);
+ // read the existing data
+ fs >> tt.data;
+
+ if (!std::equal(tt.data.names.begin(), tt.data.names.end(), argv+4, argv + argc))
+ throw std::runtime_error("Column names in csv do not match");
+
+ return tt;
+ }
+};
+
+
+int main (int /*argc*/, char * /*argv*/[])
+{
+ sqlite::connection conn{"./csv-example.db"};
+ sqlite::create_module(conn, "csv_file", csv_module());
+
+ const auto empty_csv = !conn.has_table("csv_example");
+ if (empty_csv)
+ conn.execute("CREATE VIRTUAL TABLE if not exists csv_example USING csv_file(./csv-example.csv, username, first_name, last_name);");
+
+ {
+ conn.execute("begin");
+ auto p = conn.prepare("insert into csv_example values (?, ?, ?)");
+ if (empty_csv)
+ p.execute({"anarthal", "ruben", "perez"});
+
+ p.execute({"pdimov", "peter", "dimov"});
+ p.execute({"klemens-morgenstern", "klemens", "morgenstern"});
+
+ if (empty_csv)
+ p.execute({"madmongo1", "richard", "hodges"});
+
+ conn.execute("commit");
+ }
+
+ conn.query("delete from csv_example where first_name in ('peter', 'klemens')");
+ return 0;
+}
diff --git a/subprojects/boost-sqlite/example/describe.cpp b/subprojects/boost-sqlite/example/describe.cpp
new file mode 100644
index 0000000..9b00f8b
--- /dev/null
+++ b/subprojects/boost-sqlite/example/describe.cpp
@@ -0,0 +1,202 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite.hpp>
+#include <boost/unordered_map.hpp>
+#include <boost/describe.hpp>
+#include <boost/variant2/variant.hpp>
+
+#include <iomanip>
+#include <iostream>
+
+// This example shows how a virtual table can be created using boost.describe
+
+using namespace boost;
+
+// add more conversions here if you need more types in the described struct
+void assign_value(std::int64_t & res, sqlite::value val) { res = val.get_int();}
+void assign_value(std::string & res, sqlite::value val) { res = val.get_text();}
+
+template<typename T>
+struct describe_cursor final : sqlite::vtab::cursor<>
+{
+ describe_cursor(
+ typename boost::unordered_map<sqlite3_int64, T>::const_iterator itr,
+ typename boost::unordered_map<sqlite3_int64, T>::const_iterator end
+ ) : itr(itr), end(end)
+ {
+ }
+
+ typename boost::unordered_map<sqlite3_int64, T>::const_iterator itr, end;
+ constexpr static std::size_t column_count = mp11::mp_size<describe::describe_members<T, describe::mod_any_access>>::value;
+
+ sqlite::result<void> next() {itr++; return {};}
+ sqlite::result<sqlite3_int64> row_id() {return itr->first;}
+
+ void column(sqlite::context<> ctx, int i, bool /* no_change */)
+ {
+ mp11::mp_with_index<column_count>(
+ i, [&](auto Idx)
+ {
+ using type = mp11::mp_at_c<describe::describe_members<T, describe::mod_public>, Idx>;
+ ctx.set_result(itr->second.*type::pointer);
+ });
+ }
+
+ bool eof() noexcept {return itr == end;}
+};
+
+template<typename T>
+struct describe_table final :
+ sqlite::vtab::table<describe_cursor<T>>,
+ sqlite::vtab::modifiable
+{
+ boost::unordered_map<sqlite3_int64, T> &data;
+ describe_table(boost::unordered_map<sqlite3_int64, T> &data) : data(data) {}
+
+ std::string decl;
+ const char * declaration()
+ {
+ if (!decl.empty())
+ return decl.c_str();
+ mp11::mp_for_each<describe::describe_members<T, describe::mod_public>>(
+ [&](auto elem)
+ {
+ if (decl.empty())
+ decl += "create table x(";
+ else
+ decl += ", ";
+
+ decl += elem.name;
+ });
+ decl += ");";
+ return decl.c_str();
+ }
+
+
+ sqlite3_int64 last_index = 0;
+
+ sqlite::result<describe_cursor<T>> open()
+ {
+ return describe_cursor<T>{data.begin(), data.end()};
+ }
+
+ sqlite::result<void> delete_(sqlite::value key)
+ {
+ data.erase(key.get_int());
+ return {};
+ }
+ sqlite::result<sqlite_int64> insert(sqlite::value key, span<sqlite::value> values, int /*on_conflict*/)
+ {
+ T res;
+ sqlite_int64 id = key.is_null() ? last_index++ : key.get_int();
+ auto vtr = values.begin();
+ mp11::mp_for_each<describe::describe_members<T, describe::mod_public>>(
+ [&](auto elem)
+ {
+ assign_value(res.*elem.pointer , *vtr);
+ vtr++;
+ });
+
+ auto itr = data.emplace(id, std::move(res)).first;
+ return itr->first;
+
+ }
+ sqlite::result<sqlite_int64> update(sqlite::value old_key, sqlite::value new_key,
+ span<sqlite::value> values, int /*on_conflict*/)
+ {
+ if (new_key.get_int() != old_key.get_int())
+ data.erase(old_key.get_int());
+ auto & res = data[new_key.get_int()];
+
+ auto vtr = values.begin();
+ mp11::mp_for_each<describe::describe_members<T, describe::mod_public>>(
+ [&](auto elem)
+ {
+ assign_value(res.*elem.pointer , *vtr);
+ vtr++;
+ });
+ return 0u;
+ }
+};
+
+template<typename T>
+struct describe_module final : sqlite::vtab::eponymous_module<describe_table<T>>
+{
+ boost::unordered_map<sqlite3_int64, T> data;
+ constexpr static std::size_t column_count = mp11::mp_size<describe::describe_members<T, describe::mod_any_access>>::value;
+ sqlite::result<describe_table<T>> connect(sqlite::connection ,
+ int, const char * const [])
+ {
+ return describe_table<T>{data};
+ }
+};
+
+
+void print_table(std::ostream & str, sqlite::resultset res)
+{
+ for (auto i = 0u; i < res.column_count(); i ++)
+ str << "| " << std::setfill(' ') << std::setw(15) << res.column_name(i) << " ";
+
+ str << "|\n";
+
+ for (auto i = 0u; i < res.column_count(); i ++)
+ str << "|-----------------";
+ str << "|\n";
+
+ for (auto && r : res)
+ {
+ for (auto i = 0u; i < res.column_count(); i ++)
+ str << "| " << std::setfill(' ') << std::setw(15) << r.at(i).get_text() << " ";
+
+ str << "|\n" ;
+ }
+ str << std::endl;
+}
+
+
+struct boost_library
+{
+ std::string name;
+ std::int64_t first_released;
+ std::int64_t standard;
+};
+
+BOOST_DESCRIBE_STRUCT(boost_library, (), (name, first_released, standard));
+
+
+int main (int /*argc*/, char * /*argv*/[])
+{
+ sqlite::connection conn{":memory:"};
+ auto & md = sqlite::create_module(conn, "boost_libraries", describe_module<boost_library>());
+
+ {
+ auto p = conn.prepare("insert into boost_libraries (name, first_released, standard) values ($name, $version, $std);");
+ p.execute({{"name", "process"}, {"version", 64}, {"std", 11}});
+ p.execute({{"name", "asio"}, {"version", 35}, {"std", 98}});
+ p.execute({{"name", "bimap"}, {"version", 35}, {"std", 98}});
+ p.execute({{"name", "circular_buffer"}, {"version", 35}, {"std", 98}});
+ p.execute({{"name", "mpi"}, {"version", 35}, {"std", 98}});
+ p.execute({{"name", "beast"}, {"version", 66}, {"std", 11}});
+ p.execute({{"name", "describe"}, {"version", 77}, {"std", 14}});
+ }
+
+
+ print_table(std::cout, conn.query("select * from boost_libraries;"));
+
+ // same as conn.execute("update boost_libraries set standard = 11 where standard = 98;");
+ for (auto & p : md.data)
+ if (p.second.standard == 98)
+ p.second.standard = 11;
+
+ print_table(std::cout, conn.query("select * from boost_libraries;"));
+
+ conn.prepare("delete from boost_libraries where name = ?").execute({"mpi"});
+ print_table(std::cout, conn.query("select * from boost_libraries;"));
+
+ return 0;
+}
diff --git a/subprojects/boost-sqlite/example/multi_index.cpp b/subprojects/boost-sqlite/example/multi_index.cpp
new file mode 100644
index 0000000..0ce4a84
--- /dev/null
+++ b/subprojects/boost-sqlite/example/multi_index.cpp
@@ -0,0 +1,433 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/key.hpp>
+#include <boost/optional.hpp>
+#include <algorithm>
+#include <iostream>
+
+using namespace boost;
+
+namespace mi = boost::multi_index;
+
+struct boost_library
+{
+ std::string name, version;
+};
+
+using my_container = mi::multi_index_container<
+ boost_library,
+ mi::indexed_by<
+ mi::ordered_unique<mi::key<&boost_library::name>>,
+ mi::ordered_non_unique<mi::key<&boost_library::version>>
+ >
+>;
+
+
+struct multi_index_cursor final
+ : sqlite::vtab::cursor<sqlite::string_view>
+{
+ my_container &data;
+
+ multi_index_cursor(my_container &data) : data(data) {}
+ bool inverse = false;
+ int index = 0;
+ using const_iterator = typename my_container::const_iterator;
+ using const_iterator1 = typename my_container::nth_index_const_iterator<0>::type;
+ using const_iterator2 = typename my_container::nth_index_const_iterator<1>::type;
+
+ const_iterator begin{data.begin()}, end{data.end()};
+ const_iterator1 begin1 = data.get<0>().cbegin(), end1 = data.get<0>().cend();
+ const_iterator2 begin2 = data.get<1>().cbegin(), end2 = data.get<1>().cend();
+
+ sqlite::result<void> next()
+ {
+ switch (index)
+ {
+ case 0: inverse ? end-- : begin++; break;
+ case 1: inverse ? end1-- : begin1++; break;
+ case 2: inverse ? end2-- : begin2++; break;
+ }
+ return {};
+ }
+
+ sqlite::result<sqlite3_int64> row_id()
+ {
+ static_assert(sizeof(const_iterator) <= sizeof(sqlite3_int64), "");
+
+ switch (index)
+ {
+ case 0: return reinterpret_cast<sqlite3_int64>(inverse ? &*std::prev(end ) : &*begin);
+ case 1: return reinterpret_cast<sqlite3_int64>(inverse ? &*std::prev(end1) : &*begin1);
+ case 2: return reinterpret_cast<sqlite3_int64>(inverse ? &*std::prev(end2) : &*begin2);
+ }
+
+ return sqlite3_int64();
+ }
+ sqlite::result<sqlite::string_view> column(int i, bool /*nochange*/)
+ {
+ const boost_library * lib;
+ switch (index)
+ {
+ case 0: lib = &(inverse ? *std::prev(end) : *begin); break;
+ case 1: lib = &(inverse ? *std::prev(end1) : *begin1); break;
+ case 2: lib = &(inverse ? *std::prev(end2) : *begin2); break;
+ }
+
+ const auto & elem = *lib;
+ if (i == 0)
+ return elem.name;
+ else
+ return elem.version;
+ }
+
+
+ sqlite::result<void> filter(int idx, const char * idxStr, span<sqlite::value> values)
+ {
+ inverse = (idx == 0b1000) & idx;
+ index = idx & 0b11;
+
+ boost::optional<sqlite::cstring_ref> lower, upper, equal;
+ int lower_op = 0, upper_op = 0;
+
+ bool surely_empty = false;
+
+ for (auto i = 0u; i < values.size(); i++)
+ {
+ auto txt = values[i].get_text();
+ switch (idxStr[i])
+ {
+ case SQLITE_INDEX_CONSTRAINT_EQ:
+ if (equal && (*equal != txt))
+ // two different equal constraints do that to you.
+ surely_empty = true;
+ else
+ equal.emplace(txt);
+ break;
+ case SQLITE_INDEX_CONSTRAINT_GT:
+ case SQLITE_INDEX_CONSTRAINT_GE:
+ if (lower == txt)
+ {
+ // pick the more restrictive one
+ if (lower_op == SQLITE_INDEX_CONSTRAINT_GE)
+ lower_op = idxStr[i];
+ }
+ else
+ {
+ lower = (std::max)(lower.value_or(txt), txt);
+ lower_op = idxStr[i];
+ }
+
+ break;
+ case SQLITE_INDEX_CONSTRAINT_LE:
+ case SQLITE_INDEX_CONSTRAINT_LT:
+ if (upper == txt)
+ {
+ if (upper_op == SQLITE_INDEX_CONSTRAINT_LT)
+ upper_op = idxStr[i];
+ }
+ else
+ {
+ upper = (std::min)(upper.value_or(txt), txt);
+ upper_op = idxStr[i];
+ }
+ break;
+ }
+ }
+
+ if (lower && equal && lower > equal)
+ surely_empty = true;
+
+ if (upper && equal && upper < equal)
+ surely_empty = true;
+ if (surely_empty)
+ {
+ end = begin;
+ end1 = begin1;
+ end2 = begin2;
+ return {};
+ }
+
+ switch (index)
+ {
+ default: break;
+ case 1:
+ if (equal)
+ std::tie(begin1, end1) = data.get<0>().equal_range(*equal);
+ else
+ {
+ if (lower)
+ {
+ if (lower_op == SQLITE_INDEX_CONSTRAINT_GE)
+ begin1 = data.get<0>().lower_bound(*lower);
+ else // SQLITE_INDEX_CONSTRAINT_GT
+ begin1 = data.get<0>().upper_bound(*lower);
+ }
+ if (upper)
+ {
+ if (upper_op == SQLITE_INDEX_CONSTRAINT_LE)
+ end1 = data.get<0>().upper_bound(*upper);
+ else // SQLITE_INDEX_CONSTRAINT_LT
+ end1 = data.get<0>().lower_bound(*upper);
+ }
+ break;
+ case 2:
+ if (equal)
+ std::tie(begin2, end2) = data.get<1>().equal_range(*equal);
+ else
+ {
+ if (lower)
+ {
+ if (lower_op == SQLITE_INDEX_CONSTRAINT_GE)
+ begin2 = data.get<1>().lower_bound(*lower);
+ else // SQLITE_INDEX_CONSTRAINT_GT
+ begin2 = data.get<1>().upper_bound(*lower);
+ }
+
+ if (upper)
+ {
+ if (upper_op == SQLITE_INDEX_CONSTRAINT_LE)
+ end2 = data.get<1>().upper_bound(*upper);
+ else // SQLITE_INDEX_CONSTRAINT_LT
+ end2 = data.get<1>().lower_bound(*upper);
+ }
+ }
+ break;
+ }
+ }
+ return {};
+ }
+
+ bool eof() noexcept
+ {
+ switch (index)
+ {
+ case 0: return begin == end;
+ case 1: return begin1 == end1;
+ case 2: return begin2 == end2;
+ default: return true;
+ }
+ }
+};
+
+struct map_impl final
+ : sqlite::vtab::table<multi_index_cursor>,
+ sqlite::vtab::modifiable
+{
+ my_container data;
+ const char * declaration() override
+ {
+ return R"(
+ create table url(
+ name text primary key unique not null,
+ version text);)";
+ }
+ enum indices // 32
+ {
+ no_index = 0b00000000,
+ equal = 0b00000001,
+ gt = 0b00000100,
+ ge = 0b00001100,
+ lt = 0000010000,
+ le = 0b00110000,
+ order_asc = 0b01000000,
+ order_desc = 0b10000000,
+ };
+
+ sqlite::result<cursor_type> open() override
+ {
+ return cursor_type(data);
+ }
+
+ sqlite::result<void> delete_(sqlite::value key) override
+ {
+ data.erase(key.get_text());
+ return {};
+ }
+ sqlite::result<sqlite_int64> insert(sqlite::value /*key*/, span<sqlite::value> values,
+ int /*on_conflict*/) override
+ {
+ data.insert({values[0].get_text(), values[1].get_text()});
+ return 0;
+ }
+
+ sqlite::result<sqlite_int64> update(sqlite::value old_key, sqlite::value new_key,
+ span<sqlite::value> values, int /*on_conflict*/) override
+ {
+ if (new_key.get_int() != old_key.get_int())
+ {
+
+ auto node = reinterpret_cast<my_container::value_type *>(old_key.get_int());
+ data.erase(data.iterator_to(*node));
+ }
+
+ auto res = data.insert({values[0].get_text(), values[1].get_text()});
+ if (!res.second)
+ data.replace(res.first, {values[0].get_text(), values[1].get_text()});
+ return 0;
+ }
+
+ sqlite::result<void> best_index(sqlite::vtab::index_info & info) override
+ {
+ // we're using the index to encode the mode, because it's simple enough.
+ // more complex application should use it as an index like intended
+
+ int idx = 0;
+ int idx_res = 0;
+ sqlite::unique_ptr<char[]> str;
+ // idx = 1 => name
+ // idx = 2 => version
+ if (!info.constraints().empty())
+ {
+ auto sz = info.constraints().size() + 1u;
+ str.reset(new (sqlite::memory_tag{}) char[sz]);
+ std::memset(str.get(), '\0', sz);
+ }
+
+ for (auto & ct : info.constraints())
+ {
+ if (idx_res == 0) // if we're first, set the thing
+ idx_res = (ct.iColumn + 1);
+ // check if we're already building an index
+ if (idx_res != (ct.iColumn + 1)) // wrong column, ignore.
+ continue;
+ if ( ct.usable != 0 ) // aye, that's us
+ {
+ switch (ct.op)
+ {
+ // we'll stick to these
+ case SQLITE_INDEX_CONSTRAINT_EQ: BOOST_FALLTHROUGH;
+ case SQLITE_INDEX_CONSTRAINT_GT: BOOST_FALLTHROUGH;
+ case SQLITE_INDEX_CONSTRAINT_GE: BOOST_FALLTHROUGH;
+ case SQLITE_INDEX_CONSTRAINT_LE: BOOST_FALLTHROUGH;
+ case SQLITE_INDEX_CONSTRAINT_LT:
+ str[idx] = ct.op;
+ info.usage_of(ct).argvIndex = ++idx; // use it -> value in this position in `filter`.
+ info.usage_of(ct).omit = 1; // tell sqlite that we're sure enough, so sqlite doesn't check
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (info.order_by().size() == 1u)
+ {
+ if ((info.order_by()[0].iColumn == 0)
+ || (idx == 0) || (idx == 1))
+ {
+ info.set_already_ordered();
+ if (info.order_by()[0].desc != 0)
+ idx |= 0b1001; // encode inversion, because why not ?
+ }
+ else if ((info.order_by()[0].iColumn == 0) || (idx == 0) || (idx == 2))
+ {
+ info.set_already_ordered();
+ if (info.order_by()[0].desc)
+ idx |= 0b1010; // encode inversion, because why not ?
+ }
+ }
+ info.set_index(idx_res);
+ if (str)
+ info.set_index_string(str.release(), true);
+ return {};
+ }
+};
+
+struct multi_index_map final : sqlite::vtab::eponymous_module<map_impl>
+{
+ sqlite::result<map_impl> connect(sqlite::connection, int, const char * const *)
+ {
+ return map_impl{};
+ }
+};
+
+
+std::initializer_list<std::tuple<std::string, std::string>> data = {
+ {"atomic", "1.53.0"},
+ {"chrono", "1.47.0"},
+ {"container", "1.48.0"},
+ {"context", "1.51.0"},
+ {"contract", "1.67.0"},
+ {"coroutine", "1.53.0"},
+ {"date_time", "1.29.0"},
+ {"exception", "1.36.0"},
+ {"fiber", "1.62.0"},
+ {"filesystem", "1.30.0"},
+ {"graph", "1.18.0"},
+ {"graph_parallel", "1.40.0"},
+ {"headers", "1.00.0"},
+ {"iostreams", "1.33.0"},
+ {"json", "1.75.0"},
+ {"locale", "1.48.0"},
+ {"log", "1.54.0"},
+ {"math", "1.23.0"},
+ {"mpi", "1.35.0"},
+ {"nowide", "1.73.0"},
+ {"program_options", "1.32.0"},
+ {"python", "1.19.0"},
+ {"random", "1.15.0"},
+ {"regex", "1.18.0"},
+ {"serialization", "1.32.0"},
+ {"stacktrace", "1.65.0"},
+ {"system", "1.35.0"},
+ {"test", "1.21.0"},
+ {"thread", "1.25.0"},
+ {"timer", "1.9.0"},
+ {"type_erasure", "1.54.0"},
+ {"url", "1.81.0"},
+ {"wave", "1.33.0"}
+};
+
+void print(std::ostream & os, sqlite::resultset rw, boost::source_location loc = BOOST_CURRENT_LOCATION)
+{
+ os << loc.file_name() << "(" << loc.line() << "): ";
+ os << "[";
+ for (auto & r : rw)
+ os << r.at(0).get_text() << ", ";
+ os << "]" << std::endl;
+}
+
+int main (int /*argc*/, char * /*argv*/[])
+{
+ sqlite::connection conn{":memory:"};
+ auto & m = sqlite::create_module(conn, "my_map", multi_index_map());
+ boost::ignore_unused(m);
+
+ {
+ auto p = conn.prepare("insert into my_map (name, version) values (?, ?);");
+ for (const auto & d : ::data)
+ p.execute(d);
+ }
+
+ print(std::cout, conn.query("select * from my_map order by name desc;"));
+ print(std::cout, conn.query("select * from my_map where name = 'url';"));
+ print(std::cout, conn.query("select * from my_map where name < 'url';"));
+ print(std::cout, conn.query("select * from my_map where name >= 'system' ;"));
+ print(std::cout, conn.query("select * from my_map where name >= 'system' and name < 'url' ;"));
+ print(std::cout, conn.query("select * from my_map where name > 'system' and name <= 'url' ;"));
+ print(std::cout, conn.query("select * from my_map where name > 'json';"));
+ print(std::cout, conn.query("select * from my_map where name >= 'json';"));
+ print(std::cout, conn.query("select * from my_map where name < 'json';"));
+
+ print(std::cout, conn.query("select * from my_map where name == 'json' order by name asc;"));
+ print(std::cout, conn.query("select * from my_map where name == 'json' and name == 'url';"));
+ print(std::cout, conn.query("select * from my_map where name == 'json' order by name desc;"));
+
+ print(std::cout, conn.query("select * from my_map where name < 'url' and name >= 'system' order by name desc;"));
+ print(std::cout, conn.query("select * from my_map where version == '1.81.0';"));
+
+ print(std::cout, conn.query("select * from my_map where version > '1.32.0' order by version desc;"));
+ conn.query("delete from my_map where version == '1.81.0';");
+ print(std::cout, conn.query("select * from my_map where name < 'system' and name <= 'system' ;"));
+
+
+ return 0;
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/example/ordered_map.cpp b/subprojects/boost-sqlite/example/ordered_map.cpp
new file mode 100644
index 0000000..26a285b
--- /dev/null
+++ b/subprojects/boost-sqlite/example/ordered_map.cpp
@@ -0,0 +1,304 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite.hpp>
+#include <boost/container/flat_map.hpp>
+#include <iostream>
+
+using namespace boost;
+
+// this examples shows how to expose an ordered map as a vtable.
+
+// tag::cursor[]
+struct ordered_map_cursor final : sqlite::vtab::cursor<sqlite::string_view> // <1>
+{
+ container::flat_map<std::string, std::string> &data;
+ ordered_map_cursor(container::flat_map<std::string, std::string> &data) : data(data) {}
+ bool inverse = false;
+
+ using const_iterator = typename container::flat_map<std::string, std::string>::const_iterator;
+ const_iterator begin{data.begin()}, end{data.end()}; // <2>
+
+ sqlite::result<void> next() override { if (inverse) end--; else begin++; return {};} // <3>
+
+ sqlite::result<sqlite3_int64> row_id() override
+ {
+ return {system::in_place_error, SQLITE_MISUSE, // <4>
+ "this shouldn't be called, we're omitting the row id"};
+ }
+ sqlite::result<sqlite::string_view> column(int i, bool /*nochange*/) override // <5>
+ {
+ auto & elem = inverse ? *std::prev(end) : *begin;
+
+ if (i == 0)
+ return elem.first;
+ else
+ return elem.second;
+ }
+ // end::cursor[]
+ //tag::filter[]
+ sqlite::result<void> filter(int idx, const char * idxStr, span<sqlite::value> values) override
+ {
+ if (idx != 0) // <1>
+ inverse = true;
+
+ for (auto i = 0u; i < values.size(); i ++)
+ {
+ auto txt = values[i].get_text();
+ switch (idxStr[i])
+ {
+ case SQLITE_INDEX_CONSTRAINT_EQ: // <2>
+ {
+ auto nw = data.equal_range(txt);
+ if (nw.first > begin)
+ begin = nw.first;
+ if (nw.second < end)
+ end = nw.second;
+ }
+
+ break;
+ case SQLITE_INDEX_CONSTRAINT_GT: // <3>
+ {
+ auto new_begin = data.find(txt);
+ new_begin ++;
+ if (new_begin > begin)
+ begin = new_begin;
+ }
+ break;
+ case SQLITE_INDEX_CONSTRAINT_GE: // <3>
+ {
+ auto new_begin = data.find(txt);
+ if (new_begin > begin)
+ begin = new_begin;
+ }
+ break;
+ case SQLITE_INDEX_CONSTRAINT_LE: // <4>
+ {
+ auto new_end = data.find(txt);
+ new_end++;
+ if (new_end < end)
+ end = new_end;
+
+ }
+ break;
+ case SQLITE_INDEX_CONSTRAINT_LT: // <4>
+ {
+ auto new_end = data.find(txt);
+ if (new_end < end)
+ end = new_end;
+ }
+ break;
+ }
+
+ }
+ return {};
+ }
+ //end::filter[]
+ // tag::cursor[]
+ bool eof() noexcept override // <6>
+ {
+ return begin == end;
+ }
+};
+// end::cursor[]
+
+
+// tag::table[]
+struct map_impl final
+ : sqlite::vtab::table<ordered_map_cursor>,
+ sqlite::vtab::modifiable // <1>
+
+{
+ container::flat_map<std::string, std::string> &data;
+ map_impl(container::flat_map<std::string, std::string> &data) : data(data) {}
+
+ const char * declaration() override // <2>
+ {
+ return R"(
+ create table my_map(
+ name text primary key unique not null,
+ data text) WITHOUT ROWID;)";
+ }
+
+
+ sqlite::result<cursor_type> open() override // <3>
+ {
+ return cursor_type{data};
+ }
+
+ sqlite::result<void> delete_(sqlite::value key) override // <4>
+ {
+ data.erase(key.get_text());
+ return {};
+ }
+ sqlite::result<sqlite_int64> insert(sqlite::value /*key*/, span<sqlite::value> values,
+ int /*on_conflict*/) override // <5>
+ {
+ data.emplace(values[0].get_text(), values[1].get_text());
+ return 0;
+ }
+
+ sqlite::result<sqlite_int64> update(sqlite::value old_key, sqlite::value new_key,
+ span<sqlite::value> values,
+ int /*on_conflict*/) override // <6>
+ {
+ if (new_key.get_int() != old_key.get_int())
+ data.erase(old_key.get_text());
+ data.insert_or_assign(values[0].get_text(), values[1].get_text());
+ return 0;
+ }
+
+ // end::table[]
+ // tag::best_index[]
+ sqlite::result<void> best_index(sqlite::vtab::index_info & info) override
+ {
+ // we're using the index to encode the mode, because it's simple enough.
+ // more complex application should use it as an index like intended
+
+ int idx = 0;
+ sqlite::unique_ptr<char[]> str; // <1>
+ if (info.constraints().size() > 0)
+ {
+ const auto sz = info.constraints().size()+1;
+ str.reset(static_cast<char*>(sqlite3_malloc(sz)));
+ std::memset(str.get(), '\0', sz);
+ }
+ else
+ return {};
+
+ for (auto i = 0u; i < info.constraints().size(); i++)
+ {
+ if ((idx & SQLITE_INDEX_CONSTRAINT_EQ) != 0)
+ break;
+ auto ct = info.constraints()[i];
+ if (ct.iColumn == 0
+ && ct.usable != 0) // aye, that's us
+ {
+ switch (ct.op) //<2>
+ {
+ // we'll stick to these
+ case SQLITE_INDEX_CONSTRAINT_EQ: BOOST_FALLTHROUGH;
+ case SQLITE_INDEX_CONSTRAINT_GT: BOOST_FALLTHROUGH;
+ case SQLITE_INDEX_CONSTRAINT_GE: BOOST_FALLTHROUGH;
+ case SQLITE_INDEX_CONSTRAINT_LE: BOOST_FALLTHROUGH;
+ case SQLITE_INDEX_CONSTRAINT_LT:
+ str[idx] = ct.op;
+ info.usage()[i].argvIndex = ++idx; // use it -> value in this position in `filter`.
+ info.usage()[i].omit = 1; // tell sqlite that we're sure enough, so sqlite doesn't check
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+
+ if (info.order_by().size() == 1 && info.order_by()[0].iColumn == 0)
+ {
+ idx |= info.order_by()[0].desc; // <3>
+ info.set_already_ordered(); // <4>
+ }
+
+ // <5>
+ info.set_index(idx);
+ if (str)
+ info.set_index_string(str.release(), true);
+
+ return {};
+ }
+ // end::best_index[]
+ // tag::table[]
+};
+
+// end::table[]
+
+// tag::module[]
+struct ordered_map_module final : sqlite::vtab::eponymous_module<map_impl>
+{
+ container::flat_map<std::string, std::string> data;
+ template<typename ... Args>
+ ordered_map_module(Args && ...args) : data(std::forward<Args>(args)...) {}
+
+ sqlite::result<map_impl> connect(
+ sqlite::connection /*conn*/, int /*argc*/, const char * const */*argv*/)
+ {
+ return map_impl{data};
+ }
+};
+// end::module[]
+
+
+
+std::initializer_list<std::pair<std::string, std::string>> init_data = {
+ {"atomic", "1.53.0"},
+ {"chrono", "1.47.0"},
+ {"container", "1.48.0"},
+ {"context", "1.51.0"},
+ {"contract", "1.67.0"},
+ {"coroutine", "1.53.0"},
+ {"date_time", "1.29.0"},
+ {"exception", "1.36.0"},
+ {"fiber", "1.62.0"},
+ {"filesystem", "1.30.0"},
+ {"graph", "1.18.0"},
+ {"graph_parallel", "1.40.0"},
+ {"headers", "1.00.0"},
+ {"iostreams", "1.33.0"},
+ {"json", "1.75.0"},
+ {"locale", "1.48.0"},
+ {"log", "1.54.0"},
+ {"math", "1.23.0"},
+ {"mpi", "1.35.0"},
+ {"nowide", "1.73.0"},
+ {"program_options", "1.32.0"},
+ {"python", "1.19.0"},
+ {"random", "1.15.0"},
+ {"regex", "1.18.0"},
+ {"serialization", "1.32.0"},
+ {"stacktrace", "1.65.0"},
+ {"system", "1.35.0"},
+ {"test", "1.21.0"},
+ {"thread", "1.25.0"},
+ {"timer", "1.9.0"},
+ {"type_erasure", "1.54.0"},
+ {"url", "1.81.0"},
+ {"wave", "1.33.0"}
+};
+
+void print(std::ostream & os, sqlite::resultset rw)
+{
+ os << "[";
+ for (auto & r : rw)
+ os << r.at(0).get_text() << ", ";
+ os << "]" << std::endl;
+}
+
+int main (int /*argc*/, char * /*argv*/[])
+{
+ sqlite::connection conn{":memory:"};
+
+ // tag::module[]
+ ordered_map_module & m = sqlite::create_module(conn, "my_map", ordered_map_module(init_data));
+ // end::module[]
+ boost::ignore_unused(m);
+
+ print(std::cout, conn.query("select * from my_map order by name desc;"));
+ print(std::cout, conn.query("select * from my_map where name = 'url';"));
+ print(std::cout, conn.query("select * from my_map where name < 'url' and name >= 'system' ;"));
+ print(std::cout, conn.query("select * from my_map where name > 'json';"));
+ print(std::cout, conn.query("select * from my_map where name >= 'json';"));
+ print(std::cout, conn.query("select * from my_map where name < 'json';"));
+ print(std::cout, conn.query("select * from my_map where name == 'json' order by name asc;"));
+ print(std::cout, conn.query("select * from my_map where name == 'json' order by name desc;"));
+
+ print(std::cout, conn.query("select * from my_map where name < 'url' and name >= 'system' order by name desc;"));
+ print(std::cout, conn.query("select * from my_map where data == '1.81.0';"));
+
+ conn.query("delete from my_map where data == '1.81.0';");
+
+ return 0;
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/example/tutorial.cpp b/subprojects/boost-sqlite/example/tutorial.cpp
new file mode 100644
index 0000000..fbfa4a3
--- /dev/null
+++ b/subprojects/boost-sqlite/example/tutorial.cpp
@@ -0,0 +1,136 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite.hpp>
+#include <boost/describe/class.hpp>
+
+#include <iostream>
+
+using namespace boost;
+
+// tag::query_t[]
+struct users { std::string name; std::int64_t age; };
+BOOST_DESCRIBE_STRUCT(users, (), (name, age));
+// end::query_t[]
+
+int main(int /*argc*/, char */*argv*/[])
+{
+#if defined(SQLITE_EXAMPLE_USE_DB)
+// tag::conn_path[]
+ sqlite::connection conn{"./my_db.db"};
+// end::conn_path[]
+#else
+// tag::conn_mem[]
+ sqlite::connection conn{sqlite::in_memory};
+// end::conn_mem[]
+#endif
+ // tag::execute[]
+ conn.execute(R"(
+ CREATE TABLE IF NOT EXISTS users (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ age INTEGER NOT NULL);
+ INSERT INTO users(name, age) VALUES('Alice', 30);
+ INSERT INTO users(name, age) VALUES('Bob', 25);
+ )");
+ // end::execute[]
+
+ // tag::query1[]
+ sqlite::resultset q = conn.query("SELECT name, age FROM users ORDER BY id ASC;");
+ // end::query1[]
+
+ // tag::query2[]
+ assert(q.current()[0].get_text() == "Alice");
+ assert(q.read_next()); // true if it's the last row!
+ assert(q.current()[0].get_text() == "Bob");
+ // end::query2[]
+
+
+ // tag::query3[]
+ for (const auto & t :
+ conn.query<std::tuple<sqlite::string_view, std::int64_t>>("SELECT name, age FROM users;"))
+ std::cout << "User " << std::get<0>(t) << " is " << std::get<1>(t) << " old." << std::endl;
+ // end::query3[]
+
+ // tag::query4[]
+ for (const auto & a : conn.query<users>("SELECT age, name FROM users;"))
+ std::cout << "User " << a.name << " is " << a.age << " old." << std::endl;
+ // end::query4[]
+
+ // tag::query_strict[]
+ for (const auto & a : conn.query<users>("SELECT age, name FROM users;").strict())
+ std::cout << "User " << a.name << " is " << a.age << " old." << std::endl;
+ // end::query_strict[]
+
+
+ // tag::statement_insert[]
+ conn.prepare("insert into users (name, age) values (?1, ?2), (?3, ?4)")
+ .execute({"Paul", 31, "Mark", 51});
+ // end::statement_insert[]
+
+
+ // tag::statement_multi_insert[]
+ {
+ sqlite::transaction t{conn}; // use a transaction to speed this up
+
+ auto st = conn.prepare(R"(insert into users ("name", age) values ($name, $age))");
+
+ st.execute({{"name", "Allen"}, {"age", 43}});
+ st.execute({{"name", "Tom"}, {"age", 84}});
+
+ t.commit();
+ }
+ // end::statement_multi_insert[]
+
+ // tag::to_upper[]
+ sqlite::create_scalar_function(
+ conn,
+ "to_upper",
+ [](sqlite::context<>, // <1>
+ boost::span<sqlite::value, 1u> val // <2>
+ ) -> std::string
+ {
+ if (val[0].type() != sqlite::value_type::text)
+ throw std::logic_error("Value must be string"); // <3>
+ auto txt = val[0].get_text();
+ std::string res;
+ res.resize(txt.size());
+ std::transform(txt.begin(), txt.end(), res.begin(), [](char c){return std::toupper(c);});
+ return res;
+ },
+ sqlite::deterministic // <4>
+ );
+
+ auto qu = conn.query("SELECT to_upper(name) FROM users WHERE name == 'Alice';");
+ assert(qu.current()[0].get_text() == "ALICE");
+ // end::to_upper[]
+
+
+ // tag::oldest[]
+ struct retirees
+ {
+ std::int64_t retirement_age;
+ std::int64_t count = 0u;
+ retirees(std::size_t retirement_age)
+ : retirement_age(retirement_age) {}
+
+
+ void step(span<sqlite::value, 1> args)
+ {
+ if (args[0].get_int() >= retirement_age)
+ count += 1;
+ }
+ std::int64_t final() { return count; }
+ };
+ sqlite::create_aggregate_function<retirees>(conn, "retirees", std::make_tuple(65));
+
+ q = conn.query("select retirees(age) from users;");
+ std::cout << "The amount of retirees is " << q.current()[0].get_text() << std::endl;
+ // end::oldest[]
+
+ return 0;
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/example/tutorial_ec.cpp b/subprojects/boost-sqlite/example/tutorial_ec.cpp
new file mode 100644
index 0000000..2c6af7f
--- /dev/null
+++ b/subprojects/boost-sqlite/example/tutorial_ec.cpp
@@ -0,0 +1,197 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite.hpp>
+#include <boost/describe/class.hpp>
+
+#include <iostream>
+
+using namespace boost;
+
+// tag::query_t[]
+struct users { std::string name; std::int64_t age; };
+BOOST_DESCRIBE_STRUCT(users, (), (name, age));
+// end::query_t[]
+
+int main(int /*argc*/, char */*argv*/[])
+{
+ system::error_code ec;
+ sqlite::error_info ei;
+
+ sqlite::connection conn;
+
+#if defined(SQLITE_EXAMPLE_USE_DB)
+ // tag::conn_path[]
+ conn.connect("./my_db.db", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, ec);
+ // end::conn_path[]
+#else
+ // tag::conn_mem[]
+ conn.connect(sqlite::in_memory, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, ec);
+ // end::conn_mem[]
+#endif
+
+ if (ec)
+ goto error;
+
+ // tag::execute[]
+ conn.execute(R"(
+ CREATE TABLE IF NOT EXISTS users (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ age INTEGER NOT NULL);
+ INSERT INTO users(name, age) VALUES('Alice', 30);
+ INSERT INTO users(name, age) VALUES('Bob', 25);
+ )", ec, ei);
+ // end::execute[]
+
+ if (ec)
+ goto error;
+
+{
+ // tag::query1[]
+ sqlite::resultset q = conn.query("SELECT name, age FROM users ORDER BY id ASC;", ec, ei);
+ if (ec)
+ goto error;
+ // end::query1[]
+
+ // tag::query2[]
+ assert(q.current()[0].get_text() == "Alice");
+ assert(q.read_next(ec, ei)); // true if it's the last row!
+ if (ec)
+ goto error;
+ assert(q.current()[0].get_text() == "Bob");
+ // end::query2[]
+}
+
+ // tag::query3[]
+ for (const auto & t :
+ conn.query<std::tuple<sqlite::string_view, std::int64_t>>("SELECT name, age FROM users;", ec, ei))
+ std::cout << "User " << std::get<0>(t) << " is " << std::get<1>(t) << " old." << std::endl;
+
+ if (ec)
+ goto error;
+
+ // end::query3[]
+
+ // tag::query4[]
+ for (const auto & a : conn.query<users>("SELECT age, name FROM users;", ec, ei))
+ std::cout << "User " << a.name << " is " << a.age << " old." << std::endl;
+ if (ec)
+ goto error;
+ // end::query4[]
+
+ // tag::query_strict[]
+ {
+ auto r = conn.query<users>("SELECT age, name FROM users;", ec, ei).strict();
+ while (r.read_next(ec, ei) && !ec)
+ {
+ // because this is strict, it takes ec & ei for conversion errors.
+ const auto & a = r.current(ec, ei);
+ if (ec)
+ break;
+ std::cout << "User " << a.name << " is " << a.age << " old." << std::endl;
+ }
+ }
+ if (ec)
+ goto error;
+
+ // end::query_strict[]
+
+
+ // tag::statement_insert[]
+
+ {
+ auto p = conn.prepare("insert into users (name, age) values (?1, ?2), (?3, ?4)", ec, ei);
+ if (!ec)
+ std::move(p).execute({"Paul", 31, "Mark", 51}, ec, ei);
+ if (ec)
+ goto error;
+ }
+ // end::statement_insert[]
+
+
+ // tag::statement_multi_insert[]
+ {
+ conn.execute("BEGIN TRANSACTION;", ec, ei);
+ if (ec)
+ goto error;
+ sqlite::transaction t{conn, sqlite::transaction::adopt_transaction}; // use a transaction to speed this up
+
+ auto st = conn.prepare(R"(insert into users ("name", age) values ($name, $age))", ec, ei);
+ if (!ec)
+ st.execute({{"name", "Allen"}, {"age", 43}}, ec, ei);
+ if (!ec)
+ st.execute({{"name", "Tom"}, {"age", 84}}, ec, ei);
+ if (!ec)
+ t.commit(ec, ei);
+
+ if (ec)
+ goto error;
+ }
+
+
+ // end::statement_multi_insert[]
+
+ // tag::to_upper[]
+ sqlite::create_scalar_function(
+ conn,
+ "to_upper",
+ [](sqlite::context<>, // <1>
+ boost::span<sqlite::value, 1u> val // <2>
+ ) -> sqlite::result<std::string>
+ {
+ if (val[0].type() != sqlite::value_type::text)
+ return sqlite::error(SQLITE_MISUSE, "Value must be string"); // <2>
+ auto txt = val[0].get_text();
+ std::string res;
+ res.resize(txt.size());
+ std::transform(txt.begin(), txt.end(), res.begin(), [](char c){return std::toupper(c);});
+ return res;
+ },
+ sqlite::deterministic // <3>
+ , ec, ei);
+ if (ec)
+ goto error;
+ {
+ auto qu = conn.query("SELECT to_upper(name) FROM users WHERE name == 'Alice';", ec, ei);
+ if (ec) goto error;
+ assert(qu.current()[0].get_text() == "ALICE");
+ }
+ // end::to_upper[]
+
+
+ // tag::oldest[]
+ struct retirees
+ {
+ std::int64_t retirement_age;
+ std::int64_t count = 0u;
+ retirees(std::size_t retirement_age)
+ : retirement_age(retirement_age) {}
+
+ void step(span<sqlite::value, 1> args) noexcept // no possible errors, no result needed
+ {
+ if (args[0].get_int() >= retirement_age)
+ count += 1;
+ }
+ std::int64_t final() noexcept { return count; }
+ };
+ sqlite::create_aggregate_function<retirees>(conn, "retirees", std::make_tuple(65), {}, ec, ei);
+ if (ec) goto error;
+
+ {
+ auto q = conn.query("select retirees(age) from users;", ec, ei);
+ if (ec) goto error;
+ std::cout << "The amount of retirees is " << q.current()[0].get_text() << std::endl;
+ }
+ // end::oldest[]
+
+ return 0;
+
+ error:
+ fprintf(stderr, "sqlite failure: %s - %s\n", ec.message().c_str(), ei.message().c_str());
+ return EXIT_FAILURE;
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/example/url.cpp b/subprojects/boost-sqlite/example/url.cpp
new file mode 100644
index 0000000..f5ed4f9
--- /dev/null
+++ b/subprojects/boost-sqlite/example/url.cpp
@@ -0,0 +1,315 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/extension.hpp>
+#include <boost/sqlite/vtable.hpp>
+#include <boost/system/result.hpp>
+#include <boost/url.hpp>
+
+using namespace boost;
+
+// tag::subtype[]
+constexpr int pct_subtype = static_cast<int>('U');
+
+void tag_invoke(sqlite::set_result_tag, sqlite3_context * ctx, urls::pct_string_view value)
+{
+ using boost::sqlite::ext::sqlite3_api;
+ // we're using the sqlite API here directly, because we need to set a different subtype
+ sqlite3_result_text(ctx, value.data(), value.size(), nullptr);
+ sqlite3_result_subtype(ctx, pct_subtype);
+}
+
+
+void tag_invoke(sqlite::set_result_tag, sqlite3_context * ctx, const urls::segments_encoded_view & value)
+{
+ using boost::sqlite::ext::sqlite3_api;
+ // we're using the sqlite API here directly, because we need to set a different subtype
+ sqlite3_result_text(ctx, value.buffer().data(), value.buffer().size(), nullptr);
+ sqlite3_result_subtype(ctx, pct_subtype);
+}
+// end::subtype[]
+
+struct url_cursor final
+ : sqlite::vtab::cursor<
+ variant2::variant<variant2::monostate, core::string_view, urls::pct_string_view>
+ >
+{
+ url_cursor(urls::url_view view) : view(view ) {}
+
+ urls::url_view view;
+ bool done{false};
+
+ sqlite::result<void> next() { done = true; return {};}
+
+ sqlite::result<sqlite3_int64> row_id() {return static_cast<sqlite3_int64>(0);}
+ sqlite::result<column_type> column(int i, bool /*nochange*/)
+ {
+ switch (i)
+ {
+ case 0: return view.scheme();
+ case 1: return view.encoded_user();
+ case 2: return view.encoded_password();
+ case 3: return view.encoded_host();
+ case 4: return view.port();
+ case 5: return view.encoded_path();
+ case 6: return view.encoded_query();
+ case 7: return view.encoded_fragment();
+ case 8: return view.buffer();
+ default:
+ return variant2::monostate{};
+ }
+ }
+ sqlite::result<void> filter(int /*idx*/, const char * /*idxStr*/, span<sqlite::value> values)
+ {
+ if (values.size() > 0u)
+ view = urls::parse_uri(values[0].get_text()).value();
+ return {};
+ }
+
+ bool eof() noexcept {return done || view.empty();}
+};
+
+struct url_wrapper final : sqlite::vtab::table<url_cursor>
+{
+ urls::url value;
+ const char * declaration() override
+ {
+ return R"(
+ create table url(
+ scheme text,
+ user text,
+ password text,
+ host text,
+ port text,
+ path text,
+ query text,
+ fragment text,
+ url text hidden);)";
+ }
+
+ sqlite::result<url_cursor> open() override
+ {
+ return url_cursor{value};
+ }
+
+ sqlite::result<void> best_index(sqlite::vtab::index_info & info) override
+ {
+ for (const auto & constraint : info.constraints())
+ {
+ if (constraint.iColumn == 8 && constraint.usable)
+ {
+ if (constraint.op != SQLITE_INDEX_CONSTRAINT_EQ)
+ return {SQLITE_MISUSE,
+ sqlite::error_info("query only support equality constraints")};
+ info.usage_of(constraint).argvIndex = 1;
+ info.set_index(1);
+ }
+ }
+
+ return {};
+ }
+};
+
+
+struct url_module final : sqlite::vtab::eponymous_module<url_wrapper>
+{
+ sqlite::result<url_wrapper> connect(sqlite::connection /*db*/,
+ int /*argc*/, const char * const */*argv*/)
+ {
+ return url_wrapper{};
+ }
+};
+
+struct segements_cursor final : sqlite::vtab::cursor<
+ variant2::variant<variant2::monostate, std::int64_t, core::string_view, urls::segments_encoded_view>>
+{
+ segements_cursor(urls::segments_encoded_view view) : view(view) {}
+ urls::segments_encoded_view view;
+ urls::segments_encoded_view::const_iterator itr{view.begin()};
+
+ sqlite::result<void> next() override { itr++; return {};}
+
+ sqlite::result<sqlite3_int64> row_id() override {return std::distance(view.begin(), itr);}
+ sqlite::result<column_type> column(int i, bool /*nochange*/) override
+ {
+ //nochange = true;
+ switch (i)
+ {
+ case 0: return std::distance(view.begin(), itr);
+ case 1: return *itr;
+ case 2: return view;
+ default:
+ return variant2::monostate{};
+ }
+ }
+ sqlite::result<void> filter(int /*idx*/, const char * /*idxStr*/,
+ span<sqlite::value> values) override
+ {
+ if (values.size() > 0u)
+ view = urls::segments_encoded_view(values[0].get_text());
+ itr = view.begin();
+ return {};
+ }
+ bool eof() noexcept override {return itr == view.end();}
+};
+
+struct segment_wrapper final : sqlite::vtab::table<segements_cursor>
+{
+ urls::segments_encoded_view value;
+ const char * declaration() override
+ {
+ return R"(
+ create table segments(
+ idx integer,
+ segment text,
+ segments text hidden);)";
+ }
+
+
+
+ sqlite::result<segements_cursor> open() override
+ {
+ return segements_cursor{value};
+ }
+
+ sqlite::result<void> best_index(sqlite::vtab::index_info & info) override
+ {
+ for (auto & constraint : info.constraints())
+ {
+ if (constraint.iColumn == 2
+ && constraint.usable)
+ {
+ if (constraint.op != SQLITE_INDEX_CONSTRAINT_EQ)
+ return {SQLITE_OK, sqlite::error_info("segments only support equality constraints")};
+ info.usage_of(constraint).argvIndex = 1;
+ info.set_index(1);
+ }
+ }
+
+ return {};
+ }
+};
+
+
+struct segments_module final : sqlite::vtab::eponymous_module<segment_wrapper>
+{
+ sqlite::result<segment_wrapper> connect(sqlite::connection /*conn*/,
+ int /*argc*/, const char * const */*argv*/)
+ {
+ return segment_wrapper{};
+ }
+};
+
+// tag::query_cursor[]
+struct query_cursor final : sqlite::vtab::cursor<
+ variant2::variant<variant2::monostate, std::int64_t, core::string_view, urls::pct_string_view>
+ >
+{
+ urls::params_encoded_view view;
+ urls::params_encoded_view::const_iterator itr{view.begin()};
+
+ sqlite::result<void> next() override { itr++; return {};}
+
+ sqlite::result<sqlite3_int64> row_id() override {return std::distance(view.begin(), itr);}
+ sqlite::result<column_type> column(int i, bool /*nochange*/) override // <1>
+ {
+ //nochange = true;
+ switch (i)
+ {
+ case 0: return std::distance(view.begin(), itr);
+ case 1: return itr->key;
+ case 2:
+ if (!itr->has_value)
+ return variant2::monostate{};
+ else
+ return itr->value;
+ case 3: return view.buffer();
+ default:
+ return variant2::monostate{};
+ }
+ }
+ sqlite::result<void> filter(int /*idx*/, const char * /*idxStr*/,
+ span<sqlite::value> values) override
+ {
+ if (values.size() > 0u) // <2>
+ view = urls::params_encoded_view(values[0].get_text());
+ itr = view.begin();
+
+ return {};
+ }
+ bool eof() noexcept override {return itr == view.end();}
+};
+// end::query_cursor[]
+
+// tag::query_boiler_plate[]
+struct query_wrapper final : sqlite::vtab::table<query_cursor>
+{
+ const char * declaration() override
+ {
+ return R"(
+ create table queries(
+ idx integer,
+ name text,
+ value text,
+ query_string text hidden);)"; // <1>
+ }
+
+ sqlite::result<query_cursor> open() override
+ {
+ return query_cursor{};
+ }
+
+ sqlite::result<void> best_index(sqlite::vtab::index_info & info) override
+ {
+ for (auto & constraint : info.constraints())
+ {
+ if (constraint.iColumn == 3
+ && constraint.usable)
+ {
+ if (constraint.op != SQLITE_INDEX_CONSTRAINT_EQ) // <2>
+ return sqlite::error{SQLITE_OK, "query only support equality constraints"};
+
+ info.usage_of(constraint).argvIndex = 1;
+ info.set_index(1);
+ }
+ }
+ return {};
+ }
+};
+
+struct query_module final : sqlite::vtab::eponymous_module<query_wrapper>
+{
+ sqlite::result<query_wrapper> connect(sqlite::connection /*conn*/,
+ int /*argc*/, const char * const */*argv*/)
+ {
+ return query_wrapper{};
+ }
+};
+// end::query_boiler_plate[]
+
+BOOST_SQLITE_EXTENSION(url, conn)
+{
+ sqlite::create_module(conn, "url", url_module{});
+ sqlite::create_module(conn, "segments", segments_module());
+
+ // tag::query_boiler_plate[]
+ sqlite::create_module(conn, "query", query_module());
+ // end::query_boiler_plate[]
+ sqlite::create_scalar_function(
+ conn, "pct_decode",
+ +[](boost::sqlite::context<> , boost::span<boost::sqlite::value, 1u> s)
+ {
+ return urls::pct_string_view(s[0].get_text()).decode();
+ });
+
+ sqlite::create_scalar_function(
+ conn, "pct_encode",
+ +[](boost::sqlite::context<> , boost::span<boost::sqlite::value, 1u> s)
+ {
+ return urls::encode(s[0].get_text(), urls::pchars);
+ });
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/example/url.sql b/subprojects/boost-sqlite/example/url.sql
new file mode 100644
index 0000000..813917e
--- /dev/null
+++ b/subprojects/boost-sqlite/example/url.sql
@@ -0,0 +1,19 @@
+SELECT load_extension('./url');
+
+-- invoke the url function to get any url by it's elements
+select scheme, user, password, host , port, path, query, fragment, "url"
+ from url('ws://echo.example.com/?name=boost&thingy=foo&name=sqlite&#demo');
+
+-- table-ize the segments of url
+select idx, segment from segments('/foo/bar/foo/xyz');
+
+-- tag::query[]
+-- table-ize the query of url
+select * from query('name=boost&thingy=foo&name=sqlite&foo');
+select * from query where query_string = 'name=boost&thingy=foo&name=sqlite&foo';
+-- end::query[]
+
+-- do a left join on the table, so we can use the table function to normalize data.
+select host , query.name, query.value
+from url('ws://echo.example.com/?name=boost&thingy=foo&name=sqlite#demo') left join query on query.query_string = url.query;
+
diff --git a/subprojects/boost-sqlite/include/boost/sqlite.hpp b/subprojects/boost-sqlite/include/boost/sqlite.hpp
new file mode 100644
index 0000000..474102a
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite.hpp
@@ -0,0 +1,38 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_HPP
+#define BOOST_SQLITE_HPP
+
+/** @defgroup reference Reference
+ *
+ * This page contains the documentation of the sqlite high-level API.
+ */
+
+#include <boost/sqlite/backup.hpp>
+#include <boost/sqlite/blob.hpp>
+#include <boost/sqlite/collation.hpp>
+#include <boost/sqlite/connection.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+#include <boost/sqlite/error.hpp>
+#include <boost/sqlite/field.hpp>
+#include <boost/sqlite/function.hpp>
+#include <boost/sqlite/meta_data.hpp>
+#include <boost/sqlite/memory.hpp>
+#include <boost/sqlite/hooks.hpp>
+#include <boost/sqlite/json.hpp>
+#include <boost/sqlite/resultset.hpp>
+#include <boost/sqlite/row.hpp>
+#include <boost/sqlite/statement.hpp>
+#include <boost/sqlite/static_resultset.hpp>
+#include <boost/sqlite/string.hpp>
+#include <boost/sqlite/transaction.hpp>
+#include <boost/sqlite/value.hpp>
+#include <boost/sqlite/vtable.hpp>
+
+#if defined(BOOST_SQLITE_COMPILE_EXTENSION)
+#include <boost/sqlite/extension.hpp>
+#endif
+
+#endif //BOOST_SQLITE_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/allocator.hpp b/subprojects/boost-sqlite/include/boost/sqlite/allocator.hpp
new file mode 100644
index 0000000..f449475
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/allocator.hpp
@@ -0,0 +1,48 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_ALLOCATOR_HPP
+#define BOOST_SQLITE_ALLOCATOR_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+template<typename T>
+struct allocator
+{
+ constexpr allocator() noexcept {}
+ constexpr allocator( const allocator& other ) noexcept {}
+ template< class U >
+ constexpr allocator( const allocator<U>& other ) noexcept {}
+
+#if defined(SQLITE_4_BYTE_ALIGNED_MALLOC)
+ constexpr static std::size_t alignment = 4u;
+#else
+ constexpr static std::size_t alignment = 8u;
+#endif
+
+ static_assert(alignof(T) <= alignment, "T alignment can't be fulfilled by sqlite");
+ [[nodiscard]] T* allocate( std::size_t n )
+ {
+ auto p = static_cast<T*>(sqlite3_malloc64(n * sizeof(T)));
+ if (p == nullptr)
+ boost::throw_exception(std::bad_alloc());
+ return p;
+ }
+ void deallocate( T* p, std::size_t)
+ {
+ return sqlite3_free(p);
+ }
+};
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_ALLOCATOR_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/backup.hpp b/subprojects/boost-sqlite/include/boost/sqlite/backup.hpp
new file mode 100644
index 0000000..737a28e
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/backup.hpp
@@ -0,0 +1,74 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_BACKUP_HPP
+#define BOOST_SQLITE_BACKUP_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+#include <boost/sqlite/error.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+struct connection ;
+
+///@{
+/**
+ @brief Backup a database
+ @ingroup reference
+
+ This function will create a backup of an existing database.
+ This can be useful to write an in memory database to disk et vice versa.
+
+ @param source The source database to backup
+ @param target The target of the backup
+ @param source_name The source database to read the backup from. Default is 'main'.
+ @param target_name The target database to write the backup to. Default is 'main'.
+
+ @par Error Handling
+
+ @throws system_error from overload without `ec` & `ei`
+
+ or you need to pass
+
+ @param ec The system::error_code to capture any possibly errors
+ @param ei Additional error_info when error occurs.
+
+ @par Example
+
+ @code{.cpp}
+
+ sqlite::connection conn{":memory:"};
+ {
+ sqlite::connection read{"./read_only_db.db", SQLITE_READONLY};
+ backup(read, target);
+ }
+
+ @endcode
+ */
+BOOST_SQLITE_DECL
+void
+backup(connection & source,
+ connection & target,
+ cstring_ref source_name,
+ cstring_ref target_name,
+ system::error_code & ec,
+ error_info & ei);
+
+BOOST_SQLITE_DECL
+void
+backup(connection & source,
+ connection & target,
+ cstring_ref source_name = "main",
+ cstring_ref target_name = "main");
+
+///@}
+
+BOOST_SQLITE_END_NAMESPACE
+
+
+#endif //BOOST_SQLITE_BACKUP_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/blob.hpp b/subprojects/boost-sqlite/include/boost/sqlite/blob.hpp
new file mode 100644
index 0000000..cd82363
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/blob.hpp
@@ -0,0 +1,156 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_BLOB_HPP
+#define BOOST_SQLITE_BLOB_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/memory.hpp>
+#include <boost/sqlite/error.hpp>
+#include <type_traits>
+#include <memory>
+#include <cstring>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+struct connection ;
+
+/// @brief a view to a binary large object @ingroup reference
+struct blob_view
+{
+ /// The data in the blob
+ const void * data() const {return data_;}
+ /// The size of the data int he blob, in bytes
+ std::size_t size() const {return size_;}
+ /// Construct a blob
+ blob_view(const void * data, std::size_t size) : data_(data), size_(size) {}
+
+ /// Construct an empty blob
+ blob_view() = default;
+ /// Construct a blob from some other blob-like structure.
+ template<typename T>
+ explicit blob_view(const T & value,
+ typename std::enable_if<
+ std::is_pointer<decltype(std::declval<T>().data())>::value
+ && std::is_convertible<decltype(std::declval<T>().size()), std::size_t>::value
+ >::type * = nullptr) : data_(value.data()), size_(value.size()) {}
+
+ inline blob_view(const struct blob & b);
+ private:
+ const void * data_{nullptr};
+ std::size_t size_{0u};
+};
+
+/// @brief Helper type to pass a blob full of zeroes without allocating extra memory. @ingroup reference
+enum class zero_blob : sqlite3_uint64 {};
+
+/// @brief An object that owns a binary large object. @ingroup reference
+struct blob
+{
+ /// The data in the blob
+ void * data() const {return impl_.get();}
+ /// The size of the data int he blob, in bytes
+ std::size_t size() const {return size_;}
+
+ /// Create a blob from a blob_view
+ explicit blob(blob_view bv)
+ {
+ impl_.reset(sqlite3_malloc(static_cast<int>(bv.size())));
+ size_ = bv.size();
+ std::memcpy(impl_.get(), bv.data(), size_);
+ }
+ /// Create an empty blob with size `n`.
+ explicit blob(std::size_t n) : impl_(sqlite3_malloc(static_cast<int>(n))), size_(n) {}
+
+ /// Construct an empty blob
+ constexpr blob() = default;
+ /// Release & take ownership of the blob.
+ void * release() && {return std::move(impl_).release(); }
+ private:
+ unique_ptr<void> impl_;
+ std::size_t size_{0u};
+};
+
+blob_view::blob_view(const blob & b) : data_(b.data()), size_(b.size()) {}
+
+/// @brief an object that holds a binary large object. Can be obtained by using @ref blob_handle. @ingroup reference
+struct blob_handle
+{
+ /// Default constructor
+ blob_handle() = default;
+
+ /// Construct from a handle. Takes owner ship
+ explicit blob_handle(sqlite3_blob * blob) : blob_(blob) {}
+
+ ///@{
+ /// @brief Reopen on another row
+ BOOST_SQLITE_DECL
+ void reopen(sqlite3_int64 row_id, system::error_code & ec);
+ BOOST_SQLITE_DECL
+ void reopen(sqlite3_int64 row_id);
+ ///@}
+
+ ///@{
+ /// @brief Read data from the blob
+ BOOST_SQLITE_DECL
+ void read_at(void *data, int len, int offset, system::error_code &ec);
+ BOOST_SQLITE_DECL
+ void read_at(void *data, int len, int offset);
+ ///@}
+
+ ///@{
+ /// @brief Write data to the blob
+ BOOST_SQLITE_DECL
+ void write_at(const void *data, int len, int offset, system::error_code &ec);
+ BOOST_SQLITE_DECL
+ void write_at(const void *data, int len, int offset);
+ ///@}
+
+ /// The size of the blob
+ std::size_t size() const {return static_cast<std::size_t>(sqlite3_blob_bytes(blob_.get()));}
+
+ /// The handle of the blob
+ using handle_type = sqlite3_blob*;
+ /// Returns the handle of the blob
+ handle_type handle() { return blob_.get(); }
+ /// Release the owned handle.
+ handle_type release() && { return blob_.release(); }
+ private:
+ struct deleter_
+ {
+ void operator()(sqlite3_blob *impl)
+ {
+ sqlite3_blob_close(impl);
+ }
+ };
+ std::unique_ptr<sqlite3_blob, deleter_> blob_;
+};
+
+///@{
+/// Open a blob for incremental access
+BOOST_SQLITE_DECL
+blob_handle open_blob(connection & conn,
+ cstring_ref db,
+ cstring_ref table,
+ cstring_ref column,
+ sqlite3_int64 row,
+ bool read_only,
+ system::error_code &ec,
+ error_info &ei);
+
+BOOST_SQLITE_DECL
+blob_handle open_blob(connection & conn,
+ cstring_ref db,
+ cstring_ref table,
+ cstring_ref column,
+ sqlite3_int64 row,
+ bool read_only = false);
+///}@
+
+
+
+BOOST_SQLITE_END_NAMESPACE
+
+
+#endif //BOOST_SQLITE_BLOB_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/collation.hpp b/subprojects/boost-sqlite/include/boost/sqlite/collation.hpp
new file mode 100644
index 0000000..a4624c4
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/collation.hpp
@@ -0,0 +1,140 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_COLLATION_HPP
+#define BOOST_SQLITE_COLLATION_HPP
+
+#include <boost/sqlite/connection.hpp>
+#include <boost/sqlite/detail/exception.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+///@{
+/** Define a custom collation function
+ @ingroup reference
+
+ @param conn A connection to the database in which to install the collation.
+ @param name The name of the collation.
+ @param func The function
+
+ The function must be callable with two `string_view` and return an int, indicating the comparison results.
+
+ @par Example
+
+ @code{.cpp}
+
+ // a case insensitive string omparison, e.g. from boost.urls
+ int ci_compare(string_view s0, string_view s1) noexcept;
+
+ extern sqlite::connection conn;
+
+ // Register the collation
+ sqlite::create_collation(conn, "iequal", &ci_compare);
+
+ // use the collation to get by name, case insensitively
+ conn.query("select first_name, last_name from people where first_name = 'Klemens' collate iequal;");
+
+ // order by names case insensitively
+ conn.query("select * from people order by last_name collate iequal asc;");
+
+ @endcode
+
+ */
+
+template<typename Func>
+void create_collation(
+ connection & conn,
+ cstring_ref name,
+ Func && func,
+ typename std::enable_if<
+ std::is_convertible<
+ decltype(func(string_view(), string_view())),
+ int>::value,
+ system::error_code>::type & ec)
+{
+ using func_type = typename std::decay<Func>::type;
+ unique_ptr<func_type> f{new (memory_tag{}) func_type(std::forward<Func>(func))};
+ if (f == nullptr)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_NOMEM);
+ return;
+ }
+ auto res = sqlite3_create_collation_v2(
+ conn.handle(),
+ name.c_str(),
+ SQLITE_UTF8,
+ f.get(),
+ +[](void * data, int len_l, const void * str_l, int len_r, const void * str_r) -> int
+ {
+ string_view l(static_cast<const char*>(str_l), len_l);
+ string_view r(static_cast<const char*>(str_r), len_r);
+ auto & impl = (*static_cast<func_type*>(data));
+ static_assert(noexcept(impl(l, r)),
+ "Collation function must be noexcept");
+ return impl(l, r);
+ },
+ +[](void * p) { delete_(static_cast<func_type*>(p)); }
+ );
+
+ if (res != SQLITE_OK)
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+ else
+ f.release();
+}
+
+
+template<typename Func>
+auto create_collation(
+ connection & conn,
+ cstring_ref name,
+ Func && func)
+#if !defined(BOOST_SQLITE_GENERATING_DOCS)
+ -> typename std::enable_if<
+ std::is_convertible<
+ decltype(func(string_view(), string_view())),
+ int>::value>::type
+#endif
+{
+ system::error_code ec;
+ create_collation(conn, name, std::forward<Func>(func), ec);
+ if (ec)
+ detail::throw_error_code(ec, BOOST_CURRENT_LOCATION);
+}
+
+
+inline void delete_collation(
+ connection & conn,
+ cstring_ref name,
+ system::error_code & ec)
+{
+ auto res = sqlite3_create_collation_v2(
+ conn.handle(),
+ name.c_str(),
+ SQLITE_UTF8,
+ nullptr, nullptr, nullptr);
+ if (res != SQLITE_OK)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+ }
+}
+
+
+inline auto delete_collation(
+ connection & conn,
+ cstring_ref name)
+{
+ system::error_code ec;
+ delete_collation(conn, name, ec);
+ if (ec)
+ detail::throw_error_code(ec, BOOST_CURRENT_LOCATION);
+}
+
+/// @}
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_COLLATION_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/connection.hpp b/subprojects/boost-sqlite/include/boost/sqlite/connection.hpp
new file mode 100644
index 0000000..0f6ccf8
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/connection.hpp
@@ -0,0 +1,210 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_CONNECTION_HPP
+#define BOOST_SQLITE_CONNECTION_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/error.hpp>
+#include <boost/sqlite/resultset.hpp>
+#include <boost/sqlite/statement.hpp>
+#include <memory>
+#include <boost/system/system_error.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+template<typename T, bool Strict>
+struct static_resultset;
+
+constexpr static cstring_ref in_memory = ":memory:";
+
+/** @brief main object for a connection to a database.
+ @ingroup reference
+
+ @par Example
+ @code{.cpp}
+ sqlite::connection conn;
+ conn.connect("./my-database.db");
+ conn.prepare("insert into log (text) values ($1)").execute(std::make_tuple("booting up"));
+ @endcode
+
+ */
+struct connection
+{
+ /// The handle of the connection
+ using handle_type = sqlite3*;
+ /// Returns the handle
+ handle_type handle() const { return impl_.get(); }
+ /// Release the owned handle.
+ handle_type release() && { return impl_.release(); }
+
+ ///Default constructor
+ connection() = default;
+ /// Construct the connection from a handle.
+ explicit connection(handle_type handle, bool take_ownership = true) : impl_(handle, take_ownership) {}
+ /// Move constructor.
+ connection(connection && ) = default;
+ /// Move assign operator.
+ connection& operator=(connection && ) = default;
+
+ /// Construct a connection and connect it to `filename`. `flags` is set by `SQLITE_OPEN_*` flags. @see https://www.sqlite.org/c3ref/c_open_autoproxy.html
+ connection(cstring_ref filename,
+ int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) { connect(filename, flags); }
+
+#if defined(BOOST_WINDOWS_API)
+ template<typename Path,
+ typename = std::enable_if_t<
+ std::is_same<typename Path::string_type, std::wstring>::value &&
+ std::is_constructible<cstring_ref, decltype(std::declval<Path>().string())>::value
+ >>
+ explicit connection(const Path & pth) : connection(pth.string()) {}
+#endif
+ ///@{
+ /// Connect the database to `filename`. `flags` is set by `SQLITE_OPEN_*` flags.
+ BOOST_SQLITE_DECL void connect(cstring_ref filename, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
+ BOOST_SQLITE_DECL void connect(cstring_ref filename, int flags, system::error_code & ec);
+ ///@}
+
+#if defined(BOOST_WINDOWS_API)
+ template<typename Path,
+ typename = std::enable_if_t<
+ std::is_same<typename Path::string_type, std::wstring>::value &&
+ std::is_constructible<cstring_ref, decltype(std::declval<Path>().string())>::value
+ >>
+ void connect(const Path & pth, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
+ {
+ connect(pth.string(), flags);
+ }
+
+
+ template<typename Path,
+ typename = std::enable_if_t<
+ std::is_same<typename Path::string_type, std::wstring>::value &&
+ std::is_constructible<cstring_ref, decltype(std::declval<Path>().string())>::value
+ >>
+ void connect(const Path & pth, int flags, system::error_code & ec)
+ {
+ connect(pth.string(), flags, ec);
+ }
+#endif
+
+ ///@{
+ /// Close the database connection.
+ BOOST_SQLITE_DECL void close();
+ BOOST_SQLITE_DECL void close(system::error_code & ec, error_info & ei);
+ ///@}
+
+ /// Check if the database holds a valid handle.
+ bool valid() const {return impl_ != nullptr;}
+
+
+ ///@{
+ /// Perform a query without parameters. Can only execute a single statement.
+ BOOST_SQLITE_DECL resultset query(
+ core::string_view q,
+ system::error_code & ec,
+ error_info & ei);
+
+ BOOST_SQLITE_DECL resultset query(core::string_view q);
+
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> query(
+ core::string_view q,
+ system::error_code & ec,
+ error_info & ei)
+ {
+ static_resultset<T, Strict> tmp = query(q, ec, ei);
+ if (ec)
+ return {};
+ tmp.check_columns_(ec, ei);
+ if (ec)
+ return {};
+
+ return tmp;
+ }
+
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> query(core::string_view q)
+ {
+ system::error_code ec;
+ error_info ei;
+ auto tmp = query<T, Strict>(q, ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return tmp;
+ }
+ ///@}
+
+ ///@{
+ /// Perform a query without parametert, It execute a multiple statement.
+ BOOST_SQLITE_DECL void execute(
+ cstring_ref q,
+ system::error_code & ec,
+ error_info & ei);
+
+ BOOST_SQLITE_DECL void execute(cstring_ref q);
+
+ template<typename StringLike,
+ typename = decltype(std::declval<const StringLike&>().c_str())>
+ void execute(
+ const StringLike& q,
+ system::error_code & ec,
+ error_info & ei)
+ {
+ execute(q.c_str(), ec, ei);
+ }
+ template<typename StringLike,
+ typename = decltype(std::declval<const StringLike&>().c_str())>
+ void execute(const StringLike & q) { execute(q.c_str());}
+ ///@}
+
+ ///@{
+ /// Perform a query with parameters. Can only execute a single statement.
+ BOOST_SQLITE_DECL statement prepare(
+ core::string_view q,
+ system::error_code & ec,
+ error_info & ei);
+
+ BOOST_SQLITE_DECL statement prepare(core::string_view q);
+ ///@}
+
+ /// Check if the database has the table
+ bool has_table(
+ cstring_ref table,
+ cstring_ref db_name = "main") const
+ {
+ return sqlite3_table_column_metadata(impl_.get(), db_name.c_str(), table.c_str(),
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)
+ == SQLITE_OK;
+ }
+
+ /// Check if the database has the table
+ bool has_column(
+ cstring_ref table,
+ cstring_ref column,
+ cstring_ref db_name = "main") const
+ {
+ return sqlite3_table_column_metadata(impl_.get(), db_name.c_str(), table.c_str(), column.c_str(),
+ nullptr, nullptr, nullptr, nullptr, nullptr)
+ == SQLITE_OK;
+ }
+ private:
+ struct deleter_
+ {
+ deleter_(bool owned = true) : owned_(owned) {}
+ bool owned_ = true;
+ void operator()(sqlite3 *impl)
+ {
+ if (owned_)
+ sqlite3_close_v2(impl);
+ }
+ };
+
+ std::unique_ptr<sqlite3, deleter_> impl_{nullptr, deleter_{}};
+};
+
+BOOST_SQLITE_END_NAMESPACE
+
+
+#endif //BOOST_SQLITE_CONNECTION_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/cstring_ref.hpp b/subprojects/boost-sqlite/include/boost/sqlite/cstring_ref.hpp
new file mode 100644
index 0000000..6747f45
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/cstring_ref.hpp
@@ -0,0 +1,223 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+// based on boost.process
+#ifndef BOOST_SQLITE_CSTRING_REF_HPP
+#define BOOST_SQLITE_CSTRING_REF_HPP
+
+#include <boost/config.hpp>
+#include <boost/core/detail/string_view.hpp>
+
+#include <cstddef>
+#include <type_traits>
+#include <string>
+
+
+#if __cplusplus >= 201702L
+#include <string_view>
+#endif
+
+namespace boost
+{
+namespace sqlite
+{
+
+/// Small wrapper for a null-terminated string that can be directly passed to C APIS
+/** This ref can only be modified by moving the front pointer. It does not store the
+ * size, but can detect values that can directly be passed to system APIs.
+ *
+ * It can be constructed from a `char*` pointer or any class that has a `c_str()`
+ * member function, e.g. std::string or boost::static_string.
+ *
+ */
+struct cstring_ref
+{
+ using value_type = char;
+ using traits_type = std::char_traits<char>;
+ BOOST_CONSTEXPR cstring_ref() noexcept : view_("") {}
+ BOOST_CONSTEXPR cstring_ref(std::nullptr_t) = delete;
+
+ BOOST_CONSTEXPR cstring_ref( const value_type* s ) : view_(s) {}
+
+ template<typename Source,
+ typename =
+ typename std::enable_if<
+ std::is_same<const value_type,
+ typename std::remove_pointer<decltype(std::declval<Source>().c_str())>::type
+ >::value>::type>
+ BOOST_CONSTEXPR cstring_ref(Source && src) : view_(src.c_str()) {}
+
+ BOOST_CONSTEXPR const char * c_str() const BOOST_NOEXCEPT
+ {
+ return this->data();
+ }
+
+ using string_view_type = core::string_view;
+ constexpr operator string_view_type() const {return view_;}
+
+#if __cplusplus >= 201702L
+ constexpr operator std::string_view() const {return view_;}
+#endif
+
+ using pointer = char *;
+ using const_pointer = const char *;
+ using reference = char &;
+ using const_reference = const char &;
+ using const_iterator = const_pointer;
+ using iterator = const_iterator;
+ using const_reverse_iterator = typename std::reverse_iterator<const_iterator>;
+ using reverse_iterator = typename std::reverse_iterator<iterator>;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+
+ static BOOST_CONSTEXPR size_type npos = -1;
+
+ BOOST_CONSTEXPR const_iterator begin() const BOOST_NOEXCEPT {return view_;};
+ BOOST_CONSTEXPR const_iterator end() const BOOST_NOEXCEPT {return view_ + length();};
+ BOOST_CONSTEXPR const_iterator cbegin() const BOOST_NOEXCEPT {return view_;};
+ BOOST_CONSTEXPR const_iterator cend() const BOOST_NOEXCEPT {return view_ + length();};
+
+#if defined(BOOST_NO_CXX17)
+ const_reverse_iterator rbegin() const BOOST_NOEXCEPT {return reverse_iterator(end());};
+ const_reverse_iterator rend() const BOOST_NOEXCEPT {return reverse_iterator(begin());};
+ const_reverse_iterator crbegin() const BOOST_NOEXCEPT {return reverse_iterator(end());};
+ const_reverse_iterator crend() const BOOST_NOEXCEPT {return reverse_iterator(begin());};
+#else
+ BOOST_CONSTEXPR const_reverse_iterator rbegin() const BOOST_NOEXCEPT {return reverse_iterator(end());};
+ BOOST_CONSTEXPR const_reverse_iterator rend() const BOOST_NOEXCEPT {return reverse_iterator(begin());};
+ BOOST_CONSTEXPR const_reverse_iterator crbegin() const BOOST_NOEXCEPT {return reverse_iterator(end());};
+ BOOST_CONSTEXPR const_reverse_iterator crend() const BOOST_NOEXCEPT {return reverse_iterator(begin());};
+#endif
+
+ BOOST_CONSTEXPR size_type size() const BOOST_NOEXCEPT {return length(); }
+ BOOST_CONSTEXPR size_type length() const BOOST_NOEXCEPT {return length_impl_(); }
+ BOOST_CONSTEXPR size_type max_size() const BOOST_NOEXCEPT {return static_cast<std::size_t>(-1); }
+ BOOST_ATTRIBUTE_NODISCARD BOOST_CONSTEXPR bool empty() const BOOST_NOEXCEPT {return *view_ == '\0'; }
+
+ BOOST_CONSTEXPR const_reference operator[](size_type pos) const {return view_[pos] ;}
+ BOOST_CXX14_CONSTEXPR const_reference at(size_type pos) const
+ {
+ if (pos >= size())
+ throw_exception(std::out_of_range("cstring-view out of range"));
+ return view_[pos];
+ }
+ BOOST_CONSTEXPR const_reference front() const {return *view_;}
+ BOOST_CONSTEXPR const_reference back() const {return view_[length() - 1];}
+ BOOST_CONSTEXPR const_pointer data() const BOOST_NOEXCEPT {return view_;}
+ BOOST_CXX14_CONSTEXPR void remove_prefix(size_type n) {view_ = view_ + n;}
+ void swap(cstring_ref& s) BOOST_NOEXCEPT {std::swap(view_, s.view_);}
+
+ size_type copy(value_type* s, size_type n, size_type pos = 0) const
+ {
+ return traits_type::copy(s, view_ + pos, n) - view_;
+ }
+ BOOST_CONSTEXPR cstring_ref substr(size_type pos = 0) const
+ {
+ return cstring_ref(view_ + pos);
+ }
+
+ BOOST_CXX14_CONSTEXPR string_view_type substr(size_type pos, size_type length) const
+ {
+ return string_view_type(view_).substr(pos, length);
+ }
+
+ BOOST_CXX14_CONSTEXPR int compare(cstring_ref x) const BOOST_NOEXCEPT
+ {
+ auto idx = 0u;
+ for (; view_[idx] != null_char_()[0] && x[idx] != null_char_()[0]; idx++)
+ if (!traits_type::eq(view_[idx], x[idx]))
+ return traits_type::lt(view_[idx], x[idx]) ? -1 : 1;
+
+ return traits_type::to_int_type(view_[idx]) -
+ traits_type::to_int_type(x[idx]); // will compare to null char of either.
+ }
+
+ BOOST_CXX14_CONSTEXPR bool starts_with(string_view_type x) const BOOST_NOEXCEPT
+ {
+ if (x.empty())
+ return true;
+
+ auto idx = 0u;
+ for (; view_[idx] != null_char_()[0] && idx < x.size(); idx++)
+ if (!traits_type::eq(view_[idx], x[idx]))
+ return false;
+
+ return idx == x.size() || view_[idx] != null_char_()[0];
+ }
+ BOOST_CONSTEXPR bool starts_with(value_type x) const BOOST_NOEXCEPT
+ {
+ return traits_type::eq(view_[0], x);
+ }
+
+ BOOST_CXX14_CONSTEXPR size_type find( char ch, size_type pos = 0 ) const BOOST_NOEXCEPT
+ {
+ for (auto p = view_ + pos; *p != *null_char_(); p++)
+ if (traits_type::eq(*p, ch))
+ return p - view_;
+ return npos;
+ }
+
+
+ friend BOOST_CXX14_CONSTEXPR bool operator==(cstring_ref x, cstring_ref y) BOOST_NOEXCEPT
+ {
+ std::size_t idx = 0u;
+ for (idx = 0u; x[idx] != null_char_()[0] && y[idx] != null_char_()[0]; idx++)
+ if (!traits_type::eq(x[idx], y[idx]))
+ return false;
+ return x[idx] == y[idx];
+ }
+ friend BOOST_CXX14_CONSTEXPR bool operator!=(cstring_ref x, cstring_ref y) BOOST_NOEXCEPT
+ {
+ std::size_t idx = 0u;
+ for (idx = 0u; x[idx] != null_char_()[0] &&
+ y[idx] != null_char_()[0]; idx++)
+ if (!traits_type::eq(x[idx], y[idx]))
+ return true;
+ return x[idx] != y[idx];
+ }
+ friend BOOST_CXX14_CONSTEXPR bool operator< (cstring_ref x, cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) < 0;}
+ friend BOOST_CXX14_CONSTEXPR bool operator> (cstring_ref x, cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) > 0;}
+ friend BOOST_CXX14_CONSTEXPR bool operator<=(cstring_ref x, cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) <= 0;}
+ friend BOOST_CXX14_CONSTEXPR bool operator>=(cstring_ref x, cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) >= 0;}
+
+ // modifiers
+ void clear() BOOST_NOEXCEPT { view_ = null_char_(); } // Boost extension
+
+ std::basic_string<value_type, traits_type> to_string() const
+ {
+ return std::basic_string<char, traits_type>(begin(), end());
+ }
+
+ template<typename Allocator>
+ std::basic_string<value_type, traits_type, Allocator> to_string(const Allocator& a) const
+ {
+ return std::basic_string<value_type, traits_type, Allocator>(begin(), end(), a);
+ }
+
+ template<class A> operator std::basic_string<char, std::char_traits<char>, A>() const
+ {
+ return std::basic_string<char, std::char_traits<char>, A>( view_ );
+ }
+
+ private:
+ BOOST_CONSTEXPR static const_pointer null_char_() {return "\0";}
+ constexpr std::size_t length_impl_(std::size_t n = 0) const BOOST_NOEXCEPT
+ {
+ return view_[n] == null_char_()[0] ? n : length_impl_(n+1);
+ }
+ const_pointer view_;
+};
+
+template<typename Char>
+inline std::basic_ostream<Char>& operator<<( std::basic_ostream<Char> & os, cstring_ref str )
+{
+ return os << core::string_view(str);
+}
+
+
+}
+}
+
+
+#endif //BOOST_SQLITE_CSTRING_REF_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/detail/aggregate_function.hpp b/subprojects/boost-sqlite/include/boost/sqlite/detail/aggregate_function.hpp
new file mode 100644
index 0000000..de0c447
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/detail/aggregate_function.hpp
@@ -0,0 +1,166 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_DETAIL_AGGREGATE_FUNCTION_HPP
+#define BOOST_SQLITE_DETAIL_AGGREGATE_FUNCTION_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/detail/catch.hpp>
+#include <boost/sqlite/error.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+#include <boost/sqlite/memory.hpp>
+#include <boost/sqlite/result.hpp>
+#include <boost/sqlite/value.hpp>
+
+#include <boost/callable_traits/args.hpp>
+#include <boost/callable_traits/return_type.hpp>
+#include <boost/callable_traits/has_void_return.hpp>
+#include <boost/core/span.hpp>
+
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+namespace detail
+{
+
+template<typename Func>
+struct aggregate_function_maker
+{
+ void * mem;
+
+ template<typename ... Args>
+ Func* operator()(Args && ... args)
+ {
+ return new (mem) Func(std::forward<Args>(args)...);
+ }
+};
+
+template<typename Func, typename Args>
+int create_aggregate_function(sqlite3 * db, cstring_ref name, Args && args, int flags,
+ std::true_type /* void return */)
+{
+ using args_type = callable_traits::args_t<decltype(&Func::step)>;
+ using span_type = typename std::tuple_element<1U, args_type>::type;
+ using func_type = typename std::decay<Func>::type;
+ using func_args_type = typename std::decay<Args>::type;
+
+ return sqlite3_create_function_v2(
+ db, name.c_str(),
+ span_type::extent == boost::dynamic_extent ? -1 : static_cast<int>(span_type::extent),
+ SQLITE_UTF8 | flags,
+ new (memory_tag{}) func_args_type(std::forward<Args>(args)),
+ nullptr,
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args)
+ {
+ auto aa = reinterpret_cast<value*>(args);
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+
+ execute_context_function(
+ ctx,
+ [&]() -> result<void>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return error(SQLITE_NOMEM);
+ c = mp11::tuple_apply(aggregate_function_maker<func_type>{p}, *fa);
+ }
+ c->step(span_type{aa, static_cast<std::size_t>(len)});
+ return {};
+ });
+ },
+ [](sqlite3_context* ctx)
+ {
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+
+ execute_context_function(
+ ctx,
+ [&]() -> result<decltype(c->final())>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return error(SQLITE_NOMEM);
+ c = mp11::tuple_apply(aggregate_function_maker<func_type>{p}, *fa);
+ }
+ struct reaper {void operator()(func_type * c) { c->~func_type();}};
+ std::unique_ptr<func_type, reaper> cl{c};
+ return c->final();
+ });
+ },
+ [](void * ptr) noexcept { delete_(static_cast<func_type*>(ptr));}
+ );
+}
+
+
+template<typename Func, typename Args>
+int create_aggregate_function(sqlite3 * db, cstring_ref name, Args && args, int flags,
+ std::false_type /* void return */)
+{
+ using args_type = callable_traits::args_t<decltype(&Func::step)>;
+ using span_type = typename std::tuple_element<1U, args_type>::type;
+ using func_type = typename std::decay<Func>::type;
+ using func_args_type = typename std::decay<Args>::type;
+
+ return sqlite3_create_function_v2(
+ db, name.c_str(),
+ span_type::extent == boost::dynamic_extent ? -1 : static_cast<int>(span_type::extent),
+ SQLITE_UTF8 | flags,
+ new (memory_tag{}) func_args_type(std::forward<Args>(args)),
+ nullptr,
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args)
+ {
+ auto aa = reinterpret_cast<value*>(args);
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+
+ execute_context_function(
+ ctx,
+ [&]() -> result<void>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return {system::in_place_error, error(SQLITE_NOMEM)};
+ c = mp11::tuple_apply(aggregate_function_maker<func_type>{p}, *fa);
+ }
+ return c->step(span_type{aa, static_cast<std::size_t>(len)});
+ });
+ },
+ [](sqlite3_context* ctx)
+ {
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+
+ execute_context_function(
+ ctx,
+ [&]() -> result<decltype(c->final())>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return {system::in_place_error, error(SQLITE_NOMEM)};
+ c = mp11::tuple_apply(aggregate_function_maker<func_type>{p}, *fa);
+ }
+ struct reaper {void operator()(func_type * c) { c->~func_type();}};
+ std::unique_ptr<func_type, reaper> cl{c};
+ return c->final();
+ });
+ },
+ [](void * ptr) noexcept { delete_(static_cast<func_type*>(ptr));}
+ );
+}
+
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
+
+#endif //BOOST_SQLITE_DETAIL_AGGREGATE_FUNCTION_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/detail/catch.hpp b/subprojects/boost-sqlite/include/boost/sqlite/detail/catch.hpp
new file mode 100644
index 0000000..e5b053f
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/detail/catch.hpp
@@ -0,0 +1,169 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_DETAIL_CATCH_HPP
+#define BOOST_SQLITE_DETAIL_CATCH_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/detail/exception.hpp>
+#include <boost/sqlite/error.hpp>
+#include <boost/sqlite/result.hpp>
+
+#include <boost/system/system_error.hpp>
+
+#include <stdexcept>
+
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+namespace detail
+{
+
+template<typename Func, typename ... Args>
+void execute_context_function_impl(std::false_type /* is_void */,
+ sqlite3_context * ctx,
+ Func && func, Args && ... args)
+ noexcept(noexcept(std::forward<Func>(func)(std::forward<Args>(args)...)))
+{
+ set_result(ctx, std::forward<Func>(func)(std::forward<Args>(args)...));
+}
+
+template<typename Func, typename ... Args>
+void execute_context_function_impl(std::true_type /* is_void */,
+ sqlite3_context * ctx,
+ Func && func, Args && ... args)
+ noexcept(noexcept(std::forward<Func>(func)(std::forward<Args>(args)...)))
+{
+ std::forward<Func>(func)(std::forward<Args>(args)...);
+}
+
+template<typename T>
+int extract_error(char * &errMsg, result<T> & res)
+{
+ error err = std::move(res).error();
+ if (err.info)
+ errMsg = err.info.release();
+ return err.code;
+}
+
+
+template<typename Func, typename ... Args>
+void execute_context_function(sqlite3_context * ctx,
+ Func && func, Args && ... args) noexcept
+{
+ using return_type = decltype(func(std::forward<Args>(args)...));
+#if !defined(BOOST_NO_EXCEPTIONS)
+ try
+ {
+#endif
+ execute_context_function_impl(std::is_void<return_type>{}, ctx,
+ std::forward<Func>(func),
+ std::forward<Args>(args)...);
+#if !defined(BOOST_NO_EXCEPTIONS)
+ }
+ catch(boost::system::system_error & se)
+ {
+ const auto msg = boost::sqlite::detail::get_message(se);
+ if (!msg.empty())
+ sqlite3_result_error(ctx, msg.data(), msg.size());
+ if (se.code().category() == boost::sqlite::sqlite_category())
+ sqlite3_result_error_code(ctx, se.code().value());
+ }
+ catch(std::bad_alloc &) { sqlite3_result_error_nomem(ctx); }
+ catch(std::length_error &) { sqlite3_result_error_toobig(ctx); }
+ catch(std::out_of_range &) { sqlite3_result_error_code(ctx, SQLITE_RANGE);}
+ catch(std::logic_error &le)
+ {
+ sqlite3_result_error(ctx, le.what(), std::strlen(le.what()));
+ sqlite3_result_error_code(ctx, SQLITE_MISUSE);
+ }
+ catch(std::exception & ex)
+ {
+ sqlite3_result_error(ctx, ex.what(), std::strlen(ex.what()));
+ }
+ catch(...) {sqlite3_result_error_code(ctx, SQLITE_ERROR); }
+#endif
+}
+
+}
+BOOST_SQLITE_END_NAMESPACE
+
+#if defined(BOOST_NO_EXCEPTIONS)
+#define BOOST_SQLITE_TRY
+#define BOOST_SQLITE_CATCH_RESULT(ctx)
+#define BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(msg)
+#define BOOST_SQLITE_CATCH_AND_RETURN()
+
+#else
+
+#define BOOST_SQLITE_TRY try
+#define BOOST_SQLITE_CATCH_RESULT(ctx) \
+catch(boost::system::system_error & se) \
+{ \
+ if (se.code().category() == boost::sqlite::sqlite_category()) \
+ sqlite3_result_error_code(ctx, se.code().value()); \
+ const auto msg = boost::sqlite::detail::get_message(se); \
+ if (!msg.empty()) \
+ sqlite3_result_error(ctx, msg.data(), msg.size()); \
+} \
+catch(std::bad_alloc &) { sqlite3_result_error_nomem(ctx); } \
+catch(std::length_error &) { sqlite3_result_error_toobig(ctx); } \
+catch(std::out_of_range &) { sqlite3_result_error_code(ctx, SQLITE_RANGE);} \
+catch(std::logic_error &le) \
+{ \
+ sqlite3_result_error(ctx, le.what(), std::strlen(le.what())); \
+ sqlite3_result_error_code(ctx, SQLITE_MISUSE); \
+} \
+catch(std::exception & ex) \
+{ \
+ sqlite3_result_error(ctx, ex.what(), std::strlen(ex.what())); \
+} \
+catch(...) {sqlite3_result_error_code(ctx, SQLITE_ERROR); }
+
+
+#define BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(msg) \
+catch (boost::system::system_error & se) \
+{ \
+ auto code = SQLITE_ERROR; \
+ if (se.code().category() == boost::sqlite::sqlite_category()) \
+ code = se.code().value(); \
+ const auto pre = boost::sqlite::detail::get_message(se); \
+ msg = boost::sqlite::error_info(pre).release(); \
+ return code; \
+} \
+catch(std::bad_alloc &) { return SQLITE_NOMEM; } \
+catch(std::length_error &) { return SQLITE_TOOBIG; } \
+catch(std::out_of_range &) { return SQLITE_RANGE;} \
+catch(std::logic_error &le) \
+{ \
+ msg = boost::sqlite::error_info(le.what()).release(); \
+ return SQLITE_MISUSE; \
+} \
+catch(std::exception & ex) \
+{ \
+ msg = boost::sqlite::error_info(ex.what()).release(); \
+ return SQLITE_ERROR; \
+} \
+catch(...) {return SQLITE_ERROR; } \
+
+
+#define BOOST_SQLITE_CATCH_AND_RETURN() \
+catch (boost::system::system_error & se) \
+{ \
+ auto code = SQLITE_ERROR; \
+ if (se.code().category() == boost::sqlite::sqlite_category()) \
+ code = se.code().value(); \
+ return code; \
+} \
+catch(std::bad_alloc &) { return SQLITE_NOMEM; } \
+catch(std::length_error &) { return SQLITE_TOOBIG; } \
+catch(std::out_of_range &) { return SQLITE_RANGE;} \
+catch(std::logic_error &) { return SQLITE_MISUSE;} \
+catch(...) { return SQLITE_ERROR; } \
+
+#endif
+
+#endif //BOOST_SQLITE_DETAIL_CATCH_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/detail/config.hpp b/subprojects/boost-sqlite/include/boost/sqlite/detail/config.hpp
new file mode 100644
index 0000000..2126f60
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/detail/config.hpp
@@ -0,0 +1,83 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_DETAIL_CONFIG_HPP
+#define BOOST_SQLITE_DETAIL_CONFIG_HPP
+
+#include <boost/config.hpp>
+#include <boost/core/detail/string_view.hpp>
+
+#if defined(BOOST_SQLITE_COMPILE_EXTENSION)
+#include <sqlite3ext.h>
+#define BOOST_SQLITE_COMPILING_EXTENSION 1
+#define BOOST_SQLITE_BEGIN_NAMESPACE namespace boost { namespace sqlite { inline namespace ext {
+#define BOOST_SQLITE_END_NAMESPACE } } }
+#else
+#include <sqlite3.h>
+#define BOOST_SQLITE_BEGIN_NAMESPACE namespace boost { namespace sqlite {
+#define BOOST_SQLITE_END_NAMESPACE } }
+#endif
+
+// copied from boost.json
+#if defined(BOOST_SQLITE_DOCS)
+# define BOOST_SQLITE_DECL
+#else
+# if (defined(BOOST_SQLITE_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && !defined(BOOST_SQLITE_STATIC_LINK)
+# if defined(BOOST_SQLITE_SOURCE)
+# define BOOST_SQLITE_DECL BOOST_SYMBOL_EXPORT
+# else
+# define BOOST_SQLITE_DECL BOOST_SYMBOL_IMPORT
+# endif
+# endif // static lib
+# ifndef BOOST_SQLITE_DECL
+# define BOOST_SQLITE_DECL
+# endif
+# if !defined(BOOST_SQLITE_SOURCE) && !defined(BOOST_ALL_NO_LIB) && !defined(BOOST_SQLITE_NO_LIB)
+# define BOOST_LIB_NAME boost_sqlite
+# if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_SQLITE_DYN_LINK)
+# define BOOST_DYN_LINK
+# endif
+# include <boost/config/auto_link.hpp>
+# endif
+#endif
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+
+#if defined(BOOST_SQLITE_COMPILE_EXTENSION)
+extern const sqlite3_api_routines *sqlite3_api;
+#endif
+
+using string_view = boost::core::string_view;
+
+BOOST_SQLITE_END_NAMESPACE
+
+#define BOOST_SQLITE_RETURN_EC(ev) \
+do \
+{ \
+ static constexpr auto loc##__LINE__((BOOST_CURRENT_LOCATION)); \
+ return ::boost::system::error_code(ev, boost::sqlite::sqlite_category(), &loc##__LINE__); \
+} \
+while (false)
+
+#define BOOST_SQLITE_ASSIGN_EC(ec, ev) \
+do \
+{ \
+ static constexpr auto loc##__LINE__((BOOST_CURRENT_LOCATION)); \
+ ec.assign(ev, boost::sqlite::sqlite_category(), &loc##__LINE__); \
+} \
+while (false)
+
+#if defined(BOOST_SQLITE_NO_VIRTUAL)
+#define BOOST_SQLITE_VIRTUAL
+#define BOOST_SQLITE_PURE
+#else
+#define BOOST_SQLITE_VIRTUAL virtual
+#define BOOST_SQLITE_PURE = 0
+#endif
+
+#endif // BOOST_SQLITE_DETAIL_HPP \ No newline at end of file
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/detail/exception.hpp b/subprojects/boost-sqlite/include/boost/sqlite/detail/exception.hpp
new file mode 100644
index 0000000..9f29cbe
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/detail/exception.hpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_DETAIL_EXCEPTION_HPP
+#define BOOST_SQLITE_DETAIL_EXCEPTION_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/error.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+namespace detail
+{
+
+BOOST_SQLITE_DECL
+BOOST_NORETURN void throw_error_code(const boost::system::error_code & ec,
+ const boost::source_location & loc = BOOST_CURRENT_LOCATION);
+
+BOOST_SQLITE_DECL
+BOOST_NORETURN void throw_error_code(const boost::system::error_code & ec,
+ const error_info & ei,
+ const boost::source_location & loc = BOOST_CURRENT_LOCATION);
+
+BOOST_SQLITE_DECL
+BOOST_NORETURN void throw_out_of_range(const char * msg,
+ const boost::source_location & loc);
+
+BOOST_SQLITE_DECL
+BOOST_NORETURN void throw_invalid_argument(const char * msg,
+ const boost::source_location & loc);
+
+inline core::string_view get_message(const system::system_error & se)
+{
+ auto ec_len = se.code().what().size();
+ auto se_len = std::strlen(se.what());
+
+ if (ec_len == se_len)
+ return core::string_view();
+ else
+ return core::string_view(se.what(), se_len - (ec_len + 2));
+}
+
+
+}
+BOOST_SQLITE_END_NAMESPACE
+
+
+#endif //BOOST_SQLITE_DETAIL_EXCEPTION_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/detail/scalar_function.hpp b/subprojects/boost-sqlite/include/boost/sqlite/detail/scalar_function.hpp
new file mode 100644
index 0000000..ee16a23
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/detail/scalar_function.hpp
@@ -0,0 +1,172 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_DETAIL_SCALAR_FUNCTION_HPP
+#define BOOST_SQLITE_DETAIL_SCALAR_FUNCTION_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+#include <boost/sqlite/memory.hpp>
+#include <boost/sqlite/result.hpp>
+#include <boost/sqlite/value.hpp>
+
+#include <boost/callable_traits/args.hpp>
+#include <boost/callable_traits/return_type.hpp>
+#include <boost/callable_traits/has_void_return.hpp>
+#include <boost/core/span.hpp>
+
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+template<typename ... Args>
+struct context;
+
+namespace detail
+{
+
+template<typename Func, typename ... Args, std::size_t Extent>
+auto create_scalar_function_impl(sqlite3 * db,
+ cstring_ref name,
+ Func && func,
+ int flags,
+ std::tuple<context<Args...>, boost::span<value, Extent>> * ,
+ std::false_type /* void return */,
+ std::false_type /* is pointer */) -> int
+{
+ using func_type = typename std::decay<Func>::type;
+ return sqlite3_create_function_v2(
+ db, name.c_str(),
+ Extent == boost::dynamic_extent ? -1 : static_cast<int>(Extent),
+ SQLITE_UTF8 | flags,
+ new (memory_tag{}) func_type(std::forward<Func>(func)),
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args)
+ {
+ auto cc = context<Args...>(ctx);
+ auto aa = reinterpret_cast<value*>(args);
+ auto &f = *reinterpret_cast<func_type*>(sqlite3_user_data(ctx));
+ boost::span<value, Extent> vals{aa, static_cast<std::size_t>(len)};
+
+ execute_context_function(ctx, f, cc, vals);
+ }, nullptr, nullptr,
+ +[](void * ptr) noexcept {delete_(static_cast<func_type*>(ptr));}
+ );
+}
+
+template<typename Func, typename ... Args, std::size_t Extent>
+auto create_scalar_function_impl(sqlite3 * db,
+ cstring_ref name,
+ Func && func, int flags,
+ std::tuple<context<Args...>, boost::span<value, Extent>> * ,
+ std::true_type /* void return */,
+ std::false_type /* is pointer */) -> int
+{
+ using func_type = typename std::decay<Func>::type;
+ return sqlite3_create_function_v2(
+ db,
+ name.c_str(),
+ (Extent == boost::dynamic_extent) ? -1 : static_cast<int>(Extent),
+ SQLITE_UTF8 | flags,
+ new (memory_tag{}) func_type(std::forward<Func>(func)),
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args)
+ {
+ auto cc = context<Args...>(ctx);
+ auto aa = reinterpret_cast<value*>(args);
+ auto &f = *reinterpret_cast<func_type*>(sqlite3_user_data(ctx));
+ boost::span<value, Extent> vals{aa, static_cast<std::size_t>(len)};
+
+ execute_context_function(
+ ctx,
+ [&]()
+ {
+ f(cc, vals);
+ return result<void>();
+ });
+
+ }, nullptr, nullptr,
+ +[](void * ptr){delete_(static_cast<func_type*>(ptr));}
+ );
+}
+
+
+template<typename Func, typename ... Args, std::size_t Extent>
+auto create_scalar_function_impl(sqlite3 * db,
+ cstring_ref name,
+ Func * func, int flags,
+ std::tuple<context<Args...>, boost::span<value, Extent>> * ,
+ std::false_type /* void return */,
+ std::true_type /* is pointer */) -> int
+{
+ return sqlite3_create_function_v2(
+ db, name.c_str(),
+ Extent == boost::dynamic_extent ? -1 : static_cast<int>(Extent),
+ SQLITE_UTF8 | flags,
+ reinterpret_cast<void*>(func),
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args)
+ {
+ auto cc = context<Args...>(ctx);
+ auto aa = reinterpret_cast<value*>(args);
+ auto f = reinterpret_cast<Func*>(sqlite3_user_data(ctx));
+
+ boost::span<value, Extent> vals{aa, static_cast<std::size_t>(len)};
+
+ execute_context_function(
+ ctx, f, cc, vals);
+ }, nullptr, nullptr, nullptr);
+}
+
+template<typename Func, typename ... Args, std::size_t Extent>
+auto create_scalar_function_impl(sqlite3 * db,
+ cstring_ref name,
+ Func * func, int flags,
+ std::tuple<context<Args...>, boost::span<value, Extent>> * ,
+ std::true_type /* void return */,
+ std::true_type /* is pointer */) -> int
+{
+ return sqlite3_create_function_v2(
+ db,
+ name.c_str(),
+ (Extent == boost::dynamic_extent) ? -1 : static_cast<int>(Extent),
+ SQLITE_UTF8 | flags,
+ reinterpret_cast<void*>(func),
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args)
+ {
+ auto cc = context<Args...>(ctx);
+ auto aa = reinterpret_cast<value*>(args);
+ auto f = *reinterpret_cast<Func*>(sqlite3_user_data(ctx));
+ boost::span<value, Extent> vals{aa, static_cast<std::size_t>(len)};
+ execute_context_function(
+ ctx,
+ [&]()
+ {
+ f(cc, vals);
+ return result<void>();
+ });
+
+ }, nullptr, nullptr, nullptr);
+}
+
+template<typename Func>
+auto create_scalar_function(sqlite3 * db,
+ cstring_ref name,
+ Func && func,
+ int flags)
+ -> decltype(create_scalar_function_impl(
+ db, name, std::forward<Func>(func), flags,
+ static_cast<callable_traits::args_t<Func>*>(nullptr),
+ callable_traits::has_void_return<Func>{},
+ std::is_pointer<typename std::decay<Func>::type>{}
+ ))
+{
+ return create_scalar_function_impl(db, name, std::forward<Func>(func), flags,
+ static_cast<callable_traits::args_t<Func>*>(nullptr),
+ callable_traits::has_void_return<Func>{},
+ std::is_pointer<typename std::decay<Func>::type>{});
+}
+
+
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_DETAIL_SCALAR_FUNCTION_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/detail/vtable.hpp b/subprojects/boost-sqlite/include/boost/sqlite/detail/vtable.hpp
new file mode 100644
index 0000000..f85f44c
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/detail/vtable.hpp
@@ -0,0 +1,611 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_DETAIL_VTABLE_HPP
+#define BOOST_SQLITE_DETAIL_VTABLE_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/detail/catch.hpp>
+#include <boost/sqlite/vtable.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+namespace detail
+{
+struct vtab_impl
+{
+
+template<typename Module>
+static int connect(sqlite3 * db, void * pAux, int argc, const char * const * argv,
+ sqlite3_vtab **ppVTab, char** errMsg)
+{
+ using table_type = typename Module::table_type;
+ auto &impl = *static_cast<Module*>(pAux);
+ BOOST_SQLITE_TRY
+ {
+ result<table_type> rtab = impl.connect(
+ sqlite::connection(db, false),
+ argc, argv);
+
+ if (rtab.has_error())
+ return extract_error(*errMsg, rtab);
+
+ auto tab = make_unique<table_type>(std::move(*rtab));
+ tab->db_ = db;
+ auto code = sqlite3_declare_vtab(db, tab->declaration());
+ if (code != SQLITE_OK)
+ return code;
+
+ sqlite::vtab::module_config cfg{db};
+ auto r = tab->config(cfg);
+ if (r.has_error())
+ return extract_error(*errMsg, r);
+
+ *ppVTab = tab.release();
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(*errMsg)
+}
+
+
+template<typename Module>
+static int create(sqlite3 * db, void * pAux, int argc, const char * const * argv,
+ sqlite3_vtab **ppVTab, char** errMsg)
+{
+ using table_type = typename Module::table_type;
+ auto &impl = *static_cast<Module*>(pAux);
+ BOOST_SQLITE_TRY
+ {
+ result<table_type> rtab = impl.create(
+ sqlite::connection(db, false),
+ argc, argv);
+
+ if (rtab.has_error())
+ return extract_error(*errMsg, rtab);
+
+ auto tab = make_unique<table_type>(std::move(*rtab));
+ tab->db_ = db;
+
+ auto code = sqlite3_declare_vtab(db, tab->declaration());
+ if (code != SQLITE_OK)
+ return code;
+
+ sqlite::vtab::module_config mc{db};
+ auto r = tab->config(mc);
+ if (r.has_error())
+ return extract_error(*errMsg, r);
+
+ *ppVTab = tab.release();
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(*errMsg)
+}
+
+template<typename Table>
+static int disconnect(sqlite3_vtab * tab)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto tb = static_cast<Table*>(tab);
+ tb->~Table();
+ sqlite3_free(tb);
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(tab->zErrMsg);
+}
+
+template<typename Table>
+static int destroy(sqlite3_vtab * tab)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto tb = static_cast<Table*>(tab);
+ auto res = tb->destroy();
+ tb->~Table();
+ sqlite3_free(tb);
+ if (res.has_error())
+ return std::move(res).error().code;
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(tab->zErrMsg);
+}
+
+template<typename Module, typename Table>
+static void assign_create(sqlite3_module & md, const Module &,
+ const sqlite::vtab::eponymous_module<Table> & base)
+{
+ md.xConnect = md.xCreate = &connect<Module>;
+ md.xDisconnect = md.xDestroy = &disconnect<Table>;
+ if (base.eponymous_only())
+ md.xCreate = nullptr;
+}
+
+template<typename Module, typename Table>
+static void assign_create(sqlite3_module & md, const Module &,
+ const sqlite::vtab::module<Table> &)
+{
+ md.xConnect = &connect<Module>;
+ md.xDisconnect = &disconnect<Table>;
+ md.xCreate = &create<Module>;
+ md.xDestroy = &destroy<Table>;
+}
+
+template<typename Table>
+static int open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor)
+{
+ auto tab = static_cast<Table *>(pVTab);
+
+ BOOST_SQLITE_TRY
+ {
+ auto res = tab->open();
+ if (res.has_error())
+ return extract_error(pVTab->zErrMsg, res);
+ *ppCursor = new (memory_tag{}) typename Table::cursor_type(std::move(*res));
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_AND_RETURN();
+}
+
+template<typename Cursor>
+static int close(sqlite3_vtab_cursor * cursor)
+{
+ auto p = static_cast<Cursor *>(cursor);
+
+ BOOST_SQLITE_TRY
+ {
+ p->~Cursor();
+ }
+ BOOST_SQLITE_CATCH_AND_RETURN();
+
+ sqlite3_free(p);
+ return SQLITE_OK;
+
+}
+
+template<typename Table>
+static int best_index(sqlite3_vtab *pVTab, sqlite3_index_info* info)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto tb = static_cast<Table*>(pVTab);
+
+ sqlite::vtab::index_info ii(tb->db_, info);
+ auto r = tb->best_index(ii);
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg);
+}
+
+
+template<typename Cursor>
+static int filter(sqlite3_vtab_cursor* pCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Cursor*>(pCursor);
+
+ auto r = cr->filter(idxNum, idxStr,
+ boost::span<value>{reinterpret_cast<value*>(argv),
+ static_cast<std::size_t>(argc)});
+ if (r.has_error())
+ return extract_error(pCursor->pVtab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pCursor->pVtab->zErrMsg);
+}
+
+
+template<typename Cursor>
+static int next(sqlite3_vtab_cursor* pCursor)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Cursor*>(pCursor);
+
+ auto r = cr->next();
+ if (r.has_error())
+ return extract_error(pCursor->pVtab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pCursor->pVtab->zErrMsg);
+}
+
+
+template<typename Cursor>
+static int eof(sqlite3_vtab_cursor* pCursor)
+{
+ return static_cast<Cursor*>(pCursor)->eof() ? 1 : 0;
+}
+
+template<typename Cursor>
+static auto column(sqlite3_vtab_cursor* pCursor,
+ sqlite3_context * ctx, int idx)
+ -> typename std::enable_if<!std::is_void<typename Cursor::column_type>::value, int>::type
+{
+#if SQLITE_VERSION_NUMBER >= 3032000
+ bool no_change = sqlite3_vtab_nochange(ctx) != 0;
+#else
+ bool no_change = false;
+#endif
+ auto cr = static_cast<Cursor*>(pCursor);
+ execute_context_function(
+ ctx,
+ [&]{
+ return cr->column(idx, no_change);
+ });
+
+ return SQLITE_OK;
+}
+
+
+template<typename Cursor>
+static auto column(sqlite3_vtab_cursor* pCursor,
+ sqlite3_context * ctx, int idx)
+ -> typename std::enable_if<std::is_void<typename Cursor::column_type>::value, int>::type
+{
+#if SQLITE_VERSION_NUMBER >= 3032000
+ bool no_change = sqlite3_vtab_nochange(ctx) != 0;
+#else
+ bool no_change = false;
+#endif
+ auto cr = static_cast<Cursor*>(pCursor);
+ BOOST_SQLITE_TRY
+ {
+ cr->column(context<>{ctx}, idx, no_change);
+ }
+ BOOST_SQLITE_CATCH_RESULT(ctx);
+ return SQLITE_OK;
+}
+
+template<typename Cursor>
+static int row_id(sqlite3_vtab_cursor* pCursor, sqlite3_int64 *pRowid)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Cursor*>(pCursor);
+
+ auto r = cr->row_id();
+ if (r.has_error())
+ return extract_error(pCursor->pVtab->zErrMsg, r);
+ *pRowid = *r;
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pCursor->pVtab->zErrMsg);
+}
+
+template<typename Table>
+static int update(sqlite3_vtab * pVTab, int argc, sqlite3_value ** argv, sqlite3_int64 * pRowid)
+{
+ using table_type = Table;
+ BOOST_SQLITE_TRY
+ {
+ auto & mod = *static_cast<table_type *>(pVTab);
+ auto db = mod.db_;
+ if (argc == 1 && sqlite3_value_type(argv[0]) != SQLITE_NULL)
+ {
+ auto r = mod.delete_(sqlite::value(*argv));
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+ }
+ else if (argc > 1 && sqlite3_value_type(argv[0]) == SQLITE_NULL)
+ {
+ auto r = mod.insert(value{argv[1]},
+ boost::span<value>{reinterpret_cast<value *>(argv + 2),
+ static_cast<std::size_t>(argc - 2)},
+ sqlite3_vtab_on_conflict(db));
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+ *pRowid = r.value();
+ }
+ else if (argc > 1 && sqlite3_value_type(argv[0]) != SQLITE_NULL)
+ {
+ auto r = mod.update(sqlite::value(*argv), value{argv[1]}, // ID
+ boost::span<value>{reinterpret_cast<value *>(argv + 2),
+ static_cast<std::size_t>(argc - 2)},
+ sqlite3_vtab_on_conflict(db));
+
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+ *pRowid = r.value();
+ }
+ else
+ {
+ pVTab->zErrMsg = sqlite3_mprintf("Misuse of update api");
+ return SQLITE_MISUSE;
+ }
+
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg)
+ return SQLITE_OK;
+}
+
+template<typename Module>
+static void assign_update(sqlite3_module & md, const Module &,
+ std::true_type /* modifiable */)
+{
+ md.xUpdate = &update<typename Module::table_type>;
+}
+
+template<typename Module>
+static void assign_update(sqlite3_module & /*md*/, const Module &,
+ std::false_type /* modifiable */)
+{
+}
+
+template<typename Table>
+static int begin(sqlite3_vtab* pVTab)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Table*>(pVTab);
+
+ auto r = cr->begin();
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg);
+}
+
+template<typename Table>
+static int sync(sqlite3_vtab* pVTab)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Table*>(pVTab);
+
+ auto r = cr->sync();
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg);
+}
+
+template<typename Table>
+static int commit(sqlite3_vtab* pVTab)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Table*>(pVTab);
+
+ auto r = cr->commit();
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg);
+}
+
+template<typename Table>
+static int rollback(sqlite3_vtab* pVTab)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Table*>(pVTab);
+
+ auto r = cr->rollback();
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg);
+}
+
+template<typename Module>
+static void assign_transaction(sqlite3_module & /*md*/, const Module &,
+ std::false_type /* modifiable */)
+{
+}
+
+template<typename Module>
+static void assign_transaction(sqlite3_module & md, const Module &,
+ std::true_type /* modifiable */)
+{
+ md.xBegin = &begin <typename Module::table_type>;
+ md.xSync = &sync <typename Module::table_type>;
+ md.xCommit = &commit <typename Module::table_type>;
+ md.xRollback = &rollback<typename Module::table_type>;
+}
+
+template<typename Table>
+static int find_function(sqlite3_vtab *pVtab, int nArg, const char *zName,
+ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
+ void **ppArg)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Table*>(pVtab);
+
+ auto r = cr->find_function(
+ nArg, zName, sqlite::vtab::function_setter(pxFunc, ppArg));
+ if (r.has_error())
+ return extract_error(pVtab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVtab->zErrMsg);
+}
+
+template<typename Module>
+static void assign_find_function(sqlite3_module & /*md*/, const Module &,
+ std::false_type /* overloadable */)
+{
+}
+
+template<typename Module>
+static void assign_find_function(sqlite3_module & md, const Module &,
+ std::true_type /* overloadable */)
+{
+ md.xFindFunction = &find_function<typename Module::table_type>;
+}
+
+template<typename Table>
+static int rename(sqlite3_vtab* pVTab, const char * name)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Table*>(pVTab);
+
+ auto r = cr->rename(name);
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg);
+}
+
+template<typename Module>
+static void assign_rename(sqlite3_module & /*md*/, const Module &,
+ std::false_type /* renamable */)
+{
+}
+
+template<typename Module>
+static void assign_rename(sqlite3_module & md, const Module &,
+ std::true_type /* renamable */)
+{
+ md.xRename = &rename<typename Module::table_type>;
+}
+#if SQLITE_VERSION_NUMBER >= 3007007
+
+template<typename Table>
+static int savepoint(sqlite3_vtab* pVTab, int i)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Table*>(pVTab);
+
+ auto r = cr->savepoint(i);
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg);
+}
+
+template<typename Table>
+static int release(sqlite3_vtab* pVTab, int i)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Table*>(pVTab);
+
+ auto r = cr->release(i);
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg);
+}
+
+template<typename Table>
+static int rollback_to(sqlite3_vtab* pVTab, int i)
+{
+ BOOST_SQLITE_TRY
+ {
+ auto cr = static_cast<Table*>(pVTab);
+
+ auto r = cr->rollback_to(i);
+ if (r.has_error())
+ return extract_error(pVTab->zErrMsg, r);
+
+ return SQLITE_OK;
+ }
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg);
+}
+
+template<typename Module>
+static void assign_recursive_transaction(sqlite3_module & /*md*/, const Module &,
+ std::false_type /* recursive_transaction */)
+{
+}
+
+template<typename Module>
+static void assign_recursive_transaction(sqlite3_module & md, const Module &,
+ std::true_type /* recursive_transaction */)
+{
+ md.xSavepoint = &savepoint <typename Module::table_type>;
+ md.xRelease = &release <typename Module::table_type>;
+ md.xRollbackTo = &rollback_to<typename Module::table_type>;
+}
+
+#endif
+
+#if SQLITE_VERSION_NUMBER >= 3026000
+
+template<typename Table>
+static void assign_shadow_name(sqlite3_module & /*md*/, const sqlite::vtab::module<Table> &) {}
+
+template<typename Table>
+static void assign_shadow_name(sqlite3_module & /*md*/, const sqlite::vtab::eponymous_module<Table> &) {}
+
+template<typename Module,
+ bool (*Func)(const char *) = &Module::shadow_name>
+static void assign_shadow_name(sqlite3_module & md, const Module & mod)
+{
+ md.xShadowName = +[](const char * name){return Func(name) != 0;};
+}
+
+#endif
+
+};
+
+template<typename Module>
+const sqlite3_module make_module(const Module & mod)
+{
+ sqlite3_module md;
+ std::memset(&md, 0, sizeof(sqlite3_module));
+
+#if SQLITE_VERSION_NUMBER < 3007007
+ md.iVersion = 1;
+#elif SQLITE_VERSION_NUMBER < 3026000
+ md.iVersion = 2;
+#else
+ md.iVersion = 3;
+#endif
+ using table_type = typename Module::table_type;
+ using cursor_type = typename table_type::cursor_type;
+ vtab_impl::assign_create(md, mod, mod);
+ md.xBestIndex = &vtab_impl::best_index<table_type>;
+ md.xOpen = &vtab_impl::open <table_type>;
+ md.xClose = &vtab_impl::close <cursor_type>;
+ md.xFilter = &vtab_impl::filter <cursor_type>;
+ md.xNext = &vtab_impl::next <cursor_type>;
+ md.xEof = &vtab_impl::eof <cursor_type>;
+ md.xColumn = &vtab_impl::column <cursor_type>;
+ md.xRowid = &vtab_impl::row_id <cursor_type>;
+ vtab_impl::assign_update (md, mod, std::is_base_of<sqlite::vtab::modifiable, table_type>{});
+ vtab_impl::assign_transaction (md, mod, std::is_base_of<sqlite::vtab::transaction, table_type>{});
+ vtab_impl::assign_find_function(md, mod, std::is_base_of<sqlite::vtab::overload_functions, table_type>{});
+ vtab_impl::assign_rename (md, mod, std::is_base_of<sqlite::vtab::renamable, table_type>{});
+#if SQLITE_VERSION_NUMBER >= 3007007
+ vtab_impl::assign_recursive_transaction(md, mod, std::is_base_of<sqlite::vtab::recursive_transaction, table_type>{});
+#endif
+#if SQLITE_VERSION_NUMBER >= 3026000
+ vtab_impl::assign_shadow_name(md, mod);
+#endif
+ return md;
+}
+
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_DETAIL_VTABLE_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/detail/window_function.hpp b/subprojects/boost-sqlite/include/boost/sqlite/detail/window_function.hpp
new file mode 100644
index 0000000..df8475c
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/detail/window_function.hpp
@@ -0,0 +1,238 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_IMPL_WINDOW_FUNCTION_HPP
+#define BOOST_SQLITE_IMPL_WINDOW_FUNCTION_HPP
+#if SQLITE_VERSION_NUMBER >= 3025000
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+#include <boost/sqlite/memory.hpp>
+#include <boost/sqlite/result.hpp>
+#include <boost/sqlite/value.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+namespace detail
+{
+
+template<typename Func>
+struct window_function_maker
+{
+ void * mem;
+
+ template<typename ... Args>
+ Func* operator()(Args && ... args)
+ {
+ return new (mem) Func(std::forward<Args>(args)...);
+ }
+};
+
+template<typename Func, typename Args>
+int create_window_function(sqlite3 * db, cstring_ref name, Args && args, int flags,
+ std::true_type /* is void */)
+{
+ using args_type = callable_traits::args_t<decltype(&Func::step)>;
+ using span_type = typename std::tuple_element<1U, args_type>::type;
+ using func_type = typename std::decay<Func>::type;
+ using func_args_type = typename std::decay<Args>::type;
+
+ return sqlite3_create_window_function(
+ db, name.c_str(),
+ span_type::extent == boost::dynamic_extent ? -1 : static_cast<int>(span_type::extent),
+ SQLITE_UTF8 | flags,
+ new (memory_tag{}) func_args_type(std::forward<Args>(args)),
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept //xStep
+ {
+ auto aa = reinterpret_cast<value*>(args);
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+
+ execute_context_function(
+ ctx,
+ [&]() -> result<void>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return error(SQLITE_NOMEM);
+ c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa);
+ }
+ c->step(span_type{aa, static_cast<std::size_t>(len)});
+ return {};
+ });
+
+ },
+ [](sqlite3_context* ctx) // xFinal
+ {
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+
+ execute_context_function(
+ ctx,
+ [&]() -> result<decltype(c->value())>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return error(SQLITE_NOMEM);
+ c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa);
+ }
+ struct reaper {void operator()(func_type * c) { c->~func_type();}};
+ std::unique_ptr<func_type, reaper> cl{c};
+
+ return c->value();
+ });
+ },
+ [](sqlite3_context* ctx) //xValue
+ {
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+ execute_context_function(
+ ctx,
+ [&]() -> result<decltype(c->value())>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return error(SQLITE_NOMEM);
+ c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa);
+ }
+ return c->value();
+ });
+
+ },
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args) // xInverse
+ {
+ auto aa = reinterpret_cast<value*>(args);
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+ execute_context_function(
+ ctx,
+ [&]() -> result<decltype(c->inverse(span_type{aa, static_cast<std::size_t>(len)}))>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return error(SQLITE_NOMEM);
+ c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa);
+ }
+ c->inverse(span_type{aa, static_cast<std::size_t>(len)});
+ return {};
+ });
+
+ },
+ [](void * ptr) /* xDestroy */ { delete_(static_cast<func_type*>(ptr));}
+ );
+}
+
+template<typename Func, typename Args>
+int create_window_function(sqlite3 * db, cstring_ref name, Args && args, int flags,
+ std::false_type /* is void */)
+{
+ using args_type = callable_traits::args_t<decltype(&Func::step)>;
+ using span_type = typename std::tuple_element<1U, args_type>::type;
+ using func_type = typename std::decay<Func>::type;
+ using func_args_type = typename std::decay<Args>::type;
+
+ return sqlite3_create_window_function(
+ db, name.c_str(),
+ span_type::extent == boost::dynamic_extent ? -1 : static_cast<int>(span_type::extent),
+ SQLITE_UTF8 | flags,
+ new (memory_tag{}) func_args_type(std::forward<Args>(args)),
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept //xStep
+ {
+ auto aa = reinterpret_cast<value*>(args);
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+
+ execute_context_function(
+ ctx,
+ [&]() -> result<decltype(c->step(span_type{aa, static_cast<std::size_t>(len)}))>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return {system::in_place_error, error(SQLITE_NOMEM)};
+ c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa);
+ }
+ return c->step(span_type{aa, static_cast<std::size_t>(len)});
+ });
+
+ },
+ [](sqlite3_context* ctx) // xFinal
+ {
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+ execute_context_function(
+ ctx,
+ [&]() -> result<decltype(c->value())>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return {system::in_place_error, error(SQLITE_NOMEM)};
+ c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa);
+ }
+
+ struct reaper {void operator()(func_type * c) { c->~func_type();}};
+ std::unique_ptr<func_type, reaper> cl{c};
+ return c->value();
+ });
+ },
+ [](sqlite3_context* ctx) //xValue
+ {
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+ execute_context_function(
+ ctx,
+ [&]() -> result<decltype(c->value())>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return {system::in_place_error, error(SQLITE_NOMEM)};
+ c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa);
+ }
+ return c->value();
+ });
+
+ },
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args) // xInverse
+ {
+ auto aa = reinterpret_cast<value*>(args);
+ auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx));
+ auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0));
+ execute_context_function(
+ ctx,
+ [&]() -> result<decltype(c->inverse(span_type{aa, static_cast<std::size_t>(len)}))>
+ {
+ if (c == nullptr)
+ {
+ auto p = sqlite3_aggregate_context(ctx, sizeof(func_type));
+ if (!p)
+ return {system::in_place_error, error(SQLITE_NOMEM)};
+ c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa);
+ }
+ return c->inverse(span_type{aa, static_cast<std::size_t>(len)});
+ });
+
+ },
+ [](void * ptr) /* xDestroy */ { delete_(static_cast<func_type*>(ptr));}
+ );
+}
+
+
+}
+
+BOOST_SQLITE_END_NAMESPACE
+#endif // SQLITE_VERSION
+#endif //BOOST_SQLITE_IMPL_WINDOW_FUNCTION_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/error.hpp b/subprojects/boost-sqlite/include/boost/sqlite/error.hpp
new file mode 100644
index 0000000..9fe6211
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/error.hpp
@@ -0,0 +1,178 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_ERROR_HPP
+#define BOOST_SQLITE_ERROR_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+#include <boost/sqlite/memory.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/system/error_category.hpp>
+#include <boost/system/result.hpp>
+
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+BOOST_SQLITE_DECL
+system::error_category & sqlite_category();
+
+
+/**
+ * \brief Additional information about error conditions stored in an sqlite-allocate string
+ * \ingroup reference
+ * \details Contains an error message describing what happened. Not all error
+ * conditions are able to generate this extended information - those that
+ * can't have an empty error message.
+ */
+struct error_info
+{
+ /// Default constructor.
+ error_info() = default;
+
+ /// Initialization constructor.
+ error_info(core::string_view msg) noexcept : msg_(new (memory_tag{}) char[msg.size() + 1u])
+ {
+ std::memcpy(msg_.get(), msg.data(), (std::min)(msg.size() + 1, capacity()));
+ }
+
+ /// set the message by copy
+ void set_message(core::string_view msg)
+ {
+ reserve(msg.size() + 1u);
+ std::memcpy(msg_.get(), msg.data(), (std::min)(msg.size() + 1, capacity()));
+ }
+ /// transfer ownership into the message
+ void reset(char * c = nullptr)
+ {
+ msg_.reset(c);
+ }
+
+ /// use sqlite_mprintf to generate a message
+#if defined(__GNUC__)
+ cstring_ref format(const char * fmt, ...) __attribute__((format (printf, 2, 3)))
+ {
+ va_list args;
+ va_start(args, fmt);
+ msg_.reset(sqlite3_vmprintf(fmt, args));
+ va_end(args);
+ return msg_.get();
+ }
+
+#endif
+ cstring_ref format(cstring_ref fmt, ...)
+ {
+ va_list args;
+ va_start(args, fmt);
+ msg_.reset(sqlite3_vmprintf(fmt.c_str(), args));
+ va_end(args);
+ return msg_.get();
+ }
+
+ /// use sqlite_snprintf to generate a message
+#if defined(__GNUC__)
+ cstring_ref snformat(const char * fmt, ...) __attribute__((format (printf, 2, 3)))
+ {
+ if (capacity() == 0)
+ return "";
+ va_list args;
+ va_start(args, fmt);
+ sqlite3_vsnprintf(static_cast<int>(capacity()), msg_.get(), fmt, args);
+ va_end(args);
+ return msg_.get();
+ }
+
+#endif
+ cstring_ref snformat(cstring_ref fmt, ...)
+ {
+ if (capacity() == 0)
+ return "";
+ va_list args;
+ va_start(args, fmt);
+ sqlite3_vsnprintf(static_cast<int>(capacity()), msg_.get(), fmt.c_str(), args);
+ va_end(args);
+ return msg_.get();
+ }
+ /// reserve data in the buffer i.e. allocate
+ void reserve(std::size_t sz)
+ {
+ if (msg_)
+ {
+ if (sqlite3_msize(msg_.get()) < sz)
+ msg_.reset(static_cast<char*>(sqlite3_realloc64(msg_.release(), sz)));
+ }
+ else
+ msg_.reset(static_cast<char*>(sqlite3_malloc64(sz)));
+ }
+
+ /// Get the allocated memory
+ std::size_t capacity() const {return msg_ ? sqlite3_msize(msg_.get()) : 0u;}
+
+ /// Gets the error message.
+ cstring_ref message() const noexcept { return msg_ ? msg_.get() : ""; }
+
+ char * release()
+ {
+ return msg_.release();
+ }
+ /// Restores the object to its initial state.
+ void clear() noexcept { if (msg_) *msg_ = '\0'; }
+
+
+ operator bool() const {return msg_.operator bool();}
+ private:
+ unique_ptr<char> msg_;
+};
+
+
+/**
+ * \brief An error containing both a code & optional message.
+ * \ingroup reference
+ * \details Contains an error .
+ */
+struct error
+{
+ /// The code of the error.
+ int code;
+ /// The additional information of the error
+ error_info info;
+
+ error(int code, error_info info) : code(code), info(std::move(info)) {}
+ explicit error(int code) : code(code) {}
+ error(int code, core::string_view info)
+ : code(code),
+ info(info) {}
+
+ error(system::error_code code, error_info info)
+ : code(code.category() == sqlite_category() ? code.value() : SQLITE_FAIL),
+ info(std::move(info)) {}
+
+ error(system::error_code code) : code(code.category() == sqlite_category() ? code.value() : SQLITE_FAIL)
+ {
+ if (code.category() == sqlite_category())
+ info = error_info{code.what()};
+ }
+ error() = default;
+ error(error && ) noexcept = default;
+};
+
+BOOST_NORETURN BOOST_SQLITE_DECL void throw_exception_from_error( error const & e, boost::source_location const & loc );
+
+template<typename T = void>
+using result = system::result<T, error>;
+
+template<typename T>
+struct is_result_type : std::false_type {};
+
+template<typename T>
+struct is_result_type<result<T>> : std::true_type {};
+
+
+BOOST_SQLITE_END_NAMESPACE
+
+
+#endif // BOOST_SQLITE_ERROR_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/extension.hpp b/subprojects/boost-sqlite/include/boost/sqlite/extension.hpp
new file mode 100644
index 0000000..32553fa
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/extension.hpp
@@ -0,0 +1,67 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_EXTENSION_HPP
+#define BOOST_SQLITE_EXTENSION_HPP
+
+#define BOOST_SQLITE_COMPILE_EXTENSION 1
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/connection.hpp>
+#include <boost/sqlite/detail/catch.hpp>
+#include <boost/config.hpp>
+
+
+/** @brief Declare a sqlite module.
+ @ingroup reference
+
+ @param Name The name of the module
+ @param Conn The parameter name of the connection
+
+ This macro can be used to create an sqlite extension.
+
+ @note When defining BOOST_SQLITE_COMPILE_EXTENSION (was is done in extension.hpp)
+ sqlite will use an inline namespace to avoid symbol clashes.
+
+ @par Examples
+
+ @code{.cpp}
+
+ BOOST_SQLITE_EXTENSION(extension, conn)
+ {
+ create_scalar_function(
+ conn, "assert",
+ [](boost::sqlite::context<>, boost::span<boost::sqlite::value, 1u> sp)
+ {
+ if (sp.front().get_int() == 0)
+ throw std::logic_error("assertion failed");
+ });
+ }
+
+ @endcode{.cpp}
+
+ */
+#define BOOST_SQLITE_EXTENSION(Name, Conn) \
+void sqlite_##Name##_impl (boost::sqlite::connection Conn); \
+extern "C" BOOST_SYMBOL_EXPORT \
+int sqlite3_##Name##_init( \
+ sqlite3 *db, \
+ char **pzErrMsg, \
+ const sqlite3_api_routines *pApi) \
+{ \
+ using boost::sqlite::sqlite3_api; \
+ SQLITE_EXTENSION_INIT2(pApi); \
+ \
+ BOOST_SQLITE_TRY \
+ { \
+ sqlite_##Name##_impl(boost::sqlite::connection{db, false}); \
+ return SQLITE_OK; \
+ } \
+ BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(*pzErrMsg); \
+} \
+void sqlite_##Name##_impl(boost::sqlite::connection Conn)
+
+#endif //BOOST_SQLITE_EXTENSION_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/field.hpp b/subprojects/boost-sqlite/include/boost/sqlite/field.hpp
new file mode 100644
index 0000000..20fe4bd
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/field.hpp
@@ -0,0 +1,85 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_FIELD_HPP
+#define BOOST_SQLITE_FIELD_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/blob.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+#include <boost/sqlite/value.hpp>
+
+#include <boost/core/detail/string_view.hpp>
+
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+/** @brief A holder for a sqlite field, i.e. something returned from a query.
+ @ingroup reference
+ */
+struct field
+{
+ typedef sqlite_int64 int64;
+
+ /// The type of the value
+ value_type type() const
+ {
+ return static_cast<value_type>( sqlite3_column_type(stm_, col_));
+ }
+ /// Is the held value null
+ bool is_null() const
+ {
+ return type() == value_type::null;
+ }
+ /// Is the held value is not null
+ explicit operator bool () const
+ {
+ return type() != value_type::null;
+ }
+ /// Returns the value as an `int64`.
+ int64 get_int() const
+ {
+ return sqlite3_column_int64(stm_, col_);
+ }
+ /// Returns the value as an `double`.
+ double get_double() const
+ {
+ return sqlite3_column_double(stm_, col_);
+ }
+ /// Returns the value as text, i.e. a string_view. Note that this value may be invalidated`.
+ BOOST_SQLITE_DECL
+ cstring_ref get_text() const;
+ /// Returns the value as blob, i.e. raw memory. Note that this value may be invalidated`.
+ BOOST_SQLITE_DECL
+ blob_view get_blob() const;
+ /// Returns the field as a value.
+ value get_value() const
+ {
+ return value(sqlite3_column_value(stm_, col_));
+ }
+ /// Returns the name of the column.
+ cstring_ref column_name() const
+ {
+ return sqlite3_column_name(stm_, col_);
+ }
+ /// Returns the name of the table.
+ cstring_ref table_name() const
+ {
+ return sqlite3_column_table_name(stm_, col_);
+ }
+ /// Returns the name of the original data source.
+ cstring_ref column_origin_name() const
+ {
+ return sqlite3_column_origin_name(stm_, col_);
+ }
+
+ private:
+ friend struct row;
+ sqlite3_stmt * stm_;
+ int col_ = -1;
+};
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_FIELD_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/function.hpp b/subprojects/boost-sqlite/include/boost/sqlite/function.hpp
new file mode 100644
index 0000000..14060b6
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/function.hpp
@@ -0,0 +1,452 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_FUNCTION_HPP
+#define BOOST_SQLITE_FUNCTION_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/detail/aggregate_function.hpp>
+#include <boost/sqlite/detail/scalar_function.hpp>
+#include <boost/sqlite/detail/window_function.hpp>
+#include <boost/sqlite/connection.hpp>
+#include <boost/sqlite/result.hpp>
+#include <boost/sqlite/value.hpp>
+#include <boost/sqlite/detail/exception.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+
+#include <boost/core/span.hpp>
+#include <boost/callable_traits/args.hpp>
+#include <boost/callable_traits/has_void_return.hpp>
+#include <boost/callable_traits/return_type.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+enum function_flags
+{
+ deterministic = SQLITE_DETERMINISTIC,
+ directonly = SQLITE_DIRECTONLY,
+ subtype = SQLITE_SUBTYPE,
+ innocuous = SQLITE_INNOCUOUS,
+ result_subtype = SQLITE_RESULT_SUBTYPE,
+#if defined(SQLITE_SELFORDER1)
+ selforder1 = SQLITE_SELFORDER1,
+#else
+ selforder1 = 0
+#endif
+};
+
+
+/** @brief A context that can be passed into scalar functions.
+ @ingroup reference
+
+ @tparam Args The argument that can be stored in the context.
+
+ @code{.cpp}
+ extern sqlite::connection conn;
+
+ sqlite::create_scalar_function(
+ conn, "my_sum",
+ [](sqlite::context<std::size_t> ctx,
+ boost::span<sqlite::value, 1u> args) -> std::size_t
+ {
+ auto value = args[0].get_int();
+ auto p = ctx.get_if<0>();
+ if (p != nullptr) // increment the counter
+ return (*p) += value;
+ else // set the initial value
+ ctx.set<0>(value);
+ return value;
+ });
+ @endcode
+
+*/
+template<typename ... Args>
+struct context
+{
+ template<std::size_t N>
+ using element = mp11::mp_take_c<mp11::mp_list<Args...>, N>;
+
+ /// Set the value in the context at position `Idx`
+ template<std::size_t Idx>
+ void set(element<Idx> value)
+ {
+ sqlite3_set_auxdata(ctx_, Idx, *static_cast<void**>(&value),
+ new (memory_tag{}) element<Idx>(std::move(value)),
+ +[](void * ptr)
+ {
+ delete_(static_cast<element<Idx> *>(ptr));
+ });
+ }
+
+ /// Returns the value in the context at position `Idx`. Throws if the value isn't set.
+ template<std::size_t Idx>
+ auto get() -> element<Idx> &
+ {
+ using type = element<Idx> ;
+ auto p = static_cast<type*>(sqlite3_get_auxdata(ctx_, Idx));
+ if (p == nullptr)
+ detail::throw_invalid_argument("argument not set",
+ BOOST_CURRENT_LOCATION);
+ return *p;
+ }
+
+ /// Returns the value in the context at position `Idx`. Returns nullptr .value isn't set.
+ template<std::size_t Idx>
+ auto get_if() -> element<Idx> *
+ {
+ using type = element<Idx> ;
+ return static_cast<type*>(sqlite3_get_auxdata(ctx_, Idx));
+ }
+
+
+ explicit context(sqlite3_context * ctx) noexcept : ctx_(ctx) {}
+
+ /// Set the result through the context, instead of returning it.
+ template<typename T>
+ auto set_result(T && val)
+#if !defined(BOOST_SQLITE_GENERATING_DOCS)
+ -> decltype(detail::set_result(static_cast<sqlite3_context*>(nullptr), std::forward<T>(val)))
+#endif
+ {
+ detail::set_result(ctx_, std::forward<T>(val));
+ }
+ /// Set the an error through the context, instead of throwing it.
+ void set_error(cstring_ref message, int code = SQLITE_ERROR)
+ {
+ sqlite3_result_error(ctx_, message.c_str(), -1);
+ sqlite3_result_error_code(ctx_, code);
+ }
+ /// Returns the connection of the context.
+ connection get_connection() const
+ {
+ return connection{sqlite3_context_db_handle(ctx_), false};
+ }
+
+ private:
+ sqlite3_context * ctx_;
+};
+
+
+///@{
+/** @brief create a scalar function
+ @ingroup reference
+
+ @param conn The connection to add the function to.
+ @param name The name of the function
+ @param func The function to be added
+ @param ec The system::error_code
+
+ @throws `system::system_error` when the overload without `ec` is used.
+
+ `func` must take `context<Args...>` as the first and a `span<value, N>` as the second value.
+ If `N` is not `dynamic_extent` it will be used to deduce the number of arguments for the function.
+
+ @par Example
+
+ @code{.cpp}
+ extern sqlite::connection conn;
+
+ sqlite::create_function(
+ conn, "my_sum",
+ [](sqlite::context<std::size_t> ctx,
+ boost::span<sqlite::value, 1u> args) -> std::size_t
+ {
+ auto value = args[0].get_int();
+ auto p = ctx.get_if<0>();
+ if (p != nullptr) // increment the counter
+ return (*p) += value;
+ else // set the initial value
+ ctx.set<0>(value);
+ return value;
+ });
+ @endcode
+
+ */
+template<typename Func>
+auto create_scalar_function(
+ connection & conn,
+ cstring_ref name,
+ Func && func,
+ function_flags flags,
+ system::error_code & ec,
+ error_info & ei)
+#if !defined(BOOST_SQLITE_GENERATING_DOCS)
+ -> typename std::enable_if<
+ std::is_same<
+ decltype(
+ detail::create_scalar_function(
+ static_cast<sqlite3*>(nullptr), name,
+ std::declval<Func>(), flags)
+ ), int>::value>::type
+#endif
+{
+ auto res = detail::create_scalar_function(conn.handle(), name,
+ std::forward<Func>(func), static_cast<int>(flags));
+ if (res != 0)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+ ei.set_message(sqlite3_errmsg(conn.handle()));
+ }
+}
+
+///@{
+/** @brief create a scalar function
+ @ingroup reference
+
+ @param conn The connection to add the function to.
+ @param name The name of the function
+ @param func The function to be added
+ @param ec The system::error_code
+
+ @throws `system::system_error` when the overload without `ec` is used.
+
+ `func` must take `context<Args...>` as the first and a `span<value, N>` as the second value.
+ If `N` is not `dynamic_extent` it will be used to deduce the number of arguments for the function.
+
+ @par Example
+
+ @code{.cpp}
+ extern sqlite::connection conn;
+
+ sqlite::create_function(
+ conn, "to_upper",
+ [](sqlite::context<> ctx,
+ boost::span<sqlite::value, 1u> args) -> std::string
+ {
+ std::string res;
+ auto txt = val[0].get_text();
+ res.resize(txt.size());
+ std::transform(txt.begin(), txt.end(), res.begin(),
+ [](char c){return std::toupper(c);});
+ return value;
+ });
+ @endcode
+
+ */
+template<typename Func>
+auto create_scalar_function(
+ connection & conn,
+ cstring_ref name,
+ Func && func,
+ function_flags flags = {})
+ -> typename std::enable_if<
+ std::is_same<
+ decltype(
+ detail::create_scalar_function(
+ static_cast<sqlite3*>(nullptr), name,
+ std::declval<Func>(), flags)
+ ), int>::value>::type
+{
+ system::error_code ec;
+ error_info ei;
+ create_scalar_function(conn, name, std::forward<Func>(func), flags, ec, ei);
+ if (ec)
+ detail::throw_error_code(ec, ei);
+}
+///@}
+
+
+///@{
+/** @brief create a aggregate function
+ @ingroup reference
+
+ @param conn The connection to add the function to.
+ @param name The name of the function
+ @param args
+ @param ec The system::error_code
+
+@tparam Func The function to be added
+
+ @throws `system::system_error` when the overload without `ec` is used.
+
+
+ `func` needs to be an object with two functions:
+
+ @code{.cpp}
+ void step(boost::span<sqlite::value, N> args);
+ T final();
+ @endcode
+
+
+
+ An aggregrate function will create a new `Func` for a new `aggregate` from the args tuple and call `step` for every step.
+ When the aggregation is done `final` is called and the result is returned to sqlite.
+
+ @par Example
+
+ @code{.cpp}
+ extern sqlite::connection conn;
+
+ struct aggregate_func
+ {
+ aggregate_func(std::size_t init) : counter(init) {}
+ std::size_t counter;
+ void step(, boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ }
+
+ std::size_t final()
+ {
+ return counter;
+ }
+ };
+
+ sqlite::create_function<aggregate_func>(
+ conn, "char_counter", std::make_tuple(42));
+
+ @endcode
+
+ */
+template<typename Func, typename Args = std::tuple<>>
+void create_aggregate_function(
+ connection & conn,
+ cstring_ref name,
+ Args && args,
+ function_flags flags,
+ system::error_code & ec,
+ error_info & ei)
+{
+ using func_type = typename std::decay<Func>::type;
+ auto res = detail::create_aggregate_function<Func>(
+ conn.handle(), name, std::forward<Args>(args), static_cast<int>(flags),
+ callable_traits::has_void_return<decltype(&func_type::step)>{}
+ );
+ if (res != 0)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+ ei.set_message(sqlite3_errmsg(conn.handle()));
+ }
+}
+
+template<typename Func, typename Args = std::tuple<>>
+void create_aggregate_function(
+ connection & conn,
+ cstring_ref name,
+ Args && args= {},
+ function_flags flags = {})
+{
+ system::error_code ec;
+ error_info ei;
+ create_aggregate_function<Func>(conn, name, std::forward<Args>(args), flags, ec, ei);
+ if (ec)
+ detail::throw_error_code(ec, ei);
+}
+///@}
+
+#if SQLITE_VERSION_NUMBER >= 3025000
+
+///@{
+/** @brief create a aggregate window function
+ @ingroup reference
+
+ @param conn The connection to add the function to.
+ @param name The name of the function
+ @param args The arguments to construct Func from.
+ @param ec The system::error_code
+
+@tparam Func The function to be added
+
+ @throws `system::system_error` when the overload without `ec` is used.
+
+ `func` needs to be an object with three functions:
+
+ @code{.cpp}
+ void step(boost::span<sqlite::value, N> args);
+ void inverse(boost::span<sqlite::value, N> args);
+ T final();
+ @endcode
+
+ `State` can be any type and will get deduced together with `N`.
+ An aggregrate function will create a new `State` for a new `aggregate` and call `step` for every step.
+ When an element is removed from the window `inverse` is called.
+ When the aggregation is done `final` is called and the result is returned to sqlite.
+
+ @par Example
+
+ @code{.cpp}
+ extern sqlite::connection conn;
+
+ struct window_func
+ {
+ std::size_t counter;
+ void step(boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ }
+ void inverse(boost::span<sqlite::value, 1u> val)
+ {
+ counter -= val[0].get_text().size();
+ }
+
+ std::size_t final()
+ {
+ return counter;
+ }
+ };
+
+ sqlite::create_function(
+ conn, "win_char_counter",
+ aggregate_func{});
+ @endcode
+ */
+template<typename Func, typename Args = std::tuple<>>
+void create_window_function(
+ connection & conn,
+ cstring_ref name,
+ Args && args,
+ function_flags flags,
+ system::error_code & ec)
+{
+ using func_type = typename std::decay<Func>::type;
+ auto res = detail::create_window_function<Func>(
+ conn.handle(), name, std::forward<Args>(args), static_cast<int>(flags),
+ callable_traits::has_void_return<decltype(&func_type::step)>{});
+ if (res != 0)
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+}
+
+template<typename Func, typename Args = std::tuple<>>
+void create_window_function(
+ connection & conn,
+ cstring_ref name,
+ Args && args = {},
+ function_flags flags = {})
+{
+ system::error_code ec;
+ create_window_function<Func>(conn, name, std::forward<Args>(args), flags, ec);
+ if (ec)
+ detail::throw_error_code(ec);
+}
+
+///@}
+
+///@{
+/// Delete function
+
+inline void delete_function(connection & conn, cstring_ref name, int argc, system::error_code &ec)
+{
+ auto res = sqlite3_create_function_v2(conn.handle(), name.c_str(), argc, 0, nullptr, nullptr, nullptr, nullptr, nullptr);
+ if (res != 0)
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+
+}
+
+inline void delete_function(connection & conn, cstring_ref name, int argc = -1)
+{
+ system::error_code ec;
+ delete_function(conn, name, argc, ec);
+ if (ec)
+ detail::throw_error_code(ec);
+}
+
+///@}
+#endif
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_FUNCTION_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/hooks.hpp b/subprojects/boost-sqlite/include/boost/sqlite/hooks.hpp
new file mode 100644
index 0000000..50cb4fc
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/hooks.hpp
@@ -0,0 +1,370 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_HOOKS_HPP
+#define BOOST_SQLITE_HOOKS_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/function.hpp>
+#include <boost/system/result.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+
+#if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+/** The context for pre-update events
+
+ @note This is only available if sqlite was compiled with `SQLITE_ENABLE_PREUPDATE_HOOK` enabled.
+
+ */
+struct preupdate_context
+{
+ /// Returns the old value, i.e. the value before the update.
+ system::result<value> old(int column) const
+ {
+ sqlite3_value * val;
+ int res = sqlite3_preupdate_old(db_, column, &val);
+ if (res != 0)
+ BOOST_SQLITE_RETURN_EC(res);
+ return value(val);
+ }
+ /// The count of colums to be updated
+ int count() const { return sqlite3_preupdate_count(db_); }
+ /// The nesting depth of the update.
+ int depth() const { return sqlite3_preupdate_depth(db_); }
+ /// The new value to be written to column
+ system::result<value> new_(int column) const
+ {
+ sqlite3_value * val;
+ int res = sqlite3_preupdate_new(db_, column, &val);
+ if (res != 0)
+ BOOST_SQLITE_RETURN_EC(res);
+ return value(val);
+ }
+
+ /// @brief Query the status of blob access, e.g. when using @ref blob_handle
+ /// @see https://www.sqlite.org/c3ref/preupdate_blobwrite.html
+ int blob_write() const { return sqlite3_preupdate_blobwrite(db_); }
+
+ explicit preupdate_context(sqlite3 * db) noexcept : db_(db) {}
+ private:
+ sqlite3 * db_;
+};
+
+#endif
+
+namespace detail
+{
+
+
+template<typename Func>
+bool commit_hook_impl(sqlite3 * db,
+ Func * func,
+ std::true_type)
+{
+ static_assert(noexcept(func()), "hook must be noexcept");
+ return sqlite3_commit_hook( db, func, [](void * data) { return (*static_cast<Func *>(data))() ? 1 : 0; }, nullptr) != nullptr;
+}
+
+
+template<typename Func>
+bool commit_hook_impl(sqlite3 * db,
+ Func && func,
+ std::false_type)
+{
+ static_assert(noexcept(func()), "hook must be noexcept");
+ return sqlite3_commit_hook(
+ db,
+ [](void * data) { return (*static_cast<Func *>(data))() ? 1 : 0; },
+ &func) != nullptr;
+}
+
+inline bool commit_hook(sqlite3 * db, std::nullptr_t, std::false_type)
+{
+ return sqlite3_commit_hook(db, nullptr, nullptr);
+}
+
+
+
+template<typename Func>
+bool commit_hook(sqlite3 * db,
+ Func && func)
+{
+ using func_type = typename std::decay<Func>::type;
+ return commit_hook_impl(db, std::forward<Func>(func), std::is_pointer<func_type>{});
+}
+
+
+
+template<typename Func>
+bool rollback_hook_impl(sqlite3 * db,
+ Func * func,
+ std::true_type)
+{
+ static_assert(noexcept(func()), "hook must be noexcept");
+ return sqlite3_rollback_hook( db, func, [](void * data) { (*static_cast<Func *>(data))(); }, nullptr) != nullptr;
+}
+
+
+template<typename Func>
+bool rollback_hook_impl(sqlite3 * db,
+ Func && func,
+ std::false_type)
+{
+ static_assert(noexcept(func()), "hook must be noexcept");
+ return sqlite3_rollback_hook(
+ db,
+ [](void * data) { (*static_cast<Func *>(data))(); },
+ &func) != nullptr;
+}
+
+inline bool rollback_hook_impl(sqlite3 * db, std::nullptr_t, std::false_type)
+{
+ return sqlite3_rollback_hook(db, nullptr, nullptr);
+}
+
+template<typename Func>
+bool rollback_hook(sqlite3 * db,
+ Func && func)
+{
+ using func_type = typename std::decay<Func>::type;
+ return rollback_hook_impl(db, std::forward<Func>(func), std::is_pointer<func_type>{});
+}
+
+#if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+
+template<typename Func>
+bool preupdate_hook_impl(sqlite3 * db,
+ Func * func,
+ std::true_type)
+{
+ static_assert(noexcept(func(preupdate_context(nullptr), SQLITE_SELECT, "", "", 0, 0)), "hook must be noexcept");
+ return sqlite3_preupdate_hook(
+ db, func,
+ [](void * data,
+ sqlite3* db,
+ int op,
+ const char * db_name,
+ const char * table_name,
+ sqlite_int64 key1,
+ sqlite_int64 key2)
+ {
+ (*static_cast<Func *>(data))(preupdate_context(db), op, db_name, table_name, key1, key2);
+ }, nullptr) != nullptr;
+}
+
+
+template<typename Func>
+bool preupdate_hook_impl(sqlite3 * db,
+ Func & func,
+ std::false_type)
+{
+ static_assert(noexcept(func(preupdate_context(nullptr), SQLITE_SELECT, "", "", 0, 0)),
+ "hooks but be noexcept");
+ using func_type = typename std::decay<Func>::type;
+
+ return sqlite3_preupdate_hook(
+ db,
+ [](void * data,
+ sqlite3* db,
+ int op,
+ const char * db_name,
+ const char * table_name,
+ sqlite_int64 key1,
+ sqlite_int64 key2)
+ {
+ (*static_cast<Func *>(data))(preupdate_context(db), op, db_name, table_name, key1, key2);
+ }, &func) != nullptr;
+}
+
+inline bool preupdate_hook_impl(sqlite3 * db, std::nullptr_t, std::false_type)
+{
+ return sqlite3_preupdate_hook(db, nullptr, nullptr);
+}
+
+template<typename Func>
+bool preupdate_hook(sqlite3 * db,
+ Func && func)
+{
+ using func_type = typename std::decay<Func>::type;
+ return preupdate_hook_impl(db, std::forward<Func>(func), std::is_pointer<func_type>{});
+}
+
+#endif
+
+template<typename Func>
+bool update_hook_impl(sqlite3 * db,
+ Func * func,
+ std::true_type)
+{
+ static_assert(noexcept(func(SQLITE_SELECT, "", "", 0)), "hook must be noexcept");
+ return sqlite3_update_hook(
+ db, func,
+ [](void * data,
+ sqlite3*,
+ int op,
+ const char * db,
+ const char * name,
+ sqlite_int64 key)
+ {
+ (*static_cast<Func *>(data))(op, db, name, key);
+ }, nullptr) != nullptr;
+}
+
+
+template<typename Func>
+bool update_hook_impl(sqlite3 * db,
+ Func & func,
+ std::false_type)
+{
+ static_assert(noexcept(func(SQLITE_SELECT, "", "", 0)), "hook must be noexcept");
+ using func_type = typename std::decay<Func>::type;
+
+ return sqlite3_update_hook(
+ db,
+ [](void * data,
+ int op,
+ const char * db,
+ const char * name,
+ sqlite_int64 key)
+ {
+ (*static_cast<func_type*>(data))(op, db, name, key);
+ }, &func) != nullptr;
+}
+
+inline
+bool update_hook_impl(sqlite3 * db,
+ std::nullptr_t,
+ std::false_type)
+{
+ return sqlite3_update_hook(db, nullptr, nullptr);
+}
+
+template<typename Func>
+bool update_hook(sqlite3 * db,
+ Func && func)
+{
+ using func_type = typename std::decay<Func>::type;
+ return update_hook_impl(db, std::forward<Func>(func), std::is_pointer<func_type>{});
+}
+
+
+}
+
+/**
+ @brief Install a commit hook
+ @ingroup reference
+
+ @see [related sqlite documentation](https://www.sqlite.org/c3ref/commit_hook.html)
+
+ The commit hook gets called before a commit gets performed.
+ If `func` returns true, the commit goes, otherwise it gets rolled back.
+
+ @note If the function is not a free function pointer, this function will *NOT* take ownership.
+
+ @note If `func` is a `nullptr` the hook gets reset.
+
+ @param conn The database connection to install the hook in
+ @param func The hook function
+ @return true if an hook has been replaced.
+ */
+template<typename Func>
+bool commit_hook(connection & conn, Func && func)
+{
+ return detail::commit_hook(conn.handle(), std::forward<Func>(func));
+}
+
+/**
+ @brief Install a rollback hook
+ @ingroup reference
+
+ @see [related sqlite documentation](https://www.sqlite.org/c3ref/commit_hook.html)
+
+ The rollback hook gets called when a rollback gets performed.
+
+ @note If the function is not a free function pointer, this function will *NOT* take ownership.
+
+ @note If `func` is a `nullptr` the hook gets reset.
+
+ @param conn The database connection to install the hook in
+ @param func The hook function
+ @return true if an hook has been replaced.
+ */
+template<typename Func>
+bool rollback_hook(connection & conn, Func && func)
+{
+ return detail::rollback_hook(conn.handle(), std::forward<Func>(func));
+}
+
+#if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+/** A hook for pre-update events.
+
+ @note This is only available if sqlite was compiled with `SQLITE_ENABLE_PREUPDATE_HOOK` enabled.
+ @ingroup reference
+
+ @see [related sqlite documentation](https://sqlite.org/c3ref/preupdate_count.html)
+
+ The function will get called
+
+ @note If the function is not a free function pointer, this function will *NOT* take ownership.
+
+ @note If `func` is a `nullptr` the hook gets reset.
+
+ @param conn The database connection to install the hook in
+ @param func The hook function
+ @return true if an hook has been replaced.
+
+ The signature of the handler is as following (it must noexcept):
+
+ @code{.cpp}
+ void preupdate_hook(sqlite::preupdate_context ctx,
+ int op,
+ const char * db_name,
+ const char * table_name,
+ sqlite3_int64 current_key,
+ sqlite3_int64 new_key);
+ @endcode
+
+
+
+*/
+template<typename Func>
+bool preupdate_hook(connection & conn, Func && func)
+{
+ return detail::preupdate_hook(conn.handle(), std::forward<Func>(func));
+}
+
+#endif
+
+/**
+ @brief Install an update hook
+ @ingroup reference
+
+ @see [related sqlite documentation](https://www.sqlite.org/c3ref/update_hook.html)
+
+ The update hook gets called when an update was performed.
+
+ @note If the function is not a free function pointer, this function will *NOT* take ownership.
+
+ The signature of the function is `void(int op, core::string_view db, core::string_view table, sqlite3_int64 id)`.
+ `op` is either `SQLITE_INSERT`, `SQLITE_DELETE` and `SQLITE_UPDATE`.
+
+ @note If `func` is a `nullptr` the hook gets reset.
+
+ @param conn The database connection to install the hook in
+ @param func The hook function
+ @return true if an hook has been replaced.
+ */
+template<typename Func>
+bool update_hook(connection & conn, Func && func)
+{
+ return detail::update_hook(conn.handle(), std::forward<Func>(func));
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_HOOKS_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/json.hpp b/subprojects/boost-sqlite/include/boost/sqlite/json.hpp
new file mode 100644
index 0000000..3409e56
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/json.hpp
@@ -0,0 +1,140 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_JSON_HPP
+#define BOOST_SQLITE_JSON_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/field.hpp>
+#include <boost/sqlite/resultset.hpp>
+#include <boost/sqlite/value.hpp>
+#include <boost/json/parse.hpp>
+#include <boost/json/serializer.hpp>
+#include <boost/json/storage_ptr.hpp>
+#include <boost/json/value_from.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+struct resultset;
+struct field;
+struct value;
+
+/// @brief The subtype value used by the sqlite json extension. See the [sqlite reference](https://www.sqlite.org/json1.html)
+constexpr int json_subtype = static_cast<int>('J');
+
+inline void tag_invoke(const struct set_result_tag &, sqlite3_context * ctx, const json::value & value)
+{
+ json::serializer ser;
+ ser.reset(&value);
+
+ sqlite3_int64 len = 4096;
+ unique_ptr<char> c{static_cast<char*>(sqlite3_malloc64(len))};
+
+ len = sqlite3_msize(c.get());
+ auto v = ser.read(c.get(), len);
+
+ while (!ser.done())
+ {
+ auto l = v.size();
+ len *= 2;
+ c.reset(static_cast<char*>(sqlite3_realloc(c.release(), len)));
+ v = ser.read(c.get() + l, len);
+ }
+
+ sqlite3_result_text(ctx, c.release(), v.size(), sqlite3_free);
+ sqlite3_result_subtype(ctx, json_subtype);
+}
+
+///@{
+/// @brief Check if the value or field is a json. @ingroup reference
+inline bool is_json(const value & v) { return v.type() == value_type::text && v.subtype() == json_subtype; }
+inline bool is_json(const field & f) { return f.type() == value_type::text && f.get_value().subtype() == json_subtype; }
+///@}
+
+///@{
+/// @brief Convert the value or field to a json. @ingroup reference
+inline json::value as_json(const value & v, json::storage_ptr ptr = {})
+{
+ return json::parse(v.get_text(), ptr);
+}
+inline json::value as_json(const field & f, json::storage_ptr ptr = {})
+{
+ return json::parse(f.get_text(), ptr);
+}
+///@}
+
+
+inline void tag_invoke( const json::value_from_tag &, json::value& val, const value & f)
+{
+ switch (f.type())
+ {
+ case value_type::integer:
+ val.emplace_int64() = f.get_int();
+ break;
+ case value_type::floating:
+ val.emplace_double() = f.get_double();
+ break;
+ case value_type::text:
+ {
+ auto txt = f.get_text();
+ if (f.subtype() == json_subtype)
+ val = json::parse(txt, val.storage());
+ else
+ val.emplace_string() = txt;
+ }
+ break;
+ case value_type::blob:
+ throw_exception(std::invalid_argument("cannot convert blob to json"));
+ case value_type::null:
+ default:
+ val.emplace_null();
+ }
+}
+
+inline void tag_invoke( const json::value_from_tag &, json::value& val, const field & f)
+{
+ switch (f.type())
+ {
+ case value_type::integer:
+ val.emplace_int64() = f.get_int();
+ break;
+ case value_type::floating:
+ val.emplace_double() = f.get_double();
+ break;
+ case value_type::text:
+ {
+ auto txt = f.get_text();
+ if (f.get_value().subtype() == json_subtype)
+ val = json::parse(txt, val.storage());
+ else
+ val.emplace_string() = txt;
+ }
+ break;
+ case value_type::blob:
+ throw_exception(std::invalid_argument("cannot convert blob to json"));
+ case value_type::null:
+ default:
+ val.emplace_null();
+ }
+}
+
+inline void tag_invoke( const json::value_from_tag &, json::value& val, resultset && rs)
+{
+ auto & obj = val.emplace_array();
+
+ for (auto r : rs)
+ {
+ auto & row = obj.emplace_back(json::object(obj.storage())).get_object();
+ for (auto c : r)
+ row[c.column_name()] = json::value_from(c, row.storage());
+ }
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
+
+#endif //BOOST_SQLITE_JSON_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/memory.hpp b/subprojects/boost-sqlite/include/boost/sqlite/memory.hpp
new file mode 100644
index 0000000..e136a0e
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/memory.hpp
@@ -0,0 +1,105 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_MEMORY_HPP
+#define BOOST_SQLITE_MEMORY_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+
+#include <memory>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+/// A tag to allow `operator new`
+/// @ingroup reference
+struct memory_tag {};
+BOOST_SQLITE_END_NAMESPACE
+
+
+inline void *operator new ( std::size_t size, boost::sqlite::memory_tag) noexcept
+{
+ using namespace boost::sqlite;
+ return sqlite3_malloc64(size);
+}
+inline void *operator new[]( std::size_t size, boost::sqlite::memory_tag) noexcept
+{
+ using namespace boost::sqlite;
+ return sqlite3_malloc64(size);
+}
+
+inline void operator delete ( void* ptr, boost::sqlite::memory_tag) noexcept
+{
+ using namespace boost::sqlite;
+ return sqlite3_free(ptr);
+}
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+template<typename T>
+void delete_(T * t)
+{
+ struct scoped_free
+ {
+ void * p;
+ ~scoped_free()
+ {
+ sqlite3_free(p);
+ }
+ };
+ scoped_free _{t};
+ t->~T();
+}
+
+namespace detail
+{
+
+template<typename T>
+struct deleter
+{
+ void operator()(T* t)
+ {
+ delete_(t);
+ }
+};
+
+template<typename T>
+struct deleter<T[]>
+{
+ static_assert(std::is_trivially_destructible<T>::value, "T[] needs to be trivially destructible");
+ void operator()(T* t)
+ {
+ sqlite3_free(t);
+ }
+};
+
+template<>
+struct deleter<void>
+{
+ void operator()(void* t)
+ {
+ sqlite3_free(t);
+ }
+};
+}
+
+template<typename T>
+using unique_ptr = std::unique_ptr<T, detail::deleter<T>>;
+
+template<typename T>
+inline std::size_t msize(const unique_ptr<T> & ptr)
+{
+ return sqlite3_msize(ptr.get());
+}
+
+template<typename T, typename ... Args>
+unique_ptr<T> make_unique(Args && ... args)
+{
+ unique_ptr<void> up{sqlite3_malloc64(sizeof(T))};
+ unique_ptr<T> res{new (up.get()) T(std::forward<Args>(args)...)};
+ up.release();
+ return res;
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_MEMORY_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/meta_data.hpp b/subprojects/boost-sqlite/include/boost/sqlite/meta_data.hpp
new file mode 100644
index 0000000..00f6527
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/meta_data.hpp
@@ -0,0 +1,58 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_META_DATA_HPP
+#define BOOST_SQLITE_META_DATA_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/connection.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+
+struct connection ;
+
+/// The metadata of a column
+struct column_meta_data
+{
+ /// Data type fo the column
+ cstring_ref data_type;
+ /// Name of default collation sequence
+ cstring_ref collation;
+ /// true if column has a NOT NULL constraint
+ bool not_null;
+ /// true if column is part of the PRIMARY KEY
+ bool primary_key;
+ /// true if column is AUTOINCREMENT
+ bool auto_increment;
+};
+
+///@{
+/// get the meta-data of one colum
+BOOST_SQLITE_DECL
+column_meta_data table_column_meta_data(connection & conn,
+ cstring_ref db_name, cstring_ref table_name, cstring_ref column_name,
+ system::error_code & ec, error_info &ei);
+BOOST_SQLITE_DECL
+column_meta_data table_column_meta_data(connection & conn,
+ cstring_ref table_name, cstring_ref column_name,
+ system::error_code & ec, error_info &ei);
+
+BOOST_SQLITE_DECL
+column_meta_data table_column_meta_data(connection & conn,
+ cstring_ref db_name, cstring_ref table_name, cstring_ref column_name);
+BOOST_SQLITE_DECL
+column_meta_data table_column_meta_data(connection & conn,
+ cstring_ref table_name, cstring_ref column_name);
+///@}
+
+///
+BOOST_SQLITE_END_NAMESPACE
+
+
+#endif //BOOST_SQLITE_META_DATA_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/mutex.hpp b/subprojects/boost-sqlite/include/boost/sqlite/mutex.hpp
new file mode 100644
index 0000000..63caf0e
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/mutex.hpp
@@ -0,0 +1,55 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_MUTEX_HPP
+#define BOOST_SQLITE_MUTEX_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <memory>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+/// A mutex class that maybe a noop depending on the mode sqlite3 was compiled as.
+struct mutex
+{
+ bool try_lock()
+ {
+ if (!impl_)
+ return false;
+ return sqlite3_mutex_try(impl_.get()) == SQLITE_OK;
+ }
+ void lock() { sqlite3_mutex_enter(impl_.get()); }
+ void unlock() { sqlite3_mutex_leave(impl_.get()); }
+
+ mutex() : impl_(sqlite3_mutex_alloc(SQLITE_MUTEX_FAST)) {}
+ mutex(mutex && ) = delete;
+ private:
+ struct deleter_ {void operator()(sqlite3_mutex *mtx) {sqlite3_mutex_free(mtx);}};
+ std::unique_ptr<sqlite3_mutex, deleter_> impl_;
+};
+
+/// A recursive mutex class that maybe a noop depending on the mode sqlite3 was compiled as.
+struct recursive_mutex
+{
+ bool try_lock()
+ {
+ if (!impl_)
+ return false;
+ return sqlite3_mutex_try(impl_.get()) == SQLITE_OK;
+ }
+ void lock() { sqlite3_mutex_enter(impl_.get()); }
+ void unlock() { sqlite3_mutex_leave(impl_.get()); }
+
+ recursive_mutex() : impl_(sqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE)) {}
+ recursive_mutex(recursive_mutex && ) = delete;
+ private:
+ struct deleter_ {void operator()(sqlite3_mutex *mtx) {sqlite3_mutex_free(mtx);}};
+ std::unique_ptr<sqlite3_mutex, deleter_> impl_;
+};
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_MUTEX_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/result.hpp b/subprojects/boost-sqlite/include/boost/sqlite/result.hpp
new file mode 100644
index 0000000..b609eb4
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/result.hpp
@@ -0,0 +1,147 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_RESULT_HPP
+#define BOOST_SQLITE_RESULT_HPP
+
+#include <boost/sqlite/blob.hpp>
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/value.hpp>
+
+#include <boost/variant2/variant.hpp>
+
+#include <type_traits>
+
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+
+struct set_result_tag {};
+
+
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, blob b)
+{
+ auto sz = b.size();
+ sqlite3_result_blob(ctx, std::move(b).release(), sz, &operator delete);
+}
+
+
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, zero_blob zb)
+{
+ sqlite3_result_zeroblob64(ctx, static_cast<sqlite3_uint64>(zb));
+}
+
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, double dbl) { sqlite3_result_double(ctx, dbl); }
+
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, sqlite3_int64 value)
+{
+ sqlite3_result_int64(ctx, static_cast<sqlite3_int64>(value));
+}
+
+template<typename = std::enable_if_t<!std::is_same<std::int64_t, sqlite3_int64>::value>>
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::int64_t value)
+
+{
+ sqlite3_result_int64(ctx, static_cast<sqlite3_int64>(value));
+}
+
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::nullptr_t) { sqlite3_result_null(ctx); }
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, string_view str)
+{
+ sqlite3_result_text(ctx, str.data(), str.size(), SQLITE_TRANSIENT);
+}
+template<typename String>
+inline auto tag_invoke(set_result_tag, sqlite3_context * ctx, String && str)
+ -> typename std::enable_if<std::is_convertible<String, string_view>::value>::type
+{
+ return tag_invoke(set_result_tag{}, ctx, string_view(str));
+}
+
+
+inline void tag_invoke(set_result_tag, sqlite3_context * , variant2::monostate) { }
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, const value & val)
+{
+ sqlite3_result_value(ctx, val.handle());
+}
+
+struct set_variant_result
+{
+ sqlite3_context * ctx;
+ template<typename T>
+ void operator()(T && val)
+ {
+ tag_invoke(set_result_tag{}, ctx, std::forward<T>(val));
+ }
+};
+
+template<typename ... Args>
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, const variant2::variant<Args...> & var)
+{
+ visit(set_variant_result{ctx}, var);
+}
+
+template<typename T>
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr<T> ptr)
+{
+ sqlite3_result_pointer(ctx, ptr.release(), typeid(T).name(), +[](void * ptr){delete static_cast<T*>(ptr);});
+}
+
+template<typename T>
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr<T, void(*)(T*)> ptr)
+{
+ sqlite3_result_pointer(ctx, ptr.release(), typeid(T).name(), static_cast<void(*)(void*)>(ptr.get_deleter()));
+}
+
+template<typename T, typename Deleter>
+inline auto tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr<T, Deleter> ptr)
+ -> typename std::enable_if<std::is_empty<Deleter>::value &&
+ std::is_default_constructible<Deleter>::value>::type
+{
+ sqlite3_result_pointer(ctx, ptr.release(), typeid(T).name(), +[](void * ptr){Deleter()(static_cast<T*>(ptr));});
+}
+
+inline void tag_invoke(set_result_tag, sqlite3_context * ctx, error err)
+{
+ if (err.info)
+ sqlite3_result_error(ctx, err.info.message().c_str(), -1);
+ sqlite3_result_error_code(ctx, err.code);
+}
+
+
+template<typename T>
+inline void tag_invoke(set_result_tag tag, sqlite3_context * ctx, result<T> res)
+{
+ if (res.has_value())
+ tag_invoke(tag, ctx, std::move(res).value());
+ else
+ tag_invoke(tag, ctx, std::move(res).error());
+}
+
+inline void tag_invoke(set_result_tag tag, sqlite3_context * ctx, result<void> res)
+{
+ if (res.has_error())
+ tag_invoke(tag, ctx, std::move(res).error());
+}
+
+
+namespace detail
+{
+
+template<typename Value>
+inline auto set_result(sqlite3_context * ctx, Value && value)
+ -> decltype(tag_invoke(set_result_tag{}, ctx, std::forward<Value>(value)))
+{
+ tag_invoke(set_result_tag{}, ctx, std::forward<Value>(value));
+}
+
+
+}
+
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_RESULT_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/resultset.hpp b/subprojects/boost-sqlite/include/boost/sqlite/resultset.hpp
new file mode 100644
index 0000000..7c514f0
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/resultset.hpp
@@ -0,0 +1,150 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_RESULTSET_HPP
+#define BOOST_SQLITE_RESULTSET_HPP
+
+#include <memory>
+#include <boost/sqlite/row.hpp>
+#include <boost/describe/members.hpp>
+
+#include <boost/system/result.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+struct connection ;
+/**
+ @brief Representation of a result from a database.
+ @ingroup reference
+
+ If is a forward-range with output iterators.
+
+ @par Example
+
+ @code{.cpp}
+
+ extern sqlite::connection conn;
+
+ sqlite::resultset rs = conn.query("select * from users;");
+
+ do
+ {
+ handle_row(r.current());
+ }
+ while (rs.read_next()) // read it line by line
+
+
+ @endcode
+
+*/
+struct resultset
+{
+ /// Returns the current row.
+ row current() const &
+ {
+ row r;
+ r.stm_ = impl_.get();
+ return r;
+ }
+ /// Checks if the last row has been reached.
+ bool done() const {return done_;}
+
+ ///@{
+ /// Read the next row. Returns false if there's nothing more to read.
+ BOOST_SQLITE_DECL bool read_next(system::error_code & ec, error_info & ei);
+ BOOST_SQLITE_DECL bool read_next();
+ ///@}
+
+ ///
+ std::size_t column_count() const
+ {
+ return sqlite3_column_count(impl_.get());
+ }
+ /// Returns the name of the column idx.
+ cstring_ref column_name(std::size_t idx) const
+ {
+ return sqlite3_column_name(impl_.get(), static_cast<int>(idx));
+ }
+ /// Returns the name of the source table for column idx.
+ cstring_ref table_name(std::size_t idx) const
+ {
+ return sqlite3_column_table_name(impl_.get(), static_cast<int>(idx));
+ }
+ /// Returns the origin name of the column for column idx.
+ cstring_ref column_origin_name(std::size_t idx) const
+ {
+ return sqlite3_column_origin_name(impl_.get(), static_cast<int>(idx));
+ }
+
+ /// The input iterator can be used to read every row in a for-loop
+ struct iterator
+ {
+ using value_type = value;
+ using difference_type = int;
+ using reference = value&;
+ using iterator_category = std::forward_iterator_tag;
+
+ iterator() {}
+ explicit iterator(sqlite3_stmt * stmt, bool sentinel) : sentinel_(sentinel )
+ {
+ row_.stm_ = stmt;
+ }
+
+ bool operator!=(iterator rhs) const
+ {
+ return sentinel_ != rhs.sentinel_;
+ }
+
+ row &operator*() { return row_; }
+ row *operator->() { return &row_; }
+
+ BOOST_SQLITE_DECL
+ iterator operator++();
+
+ iterator operator++(int)
+ {
+ auto l = *this;
+ ++(*this);
+ return l;
+ }
+
+ private:
+ row row_;
+ bool sentinel_ = true;
+ };
+
+ /// Return an input iterator to the currently unread row
+ iterator begin() { return iterator(impl_.get(), done_);}
+ /// Sentinel iterator.
+ iterator end() { return iterator(impl_.get(), true); }
+
+ private:
+ friend struct connection;
+ friend struct statement;
+
+ struct deleter_
+ {
+ constexpr deleter_() noexcept {}
+ bool delete_ = true;
+ void operator()(sqlite3_stmt * sm)
+ {
+ if (sqlite3_data_count(sm) > 0)
+ while ( sqlite3_step(sm) == SQLITE_ROW);
+ if (delete_)
+ sqlite3_finalize(sm);
+ else
+ {
+ sqlite3_clear_bindings(sm);
+ sqlite3_reset(sm);
+ }
+
+ }
+ };
+ std::unique_ptr<sqlite3_stmt, deleter_> impl_;
+ bool done_ = false;
+};
+
+BOOST_SQLITE_END_NAMESPACE
+
+
+#endif //BOOST_SQLITE_RESULTSET_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/row.hpp b/subprojects/boost-sqlite/include/boost/sqlite/row.hpp
new file mode 100644
index 0000000..6893f96
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/row.hpp
@@ -0,0 +1,167 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_ROW_HPP
+#define BOOST_SQLITE_ROW_HPP
+
+#include <boost/sqlite/field.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+/** @brief Representation of a row in a database.
+ @ingroup reference
+
+ Is a random-access range.
+
+ All values that are obtained by view are valid until the next row is read.
+
+ */
+struct row
+{
+ /// The size of the row
+ std::size_t size() const
+ {
+ return sqlite3_column_count(stm_);
+ }
+ /// Returns the field at `idx`, @throws std::out_of_range
+ BOOST_SQLITE_DECL
+ field at(std::size_t idx) const;
+ /// Returns the field at `idx`.
+ field operator[](std::size_t idx) const
+ {
+ field f;
+ f.stm_ = stm_;
+ f.col_ = static_cast<int>(idx);
+ return f;
+ }
+ /// Random access iterator used to iterate over the columns.
+ struct const_iterator
+ {
+ using difference_type = int;
+ using reference = field&;
+ using iterator_category = std::random_access_iterator_tag;
+
+ const_iterator & operator++()
+ {
+ f_.col_++;
+ return *this;
+ }
+
+ const_iterator operator++(int)
+ {
+ auto last = *this;
+ ++(*this);
+ return last;
+ }
+
+ const_iterator & operator--()
+ {
+ f_.col_--;
+ return *this;
+ }
+
+ const_iterator operator--(int)
+ {
+ auto last = *this;
+ --(*this);
+ return last;
+ }
+
+ field operator[](int i) const
+ {
+ auto f = f_;
+ f.col_ += i;
+ return f;
+ }
+
+ const_iterator operator+(int i) const
+ {
+ auto r = *this;
+ r.f_.col_ += i;
+ return r;
+ }
+
+ const_iterator operator-(int i) const
+ {
+ auto r = *this;
+ r.f_.col_ -= i;
+ return r;
+ }
+
+ const_iterator & operator+=(int i)
+ {
+ f_.col_ += i;
+ return *this;
+ }
+
+ const_iterator & operator-=(int i)
+ {
+ f_.col_ -= i;
+ return *this;
+ }
+
+ const field & operator*() const
+ {
+ return f_;
+ }
+
+ const field * operator->() const
+ {
+ return &f_;
+ }
+
+ bool operator==(const const_iterator& other) const
+ {
+ return f_.col_ == other.f_.col_
+ && f_.stm_ == other.f_.stm_;
+ }
+
+ bool operator!=(const const_iterator& other) const
+ {
+ return f_.col_ != other.f_.col_
+ || f_.stm_ != other.f_.stm_;
+ }
+
+ bool operator<(const const_iterator& other) const
+ {
+ return f_.col_ < other.f_.col_
+ && f_.stm_ < other.f_.stm_;
+ }
+
+ bool operator>(const const_iterator& other) const
+ {
+ return f_.col_ > other.f_.col_
+ && f_.stm_ > other.f_.stm_;
+ }
+
+ const_iterator() = default;
+ private:
+ friend struct row;
+ field f_;
+ };
+ /// Returns the begin of the column-range.
+ const_iterator begin() const
+ {
+ const_iterator ci;
+ ci.f_.col_ = 0;
+ ci.f_.stm_ = stm_;
+ return ci;
+ }
+ /// Returns the end of the column-range.
+ const_iterator end() const
+ {
+ const_iterator ci;
+ ci.f_.col_ = sqlite3_column_count(stm_);
+ ci.f_.stm_ = stm_;
+ return ci;
+ }
+ private:
+ friend struct resultset;
+ sqlite3_stmt * stm_;
+
+};
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_ROW_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/statement.hpp b/subprojects/boost-sqlite/include/boost/sqlite/statement.hpp
new file mode 100644
index 0000000..b77bbe8
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/statement.hpp
@@ -0,0 +1,651 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_STATEMENT_HPP
+#define BOOST_SQLITE_STATEMENT_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/detail/exception.hpp>
+#include <boost/sqlite/blob.hpp>
+#include <boost/sqlite/resultset.hpp>
+
+#include <boost/mp11/algorithm.hpp>
+#include <boost/core/ignore_unused.hpp>
+#include <boost/variant2/variant.hpp>
+
+
+#include <tuple>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+struct connection;
+template<typename, bool>
+struct static_resultset;
+
+/// @brief A reference to a value to temporary bind for an execute statement. Most values are captures by reference.
+/// @ingroup reference
+struct param_ref
+{
+ /// Default construct a parameter, gives `null`.
+ param_ref() = default;
+ /// Bind null
+ param_ref(variant2::monostate) : impl_{variant2::in_place_type_t<variant2::monostate>{}} {}
+ /// Bind null
+ param_ref(std::nullptr_t) : impl_{variant2::in_place_type_t<variant2::monostate>{}} {}
+ /// Bind an integer.
+ template<typename I,
+ typename = typename std::enable_if<std::is_integral<I>::value>::type>
+ param_ref(I value)
+ {
+ BOOST_IF_CONSTEXPR ((sizeof(I) == sizeof(int) && std::is_unsigned<I>::value)
+ || (sizeof(I) > sizeof(int)))
+ impl_.emplace<sqlite3_int64>(static_cast<sqlite3_int64>(value));
+ else
+ impl_.emplace<int>(static_cast<int>(value));
+ }
+ /// Bind a blob.
+ param_ref(blob_view blob) : impl_(blob) { }
+ /// Bind a string.
+ param_ref(string_view text) : impl_(text) { }
+
+ template<typename StringLike>
+ param_ref(StringLike && text,
+ typename std::enable_if<std::is_constructible<string_view, StringLike>::value>::type * = nullptr)
+ : impl_(variant2::in_place_type_t<string_view>{}, text) {}
+
+ template<typename BlobLike>
+ param_ref(BlobLike && text,
+ typename std::enable_if<
+ !std::is_constructible<string_view, BlobLike>::value
+ && std::is_constructible<blob_view, BlobLike>::value>::type * = nullptr)
+ : impl_(variant2::in_place_type_t<blob_view>{}, text) {}
+
+ /// Bind a floating point value.
+ param_ref(double value) : impl_(value) { }
+ /// Bind a zero_blob value, i.e. a blob that initialized by zero.
+ param_ref(zero_blob zb) : impl_(zb) { }
+
+#if SQLITE_VERSION_NUMBER >= 3020000
+ /// Bind pointer value to the parameter. @see https://www.sqlite.org/bindptr.html
+ template<typename T>
+ param_ref(std::unique_ptr<T> ptr)
+ : impl_(variant2::in_place_index_t<7>{},
+ std::unique_ptr<void, void(*)(void*)>(
+ static_cast<void*>(ptr.release()),
+ +[](void * ptr){delete static_cast<T*>(ptr);}),
+ typeid(T).name())
+ {
+ }
+
+ /// Bind pointer value with a function as deleter to the parameter. @see https://www.sqlite.org/bindptr.html
+ template<typename T>
+ param_ref(std::unique_ptr<T, void(*)(T*)> ptr)
+ : impl_(variant2::in_place_index_t<7>{},
+ std::unique_ptr<void, void(*)(void*)>(
+ static_cast<void*>(ptr.release()),
+ +[](void * ptr){delete static_cast<T*>(ptr);}),
+ typeid(T).name())
+ {
+ }
+
+ /// @brief Bind pointer value with a function custom deleter to the parameter.
+ /// The deleter needs to be default constructible. @see https://www.sqlite.org/bindptr.html
+ template<typename T, typename Deleter>
+ param_ref(std::unique_ptr<T, Deleter> ptr,
+ typename std::enable_if<std::is_empty<Deleter>::value &&
+ std::is_default_constructible<Deleter>::value, int>::type * = nullptr)
+ : impl_(variant2::in_place_index_t<7>{},
+ std::unique_ptr<void, void(*)(void*)>(
+ static_cast<void*>(ptr.release()),
+ +[](void * ptr){delete static_cast<T*>(ptr);}),
+ typeid(T).name())
+ {
+ }
+#endif
+
+ /// Apply the param_ref to a statement.
+ int apply(sqlite3_stmt * stmt, int c) const
+ {
+ return variant2::visit(visitor{stmt, c}, impl_);
+ }
+
+ private:
+ struct make_visitor
+ {
+ template<typename T>
+ auto operator()(T&& t) const -> typename std::enable_if<std::is_constructible<param_ref, T&&>::value, param_ref>::type
+ {
+ return param_ref(std::forward<T>(t));
+ }
+ };
+
+ public:
+ /// Construct param_ref from a variant
+ template<typename T>
+ param_ref(T && t,
+ decltype(variant2::visit(make_visitor(), std::forward<T>(t))) * = nullptr)
+ : param_ref(variant2::visit(make_visitor(), std::forward<T>(t)))
+ {}
+ private:
+
+ struct visitor
+ {
+ sqlite3_stmt * stmt;
+ int col;
+
+ int operator()(variant2::monostate )
+ {
+ return sqlite3_bind_null(stmt, col);
+ }
+ int operator()(int i )
+ {
+ return sqlite3_bind_int(stmt, col, i);
+ }
+ int operator()(sqlite3_int64 i64 )
+ {
+ return sqlite3_bind_int64(stmt, col, i64);
+ }
+
+ int operator()(blob_view blob)
+ {
+ if (blob.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()))
+ return sqlite3_bind_blob64(stmt, col, blob.data(), blob.size(), SQLITE_STATIC);
+ else
+ return sqlite3_bind_blob(stmt, col, blob.data(), static_cast<int>(blob.size()), SQLITE_STATIC);
+ }
+
+ int operator()(string_view text)
+ {
+ if (text.size() > std::numeric_limits<int>::max())
+ return sqlite3_bind_text64(stmt, col, text.data(), text.size(), SQLITE_STATIC, SQLITE_UTF8);
+ else
+ return sqlite3_bind_text(stmt, col, text.data(), static_cast<int>(text.size()), SQLITE_STATIC);
+ }
+ int operator()(double value)
+ {
+ return sqlite3_bind_double(stmt, col, value);
+ }
+ int operator()(zero_blob zb)
+ {
+ if (static_cast<std::size_t>(zb) > static_cast<std::size_t>(std::numeric_limits<int>::max()))
+ return sqlite3_bind_zeroblob64(stmt, col, static_cast<sqlite3_uint64>(zb));
+ else
+ return sqlite3_bind_zeroblob(stmt, col, static_cast<int>(zb));
+ }
+#if SQLITE_VERSION_NUMBER >= 3020000
+ int operator()(std::pair<std::unique_ptr<void, void(*)(void*)>, const char*> & p)
+ {
+ auto d =p.first.get_deleter();
+ return sqlite3_bind_pointer(stmt, col, p.first.release(), p.second, d);
+ }
+#endif
+ };
+
+ mutable // so we can use it with
+ variant2::variant<variant2::monostate, int, sqlite3_int64,
+ blob_view, string_view, double, zero_blob
+#if SQLITE_VERSION_NUMBER >= 3020000
+ , std::pair<std::unique_ptr<void, void(*)(void*)>, const char*>
+#endif
+ > impl_;
+};
+
+
+/** @brief A statement used for a prepared-statement.
+ @ingroup reference
+
+ */
+struct statement
+{
+ ///@{
+ /** @brief execute the prepared statement once.
+
+ @param params The arguments to be passed to the prepared statement. This can be a map or a vector of param_ref.
+ @param ec The system::error_code used to deliver errors for the exception less overload.
+ @param info The error_info used to deliver errors for the exception less overload.
+ @return The resultset of the query.
+
+ @code{.cpp}
+ extern sqlite::connection conn;
+ statement st = conn.prepare("select id from users where name = $1;");
+ resultset q = std::move(st).execute(std::make_tuple("peter"));
+ @endcode
+
+ */
+ template <typename ArgRange = std::initializer_list<param_ref>>
+ resultset execute(
+ ArgRange && params,
+ system::error_code& ec,
+ error_info& info) &&
+ {
+ bind_impl(std::forward<ArgRange>(params), ec, info);
+ resultset rs;
+ rs.impl_.reset(impl_.release());
+ if (!ec)
+ rs.read_next(ec, info);
+ return rs;
+ }
+
+ template <typename ArgRange = std::initializer_list<param_ref>>
+ resultset execute(ArgRange && params) &&
+ {
+ system::error_code ec;
+ error_info ei;
+ auto tmp = std::move(*this).execute(std::forward<ArgRange>(params), ec, ei);
+ if (ec)
+ detail::throw_error_code(ec, ei);
+ return tmp;
+ }
+
+ resultset execute(
+ std::initializer_list<std::pair<string_view, param_ref>> params,
+ system::error_code& ec,
+ error_info& info) &&
+ {
+ bind_impl(std::move(params), ec, info);
+ resultset rs;
+ rs.impl_.reset(impl_.release());
+ if (!ec)
+ rs.read_next(ec, info);
+ return rs;
+ }
+
+ resultset execute(std::initializer_list<std::pair<string_view, param_ref>> params) &&
+ {
+ system::error_code ec;
+ error_info ei;
+ auto tmp = std::move(*this).execute(std::move(params), ec, ei);
+ if (ec)
+ detail::throw_error_code(ec, ei);
+ return tmp;
+ }
+
+ template<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>>
+ static_resultset<T, Strict> execute(
+ ArgRange && params,
+ system::error_code & ec,
+ error_info & ei) &&
+ {
+ static_resultset<T, Strict> tmp = std::move(*this).execute(std::forward<ArgRange>(params), ec, ei);
+ if (ec)
+ return {};
+ tmp.check_columns_(ec, ei);
+ if (ec)
+ return {};
+
+ return tmp;
+ }
+
+ template<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>>
+ static_resultset<T, Strict> execute(ArgRange && params) &&
+ {
+ system::error_code ec;
+ error_info ei;
+ auto tmp = std::move(*this).execute<T>(std::forward<ArgRange>(params), ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return tmp;
+ }
+
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> execute(
+ std::initializer_list<std::pair<string_view, param_ref>> params,
+ system::error_code & ec,
+ error_info & ei) &&
+ {
+ static_resultset<T, Strict> tmp = std::move(*this).execute(std::move(params), ec, ei);
+ if (ec)
+ return {};
+ tmp.check_columns_(ec, ei);
+ if (ec)
+ return {};
+
+ return tmp;
+ }
+
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> execute(std::initializer_list<std::pair<string_view, param_ref>> params) &&
+ {
+ system::error_code ec;
+ error_info ei;
+ auto tmp = std::move(*this).execute<T, Strict>(std::move(params), ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return tmp;
+ }
+
+ ///@}
+
+ ///@{
+ /** @brief execute the prepared statement and reset it afterwards.
+
+ @warning The handle is shared between the statement & resultset. The statemens need to be kept alive.
+
+ @param params The arguments to be passed to the prepared statement. This can be a map, a vector or a stuple of param_ref.
+ @param ec The system::error_code used to deliver errors for the exception less overload.
+ @param info The error_info used to deliver errors for the exception less overload.
+ @return The resultset of the query.
+
+ @code{.cpp}
+ extern sqlite::connection conn;
+ statement st = conn.prepare("select id from users where name = $1;");
+ resultset q = std::move(st).execute(std::make_tuple("peter"));
+ @endcode
+
+
+
+ */
+ template <typename ArgRange = std::initializer_list<param_ref>>
+ resultset execute(
+ ArgRange && params,
+ system::error_code& ec,
+ error_info& info) &
+ {
+ bind_impl(std::forward<ArgRange>(params), ec, info);
+ resultset rs;
+ rs.impl_.get_deleter().delete_ = false;
+ rs.impl_.reset(impl_.get());
+ if (!ec)
+ rs.read_next(ec, info);
+ return rs;
+ }
+
+
+ template <typename ArgRange = std::initializer_list<param_ref>>
+ resultset execute(ArgRange && params) &
+ {
+ system::error_code ec;
+ error_info ei;
+ auto tmp = execute(std::forward<ArgRange>(params), ec, ei);
+ if (ec)
+ detail::throw_error_code(ec, ei);
+ return tmp;
+ }
+
+
+ resultset execute(
+ std::initializer_list<std::pair<string_view, param_ref>> params,
+ system::error_code& ec,
+ error_info& info) &
+ {
+ bind_impl(std::move(params), ec, info);
+ resultset rs;
+ rs.impl_.get_deleter().delete_ = false;
+ rs.impl_.reset(impl_.get());
+ if (!ec)
+ rs.read_next(ec, info);
+ return rs;
+ }
+
+ resultset execute(std::initializer_list<std::pair<string_view, param_ref>> params) &
+ {
+ system::error_code ec;
+ error_info ei;
+ auto tmp = execute(std::move(params), ec, ei);
+ if (ec)
+ detail::throw_error_code(ec, ei);
+ return tmp;
+ }
+
+ template<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>>
+ static_resultset<T, Strict> execute(
+ ArgRange && params,
+ system::error_code & ec,
+ error_info & ei) &
+ {
+ static_resultset<T, Strict> tmp = execute(std::forward<ArgRange>(params), ec, ei);
+ if (ec)
+ return {};
+ tmp.check_columns_(ec, ei);
+ if (ec)
+ return {};
+
+ return tmp;
+ }
+
+ template<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>>
+ static_resultset<T, Strict> execute(ArgRange && params) &
+ {
+ system::error_code ec;
+ error_info ei;
+ auto tmp = execute<T, Strict>(std::forward<ArgRange>(params), ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return tmp;
+ }
+
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> execute(
+ std::initializer_list<std::pair<string_view, param_ref>> params,
+ system::error_code & ec,
+ error_info & ei) &
+ {
+ static_resultset<T, Strict> tmp = execute(std::move(params), ec, ei);
+ if (ec)
+ return {};
+ tmp.check_columns_(ec, ei);
+ if (ec)
+ return {};
+
+ return tmp;
+ }
+
+ template<typename T, bool Strict = false>
+ static_resultset<T, Strict> execute(std::initializer_list<std::pair<string_view, param_ref>> params) &
+ {
+ system::error_code ec;
+ error_info ei;
+ auto tmp = execute<T, Strict>(std::move(params), ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return tmp;
+ }
+
+ ///@}
+
+
+ /// Returns the sql used to construct the prepared statement.
+ core::string_view sql()
+ {
+ return sqlite3_sql(impl_.get());
+ }
+
+#if SQLITE_VERSION_NUMBER >= 3014000
+ /// Returns the expanded sql used to construct the prepared statement.
+ core::string_view expanded_sql()
+ {
+ return sqlite3_expanded_sql(impl_.get());
+ }
+#endif
+
+ /// Returns the expanded sql used to construct the prepared statement.
+#ifdef SQLITE_ENABLE_NORMALIZE
+ core::string_view normalized_sql()
+ {
+ return sqlite3_normalized_sql(impl_.get());
+ }
+#endif
+
+ /// Returns the declared type of the column
+ core::string_view declared_type(int id) const
+ {
+ return sqlite3_column_decltype(impl_.get(), id);
+ }
+
+ private:
+
+ template<typename ... Args>
+ void bind_impl(std::tuple<Args...> && vec,
+ system::error_code & ec,
+ error_info & ei)
+ {
+ const auto sz = sqlite3_bind_parameter_count(impl_.get());
+ if (sizeof...(Args) < static_cast<std::size_t>(sz))
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_ERROR);
+ ei.format("To few parameters provided. Needed %d got %ld",
+ sz, sizeof...(Args));
+ return;
+ }
+
+ int i = 1, ar = SQLITE_OK;
+ mp11::tuple_for_each(std::move(vec),
+ [&](param_ref pr)
+ {
+ if (ar == SQLITE_OK)
+ ar = pr.apply(impl_.get(), i++);
+ });
+ if (ar != SQLITE_OK)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, ar);
+ ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get())));
+ return;
+ }
+ }
+
+
+ template<typename ... Args>
+ void bind_impl(const std::tuple<Args...> & vec,
+ system::error_code & ec,
+ error_info & ei)
+ {
+ const auto sz = sqlite3_bind_parameter_count(impl_.get());
+ if (static_cast<int>(sizeof...(Args)) < sz)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_ERROR);
+ ei.format("To few parameters provided. Needed %d got %ld",
+ sz, sizeof...(Args));
+ return;
+ }
+
+ int i = 1, ar = SQLITE_OK;
+ mp11::tuple_for_each(std::move(vec),
+ [&](param_ref pr)
+ {
+ if (ar == SQLITE_OK)
+ ar = pr.apply(impl_.get(), i++);
+ });
+ if (ar != SQLITE_OK)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, ar);
+ ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get())));
+ return;
+ }
+ }
+
+ template<typename ParamVector>
+ void bind_impl(ParamVector && vec, system::error_code & ec, error_info & ei,
+ typename std::enable_if<std::is_convertible<
+ typename std::decay<ParamVector>::type::value_type, param_ref>::value>::type * = nullptr)
+ {
+ const auto sz = sqlite3_bind_parameter_count(impl_.get());
+ if (vec.size() < static_cast<std::size_t>(sz))
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_ERROR);
+ ei.format("To few parameters provided. Needed %d got %ld",
+ sz, vec.size());
+ }
+ int i = 1;
+ for (const param_ref & pr : std::forward<ParamVector>(vec))
+ {
+ int ar = pr.apply(impl_.get(), i++);
+ if (ar != SQLITE_OK)
+ {
+
+ BOOST_SQLITE_ASSIGN_EC(ec, ar);
+ ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get())));
+ return;
+ }
+ }
+ }
+
+ template<typename ParamMap>
+ void bind_impl(ParamMap && vec, system::error_code & ec, error_info & ei,
+ typename std::enable_if<
+ std::is_convertible<typename std::decay<ParamMap>::type::key_type, string_view>::value &&
+ std::is_convertible<typename std::decay<ParamMap>::type::mapped_type, param_ref>::value
+ >::type * = nullptr)
+ {
+ for (auto i = 1; i <= sqlite3_bind_parameter_count(impl_.get()); i ++)
+ {
+ auto c = sqlite3_bind_parameter_name(impl_.get(), i);
+ if (c == nullptr)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISUSE);
+ ei.set_message("Parameter maps require all parameters to be named.");
+ return ;
+ }
+ auto itr = vec.find(c+1);
+ if (itr == vec.end())
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISUSE);
+ ei.format("Can't find value for key '%s'", c+1);
+ return ;
+ }
+ int ar = SQLITE_OK;
+ if (std::is_rvalue_reference<ParamMap&&>::value)
+ ar = param_ref(std::move(itr->second)).apply(impl_.get(), i);
+ else
+ ar = param_ref(itr->second).apply(impl_.get(), i);
+
+ if (ar != SQLITE_OK)
+ {
+
+ BOOST_SQLITE_ASSIGN_EC(ec, ar);
+ ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get())));
+ return;
+ }
+ }
+ }
+
+ void bind_impl(std::initializer_list<std::pair<string_view, param_ref>> params,
+ system::error_code & ec, error_info & ei)
+ {
+ for (auto i = 1; i <= sqlite3_bind_parameter_count(impl_.get()); i ++)
+ {
+ auto c = sqlite3_bind_parameter_name(impl_.get(), i);
+ if (c == nullptr)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISUSE);
+ ei.set_message("Parameter maps require all parameters to be named.");
+ return ;
+ }
+
+ auto itr = std::find_if(params.begin(), params.end(),
+ [&](const std::pair<string_view, param_ref> & p)
+ {
+ return p.first == (c+1);
+ });
+ if (itr == params.end())
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISUSE);
+ ei.format("Can't find value for key '%s'", c+1);
+ return ;
+ }
+ auto ar = itr->second.apply(impl_.get(), i);
+ if (ar != SQLITE_OK)
+ {
+
+ BOOST_SQLITE_ASSIGN_EC(ec, ar);
+ ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get())));
+ return;
+ }
+ }
+ }
+
+
+ friend
+ struct connection;
+ struct deleter_
+ {
+ void operator()(sqlite3_stmt * sm)
+ {
+ sqlite3_finalize(sm);
+ }
+ };
+ std::unique_ptr<sqlite3_stmt, deleter_> impl_;
+};
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_STATEMENT_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/static_resultset.hpp b/subprojects/boost-sqlite/include/boost/sqlite/static_resultset.hpp
new file mode 100644
index 0000000..fd6c359
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/static_resultset.hpp
@@ -0,0 +1,497 @@
+//
+// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_STATIC_RESULTSET_HPP
+#define BOOST_SQLITE_STATIC_RESULTSET_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/resultset.hpp>
+#include <boost/sqlite/connection.hpp>
+
+#include <boost/describe/members.hpp>
+
+#include <array>
+#include <cstdint>
+
+#if __cplusplus >= 202002L
+#include <boost/pfr/core.hpp>
+#include <boost/pfr/core_name.hpp>
+#include <boost/pfr/traits.hpp>
+#endif
+
+
+namespace boost { template<typename> class optional;}
+
+#if __cplusplus >= 201702L
+#include <optional>
+#endif
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+namespace detail
+{
+
+inline void convert_field(sqlite_int64 & target, const field & f) {target = f.get_int();}
+
+template<typename = std::enable_if_t<!std::is_same<std::int64_t, sqlite_int64>::value>>
+inline void convert_field(std::int64_t & target, const field & f)
+{
+ target = static_cast<std::int64_t>(f.get_int());
+}
+
+inline void convert_field(double & target, const field & f) {target = f.get_double();}
+
+
+template<typename Traits = std::char_traits<char>, typename Allocator = std::allocator<char>>
+inline void convert_field(std::basic_string<char, Traits, Allocator> & target, const field & f)
+{
+ auto t = f.get_text();
+ target.assign(t.begin(), t.end());
+}
+
+inline void convert_field(string_view & target, const field & f) {target = f.get_text();}
+inline void convert_field(blob & target, const field & f) {target = blob(f.get_blob());}
+inline void convert_field(blob_view & target, const field & f) {target = f.get_blob();}
+
+#if __cplusplus >= 201702L
+template<typename T>
+inline void convert_field(std::optional<T> & target, const field & f)
+{
+ if (f.is_null())
+ target.reset();
+ else
+ convert_field(target.emplace(), f);
+}
+#endif
+
+template<typename T>
+inline void convert_field(boost::optional<T> & target, const field & f)
+{
+ if (f.is_null())
+ target.reset();
+ else
+ return convert_field(target.emplace_back(), f);
+}
+
+template<typename T>
+inline constexpr bool field_type_is_nullable(const T& ) {return false;}
+#if __cplusplus >= 201702L
+template<typename T>
+inline bool field_type_is_nullable(const std::optional<T> &) { return true; }
+#endif
+template<typename T>
+inline bool field_type_is_nullable(const boost::optional<T> &) { return true; }
+
+inline value_type required_field_type(const sqlite3_int64 &) {return value_type::integer;}
+
+template<typename = std::enable_if_t<!std::is_same<std::int64_t, sqlite_int64>::value>>
+inline value_type required_field_type(const std::int64_t &) {return value_type::integer;}
+
+template<typename Allocator, typename Traits>
+inline value_type required_field_type(const std::basic_string<char, Allocator, Traits> & )
+{
+ return value_type::text;
+}
+
+inline value_type required_field_type(const string_view &) {return value_type::text;}
+inline value_type required_field_type(const blob &) {return value_type::blob;}
+inline value_type required_field_type(const blob_view &) {return value_type::blob;}
+
+
+template<typename ... Args>
+void check_columns(const std::tuple<Args...> *, const resultset & r,
+ system::error_code &ec, error_info & ei)
+{
+ if (r.column_count() != sizeof...(Args))
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH);
+ ei.format("Tuple size doesn't match column count [%ld != %ld]", sizeof...(Args), r.column_count());
+ }
+}
+
+template<bool Strict, typename ... Args>
+void convert_row(std::tuple<Args...> & res, const row & r, system::error_code ec, error_info & ei)
+{
+ std::size_t idx = 0u;
+
+ mp11::tuple_for_each(
+ res,
+ [&](auto & v)
+ {
+ const auto i = idx++;
+ const auto & f = r[i];
+ BOOST_IF_CONSTEXPR (Strict)
+ {
+ if (!ec) // only check if we don't have an error yet.
+ {
+ if (f.is_null() && !field_type_is_nullable(v))
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_NOTNULL);
+ ei.format("unexpected null in column %d", i);
+ }
+ else if (f.type() != required_field_type(v))
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_DATATYPE);
+ ei.format("unexpected type [%s] in column %d, expected [%s]",
+ value_type_name(f.type()), i, value_type_name(required_field_type(v)));
+ }
+ }
+ }
+ else
+ boost::ignore_unused(ec, ei);
+
+ detail::convert_field(v, f);
+ });
+}
+
+#if defined(BOOST_DESCRIBE_CXX14)
+
+template<typename T, typename = typename std::enable_if<describe::has_describe_members<T>::value>::type>
+void check_columns(const T *, const resultset & r,
+ system::error_code &ec, error_info & ei)
+{
+ using mems = boost::describe::describe_members<T, describe::mod_public>;
+ constexpr std::size_t sz = mp11::mp_size<mems>();
+ if (r.column_count() != sz)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH);
+ ei.format("Describe size doesn't match column count [%ld != %ld]", sz, r.column_count());
+ }
+
+ // columns can be duplicated!
+ std::array<bool, sz> found;
+ std::fill(found.begin(), found.end(), false);
+
+ for (std::size_t i = 0ul; i < r.column_count(); i++)
+ {
+ bool cfound = false;
+ boost::mp11::mp_for_each<mp11::mp_iota_c<sz>>(
+ [&](auto sz)
+ {
+ auto d = mp11::mp_at_c<mems, sz>();
+ if (d.name == r.column_name(i))
+ {
+ found[sz] = true;
+ cfound = true;
+ }
+ });
+
+ if (!cfound)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH);
+ ei.format("Column '%s' not found in described struct.", r.column_name(i).c_str());
+ break;
+ }
+ }
+
+ if (ec)
+ return;
+
+
+ auto itr = std::find(found.begin(), found.end(), false);
+ if (itr != found.end())
+ {
+ mp11::mp_with_index<sz>(
+ std::distance(found.begin(), itr),
+ [&](auto sz)
+ {
+ auto d = mp11::mp_at_c<mems, sz>();
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH);
+ ei.format("Described field '%s' not found in resultset struct.", d.name);
+ });
+ }
+}
+
+template<bool Strict, typename T,
+ typename = typename std::enable_if<describe::has_describe_members<T>::value>::type>
+void convert_row(T & res, const row & r, system::error_code ec, error_info & ei)
+{
+ for (auto && f: r)
+ {
+ boost::mp11::mp_for_each<boost::describe::describe_members<T, describe::mod_public> >(
+ [&](auto D)
+ {
+ if (D.name == f.column_name())
+ {
+ auto & r = res.*D.pointer;
+ BOOST_IF_CONSTEXPR(Strict)
+ {
+ if (!ec)
+ {
+ if (f.is_null() && !field_type_is_nullable(r))
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_NOTNULL);
+ ei.format("unexpected null in column %s", D.name);
+ }
+ else if (f.type() != required_field_type(r))
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_DATATYPE);
+ ei.format("unexpected type [%s] in column %s, expected [%s]",
+ value_type_name(f.type()), D.name, value_type_name(required_field_type(r)));
+ }
+ }
+ }
+
+ detail::convert_field(r, f);
+ }
+ });
+ }
+}
+
+#endif
+
+#if __cplusplus >= 202002L
+
+template<typename T>
+ requires (std::is_aggregate_v<T> && !describe::has_describe_members<T>::value)
+void check_columns(const T *, const resultset & r,
+ system::error_code &ec, error_info & ei)
+{
+ constexpr std::size_t sz = pfr::tuple_size_v<T>;
+ if (r.column_count() != sz)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH);
+ ei.format("Describe size doesn't match column count [%ld != %ld]", sz, r.column_count());
+ }
+
+ // columns can be duplicated!
+ std::array<bool, sz> found;
+ std::fill(found.begin(), found.end(), false);
+
+ for (std::size_t i = 0ul; i < r.column_count(); i++)
+ {
+ bool cfound = false;
+ boost::mp11::mp_for_each<mp11::mp_iota_c<sz>>(
+ [&](auto sz)
+ {
+ if (pfr::get_name<sz, T>() == r.column_name(i))
+ {
+ found[sz] = true;
+ cfound = true;
+ }
+ });
+
+ if (!cfound)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH);
+ ei.format("Column %s not found in struct.", r.column_name(i).c_str());
+ break;
+ }
+ }
+
+ if (ec)
+ return;
+
+
+ auto itr = std::find(found.begin(), found.end(), false);
+ if (itr != found.end())
+ {
+ mp11::mp_with_index<sz>(
+ std::distance(found.begin(), itr),
+ [&](auto sz)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH);
+ auto nm = pfr::get_name<sz, T>();
+ ei.format("PFR field %.*s not found in resultset struct.", static_cast<int>(nm.size()), nm.data());
+ });
+ }
+}
+
+template<bool Strict, typename T>
+ requires (std::is_aggregate_v<T> && !describe::has_describe_members<T>::value)
+void convert_row(T & res, const row & r, system::error_code ec, error_info & ei)
+{
+ for (auto && f: r)
+ {
+ boost::mp11::mp_for_each<mp11::mp_iota_c<pfr::tuple_size_v<T>>>(
+ [&](auto D)
+ {
+ if (pfr::get_name<D, T>() == f.column_name().c_str())
+ {
+ auto & r = pfr::get<D()>(res);
+ BOOST_IF_CONSTEXPR(Strict)
+ {
+ if (!ec)
+ {
+ if (f.is_null() && !field_type_is_nullable(r))
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_NOTNULL);
+ ei.format("unexpected null in column %s", D.name);
+ }
+ else if (f.type() != required_field_type(r))
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_DATATYPE);
+ ei.format("unexpected type [%s] in column %s, expected [%s]",
+ value_type_name(f.type()), D.name, value_type_name(required_field_type(r)));
+ }
+ }
+ }
+ detail::convert_field(r, f);
+ }
+ });
+ }
+}
+
+#endif
+
+}
+
+/**
+ @brief A typed resultset using a tuple or a described struct.
+ @ingroup reference
+ @tparam T The static type of the query.
+ @tparam Strict Disables implicit conversions.
+
+ If is a forward-range with output iterators.
+
+ @par Example
+
+ @code{.cpp}
+
+ extern sqlite::connection conn;
+ struct user { std::string first_name; std::string last_name; };
+ BOOST_DESCRIBE_STRUCT(user, (), (first_name, last_name));
+
+ sqlite::resultset rs = conn.query("select first_name, last_name from users;");
+
+ do
+ {
+ user usr = r.current();
+ handle_row(u);
+ }
+ while (rs.read_next()) // read it line by line
+
+ @endcode
+
+*/
+template<typename T, bool Strict >
+struct static_resultset
+{
+ /// Returns the current row.
+ T current() const &
+ {
+ system::error_code ec;
+ error_info ei;
+ auto tmp = current(ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return tmp;
+ }
+
+ /// Returns the current row.
+ T current(system::error_code & ec, error_info & ei) const &
+ {
+ T res;
+ detail::convert_row<Strict>(res, result_.current(), ec, ei);
+ return res;
+ }
+
+ /// Checks if the last row has been reached.
+ bool done() const {return result_.done();}
+
+ ///@{
+ /// Read the next row. Returns false if there's nothing more to read.
+ BOOST_SQLITE_DECL bool read_next(system::error_code & ec, error_info & ei) { return result_.read_next(ec, ei); }
+ BOOST_SQLITE_DECL bool read_next() { return result_.read_next(); }
+ ///@}
+
+ ///
+ std::size_t column_count() const { return result_.column_count(); }
+ /// Returns the name of the column idx.
+ core::string_view column_name(std::size_t idx) const { return result_.column_name(idx); }
+
+ /// Returns the name of the source table for column idx.
+ core::string_view table_name(std::size_t idx) const { return result_.table_name(idx);}
+ /// Returns the origin name of the column for column idx.
+ core::string_view column_origin_name(std::size_t idx) const { return result_.column_origin_name(idx);}
+
+ static_resultset() = default;
+ static_resultset(resultset && result) : result_(std::move(result)) { }
+
+
+ static_resultset(static_resultset<T, false> && rhs) : result_(std::move(rhs.result_)) {}
+
+ /// The input iterator can be used to read every row in a for-loop
+ struct iterator
+ {
+ using value_type = T;
+ using difference_type = int;
+ using reference = T&;
+ using iterator_category = std::forward_iterator_tag;
+
+ iterator()
+ {
+
+ }
+ explicit iterator(resultset::iterator itr) : itr_(itr)
+ {
+ system::error_code ec;
+ error_info ei;
+ if (itr->size() > 0ul)
+ detail::convert_row<Strict>(value_, *itr, ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ }
+
+ bool operator!=(iterator rhs) const
+ {
+ return itr_ != rhs.itr_;
+ }
+
+ value_type &operator*() { return value_; }
+ value_type *operator->() { return &value_; }
+
+ iterator& operator++()
+ {
+ ++itr_;
+
+ system::error_code ec;
+ error_info ei;
+ if (itr_->size() > 0ul)
+ detail::convert_row<Strict>(value_, *itr_, ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+
+ return *this;
+ }
+ iterator operator++(int)
+ {
+ auto l = *this;
+ ++(*this);
+ return l;
+ }
+ private:
+ resultset::iterator itr_;
+ value_type value_;
+ };
+
+ /// Return an input iterator to the currently unread row
+ iterator begin() { return iterator(result_.begin());}
+ /// Sentinel iterator.
+ iterator end() { return iterator(result_.end()); }
+
+
+
+ static_resultset<T, true> strict() &&
+ {
+ return {std::move(result_)};
+ }
+ private:
+
+ friend struct static_resultset<T, true>;
+ friend struct connection;
+ friend struct statement;
+ resultset result_;
+ void check_columns_( system::error_code & ec, error_info & ei)
+ {
+ detail::check_columns(static_cast<T*>(nullptr), result_, ec, ei);
+ }
+};
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_STATIC_RESULTSET_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/string.hpp b/subprojects/boost-sqlite/include/boost/sqlite/string.hpp
new file mode 100644
index 0000000..5a81978
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/string.hpp
@@ -0,0 +1,49 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_STRING_HPP
+#define BOOST_SQLITE_STRING_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+inline
+bool like(
+ cstring_ref lhs,
+ cstring_ref rhs,
+ char escape = '\0')
+{
+ return sqlite3_strlike(lhs.c_str(), rhs.c_str(), escape) != 0;
+}
+
+inline
+bool glob(
+ cstring_ref lhs,
+ cstring_ref rhs)
+{
+ return sqlite3_strglob(lhs.c_str(), rhs.c_str()) != 0;
+}
+
+inline
+int icmp(
+ cstring_ref lhs,
+ cstring_ref rhs)
+{
+ return sqlite3_stricmp(lhs.c_str(), rhs.c_str());
+}
+
+inline
+int icmp(
+ core::string_view lhs,
+ core::string_view rhs,
+ std::size_t n)
+{
+ return sqlite3_strnicmp(lhs.data(), rhs.data(), static_cast<int>(n));
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_STRING_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/transaction.hpp b/subprojects/boost-sqlite/include/boost/sqlite/transaction.hpp
new file mode 100644
index 0000000..b90bdbf
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/transaction.hpp
@@ -0,0 +1,198 @@
+//
+// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_TRANSACTION_HPP
+#define BOOST_SQLITE_TRANSACTION_HPP
+
+#include <boost/sqlite/connection.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+/**
+ * @brief A simple transaction guard implementing RAAI for transactions
+ * @ingroup reference
+ *
+ * @par Example
+ * @code{.cpp}
+ * sqlite::connection conn;
+ * conn.connect("./my-database.db");
+ *
+ * sqlite::transaction t{conn};
+ * conn.prepare("insert into log (text) values ($1)").execute(std::make_tuple("booting up"));
+ * t.commit();
+ * @endcode
+ */
+struct transaction
+{
+ /// The mode of the transaction
+ enum behaviour {deferred, immediate, exclusive};
+ /// A tag to use, to adopt an already initiated transaction.
+ constexpr static struct adopt_transaction_t {} adopt_transaction{};
+
+
+ /// Create transaction guard on an existing transaction
+ transaction(connection & conn, adopt_transaction_t) : conn_(conn), completed_(false)
+ {
+ }
+
+
+ /// Create transaction guard and initiate a transaction
+ transaction(connection & conn) : conn_(conn)
+ {
+ conn.execute("BEGIN");
+ completed_ = false;
+ }
+
+ /// Create transaction guard and initiate a transaction with the defined behaviour
+ transaction(connection & conn, behaviour b) : conn_(conn)
+ {
+ switch (b)
+ {
+ case deferred: conn.execute("BEGIN DEFERRED"); break;
+ case immediate: conn.execute("BEGIN IMMEDIATE"); break;
+ case exclusive: conn.execute("BEGIN EXCLUSIVE"); break;
+ }
+ completed_ = false;
+ }
+
+ // see https://www.sqlite.org/lang_transaction.html re noexcept
+ /// rollback the transaction if not committed.
+ ~transaction() noexcept(SQLITE_VERSION_NUMBER >= 3007011)
+ {
+ if (!completed_)
+ conn_.execute("ROLLBACK");
+ }
+
+ ///@{
+ /// Commit the transaction.
+ void commit()
+ {
+ conn_.execute("COMMIT");
+ completed_ = true;
+ }
+
+ void commit(system::error_code & ec, error_info & ei)
+ {
+ conn_.execute("COMMIT", ec, ei);
+ completed_ = true;
+ }
+ ///@}
+
+ ///@{
+ /// Rollback the transaction explicitly.
+ void rollback()
+ {
+ conn_.execute("ROLLBACK");
+ completed_ = true;
+ }
+
+ void rollback(system::error_code & ec, error_info & ei)
+ {
+ conn_.execute("ROLLBACK", ec, ei);
+ completed_ = true;
+ }
+ ///@}
+
+ private:
+ connection & conn_;
+ bool completed_ = true;
+};
+
+/**
+ * @brief A simple transaction guard implementing RAAI for savepoints. Savepoints can be used recursively.
+ * @ingroup reference
+ *
+ * @par Example
+ * @code{.cpp}
+ * sqlite::connection conn;
+ * conn.connect("./my-database.db");
+ *
+ * sqlite::savepoint t{conn, "my-savepoint};
+ * conn.prepare("insert into log (text) values ($1)").execute(std::make_tuple("booting up"));
+ * t.commit();
+ * @endcode
+*/
+struct savepoint
+{
+ /// A tag to use, to adopt an already initiated transaction.
+ constexpr static transaction::adopt_transaction_t adopt_transaction{};
+
+ /// Create savepoint guard on an existing savepoint
+ savepoint(connection & conn, std::string name, transaction::adopt_transaction_t)
+ : conn_(conn), name_(std::move(name))
+ {
+ }
+
+ /// Create transaction guard and initiate it
+ savepoint(connection & conn, std::string name) : conn_(conn), name_(std::move(name))
+ {
+ conn.execute("SAVEPOINT " + name_);
+ completed_ = false;
+ }
+
+
+ /// rollback to the savepoint if not committed.
+ ~savepoint() noexcept(SQLITE_VERSION_NUMBER >= 3007011)
+ {
+ if (!completed_)
+ conn_.execute("ROLLBACK TO " + name_);
+ }
+
+ ///@{
+ /// Commit/Release the transaction.
+ void commit()
+ {
+ conn_.execute("RELEASE " + name_);
+ completed_ = true;
+ }
+
+ void commit(system::error_code & ec, error_info & ei)
+ {
+ conn_.execute("RELEASE " + name_, ec, ei);
+ completed_ = true;
+ }
+
+ void release()
+ {
+ conn_.execute("RELEASE " + name_);
+ completed_ = true;
+ }
+
+ void release(system::error_code & ec, error_info & ei)
+ {
+ conn_.execute("RELEASE " + name_, ec, ei);
+ completed_ = true;
+ }
+ ///@}
+
+ ///@{
+ /// Rollback the transaction explicitly.
+ void rollback()
+ {
+ conn_.execute("ROLLBACK TO" + name_);
+ completed_ = true;
+ }
+
+ void rollback(system::error_code & ec, error_info & ei)
+ {
+ conn_.execute("ROLLBACK TO " + name_, ec, ei);
+ completed_ = true;
+ }
+ ///@}
+
+ /// The name of the savepoint.
+ const std::string & name() const {return name_;}
+ private:
+ connection & conn_;
+ std::string name_;
+ bool completed_ = true;
+};
+
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_TRANSACTION_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/value.hpp b/subprojects/boost-sqlite/include/boost/sqlite/value.hpp
new file mode 100644
index 0000000..16dc1c1
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/value.hpp
@@ -0,0 +1,130 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_SQLITE_VALUE_HPP
+#define BOOST_SQLITE_VALUE_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/blob.hpp>
+#include <boost/sqlite/cstring_ref.hpp>
+
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+/** @brief The type of a value
+ @ingroup reference
+
+ [related sqlite documentation](https://www.sqlite.org/datatype3.html)
+*/
+enum class value_type
+{
+ /// An integral value
+ integer = SQLITE_INTEGER,
+ /// A floating piont value
+ floating = SQLITE_FLOAT,
+ /// A textual value
+ text = SQLITE_TEXT,
+ /// A binary value
+ blob = SQLITE_BLOB,
+ /// No value
+ null = SQLITE_NULL,
+};
+
+inline
+const char * value_type_name(value_type vt)
+{
+ switch (vt)
+ {
+ case value_type::text: return "text";
+ case value_type::blob: return "blob";
+ case value_type::floating: return "floating";
+ case value_type::integer: return "integer";
+ case value_type::null: return "null";
+ default: return "unknown";
+ }
+}
+
+
+/** @brief A holder for a sqlite values used for internal APIs
+ @ingroup reference
+
+ */
+struct value
+{
+ // The value for integers in the database
+ typedef sqlite3_int64 int64 ;
+
+ /// The type of the value
+ value_type type() const
+ {
+ return static_cast<value_type>(sqlite3_value_type(value_));
+ }
+ /// The subtype of the value, see
+ int subtype() const
+ {
+ return sqlite3_value_subtype(value_);
+ }
+ /// Is the held value null
+ bool is_null() const
+ {
+ return type() == value_type::null;
+ }
+ /// Is the held value is not null
+ explicit operator bool () const
+ {
+ return type() != value_type::null;
+ }
+ /// Returns the value as an `integer`.
+ int64 get_int() const
+ {
+ return sqlite3_value_int64(value_);
+ }
+ /// Returns the value as an `double`.
+ double get_double() const
+ {
+ return sqlite3_value_double(value_);
+ }
+ /// Returns the value as text, i.e. a string_view. Note that this value may be invalidated`.
+ BOOST_SQLITE_DECL
+ cstring_ref get_text() const;
+ /// Returns the value as blob, i.e. raw memory. Note that this value may be invalidated`.
+ BOOST_SQLITE_DECL
+ blob_view get_blob() const;
+
+ /// Best numeric datatype of the value
+ value_type numeric_type() const{return static_cast<value_type>(sqlite3_value_numeric_type(value_));}
+
+#if SQLITE_VERSION_NUMBER >= 3032000
+ /// True if the column is unchanged in an UPDATE against a virtual table.
+ bool nochange() const {return 0 != sqlite3_value_nochange(value_);}
+#endif
+
+#if SQLITE_VERSION_NUMBER >= 3031000
+ /// True if value originated from a bound parameter
+ bool from_bind() const {return 0 != sqlite3_value_frombind(value_);}
+#endif
+ /// Construct value from a handle.
+ explicit value(sqlite3_value * value_) noexcept : value_(value_) {}
+
+ /// The handle of the value.
+ using handle_type = sqlite3_value *;
+ /// Returns the handle.
+ handle_type handle() const {return value_;}
+ handle_type & handle() {return value_;}
+
+#if SQLITE_VERSION_NUMBER >= 3020000
+ /// Get a value that was passed through the pointer interface.
+ /// A value can be set as a pointer by binding/returning a unique_ptr.
+ template<typename T>
+ T * get_pointer() {return static_cast<T*>(sqlite3_value_pointer(value_, typeid(T).name()));}
+#endif
+ private:
+ sqlite3_value * value_ = nullptr;
+};
+
+static_assert(sizeof(value) == sizeof(sqlite3_value*), "value must be same as sqlite3_value* pointer");
+
+BOOST_SQLITE_END_NAMESPACE
+
+#endif //BOOST_SQLITE_VALUE_HPP
diff --git a/subprojects/boost-sqlite/include/boost/sqlite/vtable.hpp b/subprojects/boost-sqlite/include/boost/sqlite/vtable.hpp
new file mode 100644
index 0000000..a984f4d
--- /dev/null
+++ b/subprojects/boost-sqlite/include/boost/sqlite/vtable.hpp
@@ -0,0 +1,615 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_VTABLE_HPP
+#define BOOST_SQLITE_VTABLE_HPP
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/detail/catch.hpp>
+#include <boost/sqlite/function.hpp>
+
+#include <boost/core/span.hpp>
+#include <boost/core/demangle.hpp>
+
+#include <bitset>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+namespace detail
+{
+struct vtab_impl;
+}
+
+
+namespace vtab
+{
+
+/// Helper type to set a function through the xFindFunction callback
+struct function_setter
+{
+ /** Set the function
+ *
+ * @tparam Func The function type (either a lambda by ref or a pointer by copy)
+ * @param func The function to be used
+ *
+ * The function can either take a single argument, a `span<sqlite::value, N>`
+ * for scalar functions,
+ * or a `context<Args...>` as first, and the span as second for aggegrate functions.
+ *
+ */
+ template<typename Func>
+ void set(Func & func)
+ {
+ set_impl(func, static_cast<callable_traits::args_t<Func>*>(nullptr));
+ }
+
+ template<typename ... Args, std::size_t Extent>
+ void set(void(* ptr)(context<Args...>, span<value, Extent>)) noexcept
+ {
+ *ppArg_ = reinterpret_cast<void*>(ptr);
+ *pxFunc_ =
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args)
+ {
+ auto cc = context<Args...>(ctx);
+ auto aa = reinterpret_cast<value*>(args);
+ auto &f = *reinterpret_cast<void(*)(context<Args...>, span<value, Extent>)>(sqlite3_user_data(ctx));
+ detail::execute_context_function(ctx, f, cc, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)});
+ };
+ }
+
+ template<typename T, typename ... Args, std::size_t Extent>
+ void set(T(* ptr)(context<Args...>, span<value, Extent>))
+ {
+ *ppArg_ = reinterpret_cast<void*>(ptr);
+ *pxFunc_ =
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept
+ {
+ auto cc = context<Args...>(ctx);
+ auto aa = reinterpret_cast<value*>(args);
+ auto &f = *reinterpret_cast<T(*)(context<Args...>, span<value, Extent>)>(sqlite3_user_data(ctx));
+ detail::execute_context_function(ctx, f, cc, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)});
+ };
+ }
+
+ template<std::size_t Extent>
+ void set(void(* ptr)(span<value, Extent>))
+ {
+ *ppArg_ = reinterpret_cast<void*>(ptr);
+ *pxFunc_ =
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept
+ {
+ auto aa = reinterpret_cast<value*>(args);
+ auto &f = *reinterpret_cast<void(*)(span<value, Extent>)>(sqlite3_user_data(ctx));
+ detail::execute_context_function(ctx, f, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)});
+ };
+ }
+
+ template<typename T, std::size_t Extent>
+ void set(T(* ptr)(span<value, Extent>))
+ {
+ *ppArg_ = reinterpret_cast<void*>(ptr);
+ *pxFunc_ =
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept
+ {
+ auto aa = reinterpret_cast<value*>(args);
+ auto &f = *reinterpret_cast<T(*)(span<value, Extent>)>(sqlite3_user_data(ctx));
+ detail::execute_context_function(ctx, f, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)});
+ };
+ }
+
+ explicit function_setter(void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
+ void **ppArg) : pxFunc_(pxFunc), ppArg_(ppArg) {}
+ private:
+
+ template<typename Func, typename ... Args, std::size_t Extent>
+ void set_impl(Func & func, std::tuple<context<Args...>, span<value, Extent>>)
+ {
+ *ppArg_ = &func;
+ *pxFunc_ =
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept
+ {
+ auto cc = context<Args...>(ctx);
+ auto aa = reinterpret_cast<value*>(args);
+ auto &f = *reinterpret_cast<Func*>(sqlite3_user_data(ctx));
+ detail::execute_context_function(ctx, f, cc, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)});
+ };
+ }
+ template<typename Func, std::size_t Extent>
+ void set_impl(Func & func, std::tuple<span<value, Extent>> * )
+ {
+ *ppArg_ = &func;
+ *pxFunc_ =
+ +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept
+ {
+ auto aa = reinterpret_cast<value*>(args);
+ auto &f = *reinterpret_cast<Func *>(sqlite3_user_data(ctx));
+ detail::execute_context_function(ctx, f, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)});
+ };
+ }
+
+ void (**pxFunc_)(sqlite3_context*,int,sqlite3_value**);
+ void **ppArg_;
+};
+
+#if SQLITE_VERSION_NUMBER >= 3038000
+/** @brief Utility function that can be used in `xFilter` for the `in` operator.
+ @see https://www.sqlite.org/capi3ref.html#sqlite3_vtab_in_first
+ @ingroup reference
+ @Note requires sqlite version >= 3.38
+*/
+struct in
+{
+ struct iterator
+ {
+ iterator() = default;
+ explicit iterator(sqlite3_value * value ) : value_(value)
+ {
+ if (value == nullptr)
+ return ;
+ auto res = sqlite3_vtab_in_first(value, &out_.handle());
+ if (res != SQLITE_OK)
+ {
+ system::error_code ec;
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+ detail::throw_error_code(ec, ec.location());
+ }
+ }
+
+ iterator & operator++()
+ {
+ auto res = sqlite3_vtab_in_next(value_, &out_.handle());
+ if (res != SQLITE_OK)
+ {
+ system::error_code ec;
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+ detail::throw_error_code(ec, ec.location());
+ }
+ return *this;
+ }
+
+ iterator operator++(int)
+ {
+ auto last = *this;
+ ++(*this);
+ return last;
+ }
+
+
+ const value & operator*() const
+ {
+ return out_;
+ }
+
+ const value * operator->() const
+ {
+ return &out_;
+ }
+
+ bool operator==(const iterator& other) const
+ {
+ return value_ == other.value_
+ && out_.handle() == other.out_.handle();
+ }
+
+ bool operator!=(const iterator& other) const
+ {
+ return value_ != other.value_
+ || out_.handle() != other.out_.handle();
+ }
+
+
+ private:
+ sqlite3_value * value_{nullptr};
+ value out_{nullptr};
+ };
+
+ /// Returns a forward iterator to the `in` sequence for an `in` constraint pointing to the begin.
+ iterator begin() {return iterator(out_);}
+ /// Returns a forward iterator to the `in` sequence for an `in` constraint pointing to the end.
+ iterator end() {return iterator();}
+
+ explicit in(sqlite3_value * out) : out_(out) {}
+ explicit in(sqlite::value out) : out_(out.handle()) {}
+ private:
+ sqlite3_value * out_{nullptr};
+};
+
+#endif
+
+/// index info used by the find_index function
+/// @ingroup reference
+/// @see [sqlite reference](https://www.sqlite.org/vtab.html#xbestindex)
+struct index_info
+{
+ /// Returns constraints of the index.
+ BOOST_ATTRIBUTE_NODISCARD
+ span<const sqlite3_index_info::sqlite3_index_constraint> constraints() const
+ {
+ return {info_->aConstraint, static_cast<std::size_t>(info_->nConstraint)};
+ }
+
+ /// Returns ordering of the index.
+ BOOST_ATTRIBUTE_NODISCARD
+ span<const sqlite3_index_info::sqlite3_index_orderby> order_by() const
+ {
+ return {info_->aOrderBy, static_cast<std::size_t>(info_->nOrderBy)};
+ }
+
+ BOOST_ATTRIBUTE_NODISCARD
+ span<sqlite3_index_info::sqlite3_index_constraint_usage> usage()
+ {
+ return {info_->aConstraintUsage, static_cast<std::size_t>(info_->nConstraint)};
+ }
+
+ BOOST_ATTRIBUTE_NODISCARD
+ sqlite3_index_info::sqlite3_index_constraint_usage & usage_of(
+ const sqlite3_index_info::sqlite3_index_constraint & info)
+ {
+ auto dist = std::distance(constraints().begin(), &info);
+ auto itr = usage().begin() + dist;
+ BOOST_ASSERT(itr < usage().end());
+ return *itr;
+ }
+
+#if SQLITE_VERSION_NUMBER >= 3022000
+
+ /// Receive the collation for the contrainst of the position.
+ const char * collation(std::size_t idx) const
+ {
+ return sqlite3_vtab_collation(info_, static_cast<int>(idx));
+ }
+#endif
+
+ int on_conflict() const {return sqlite3_vtab_on_conflict(db_);}
+
+#if SQLITE_VERSION_NUMBER >= 3038000
+ /// Returns true if the constraint is
+ bool distinct() const {return sqlite3_vtab_distinct(info_);}
+
+ value * rhs_value(std::size_t idx) const
+ {
+ value * v = nullptr;
+ if (sqlite3_vtab_rhs_value(
+ info_, static_cast<int>(idx),
+ reinterpret_cast<sqlite3_value**>(v)) == SQLITE_OK)
+ return v;
+ else
+ return nullptr;
+ }
+#endif
+
+ void set_already_ordered() { info_->orderByConsumed = 1; }
+ void set_estimated_cost(double cost) { info_->estimatedCost = cost; }
+#if SQLITE_VERSION_NUMBER >= 3008200
+ void set_estimated_rows(sqlite3_int64 rows) { info_->estimatedRows = rows; }
+#endif
+#if SQLITE_VERSION_NUMBER >= 3009000
+ void set_index_scan_flags(int flags) { info_->idxFlags = flags; }
+#endif
+#if SQLITE_VERSION_NUMBER >= 3010000
+ std::bitset<64u> columns_used()
+ {
+ return std::bitset<64u>(info_->colUsed);
+ }
+#endif
+
+ void set_index(int value) { info_->idxNum = value; }
+ void set_index_string(char * str,
+ bool take_ownership = true)
+ {
+ info_->idxStr = str;
+ info_->needToFreeIdxStr = take_ownership ? 1 : 0;
+ }
+
+ sqlite3_index_info * info() const { return info_; }
+ sqlite3 * db() const { return db_; }
+
+ private:
+ explicit index_info(sqlite3 * db, sqlite3_index_info * info) : db_(db), info_(info) {}
+ sqlite3 * db_;
+ sqlite3_index_info * info_{nullptr};
+ friend struct detail::vtab_impl;
+};
+
+
+struct module_config
+{
+#if SQLITE_VERSION_NUMBER >= 3031000
+ /// @brief Can be used to set SQLITE_VTAB_INNOCUOUS
+ void set_innocuous()
+ {
+ sqlite3_vtab_config(db_, SQLITE_VTAB_INNOCUOUS);
+ }
+ /// @brief Can be used to set SQLITE_VTAB_DIRECTONLY
+ void set_directonly() {sqlite3_vtab_config(db_, SQLITE_VTAB_DIRECTONLY);}
+
+#endif
+
+ /// @brief Can be used to set SQLITE_VTAB_CONSTRAINT_SUPPORT
+ void set_constraint_support(bool enabled = false)
+ {
+ sqlite3_vtab_config(db_, SQLITE_VTAB_CONSTRAINT_SUPPORT, enabled ? 1 : 0);
+ }
+
+ private:
+ explicit module_config(sqlite3 *db) : db_(db) {}
+ friend struct detail::vtab_impl;
+ sqlite3 *db_;
+};
+
+template<typename Table>
+struct module
+{
+ using table_type = Table;
+
+ /// @brief Creates the instance
+ /// The instance_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab.
+ /// instance_type must have a member `declaration` that returns a `const char *` for the declaration.
+ BOOST_SQLITE_VIRTUAL result<table_type> create(sqlite::connection db, int argc, const char * const argv[]) BOOST_SQLITE_PURE;
+
+ /// @brief Create a table
+ /// The table_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab.
+ /// table_type must have a member `declaration` that returns a `const char *` for the declaration.
+ BOOST_SQLITE_VIRTUAL result<table_type> connect(sqlite::connection db, int argc, const char * const argv[]) BOOST_SQLITE_PURE;
+};
+
+template<typename Table>
+struct eponymous_module
+{
+ using table_type = Table;
+
+ /// @brief Creates the instance
+ /// The instance_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab.
+ /// instance_type must have a member `declaration` that returns a `const char *` for the declaration.
+ BOOST_SQLITE_VIRTUAL result<table_type> connect(sqlite::connection db, int argc, const char * const argv[]) BOOST_SQLITE_PURE;
+
+ eponymous_module(bool eponymous_only = false) : eponymous_only_(eponymous_only) {}
+
+ bool eponymous_only() const {return eponymous_only_;}
+ protected:
+ bool eponymous_only_{false};
+
+};
+
+template<typename ColumnType>
+struct cursor;
+
+/// The basis for vtable
+template<typename Cursor>
+struct table : protected sqlite3_vtab
+{
+ using cursor_type = Cursor;
+
+ BOOST_SQLITE_VIRTUAL result<void> config(module_config &) {return {};}
+
+ /// The Table declaration to be used with sqlite3_declare_vtab
+ BOOST_SQLITE_VIRTUAL const char *declaration() BOOST_SQLITE_PURE;
+
+ /// Destroy the storage = this function needs to be present for non eponymous tables
+ BOOST_SQLITE_VIRTUAL result<void> destroy() { return {}; }
+
+ /// Tell sqlite how to communicate with the table.
+ /// Optional, this library will fill in a default function that leaves comparisons to sqlite.
+ BOOST_SQLITE_VIRTUAL result<void> best_index(index_info & /*info*/) {return {system::in_place_error, SQLITE_OK};}
+
+ /// @brief Start a search on the table.
+ /// The cursor_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab_cursor.
+ BOOST_SQLITE_VIRTUAL result<cursor_type> open() BOOST_SQLITE_PURE;
+
+ /// Get the connection of the vtable
+ sqlite::connection connection() const {return sqlite::connection{db_, false};}
+
+ table(const sqlite::connection & conn) : db_(conn.handle()) {}
+ table(sqlite3 * db = nullptr) : db_(db) {}
+ private:
+ template<typename ColumnType>
+ friend struct cursor;
+ friend struct detail::vtab_impl;
+
+ sqlite3 * db_{nullptr};
+};
+
+/// Cursor needs the following member. @ingroup reference
+template<typename ColumnType = void>
+struct cursor : protected sqlite3_vtab_cursor
+{
+ using column_type = ColumnType;
+
+ /// @brief Apply a filter to the cursor. Required when best_index is implemented.
+ BOOST_SQLITE_VIRTUAL result<void> filter(
+ int /*index*/, const char * /*index_data*/,
+ boost::span<sqlite::value> /*values*/)
+ {
+ return {system::in_place_error, SQLITE_OK};
+ }
+
+ /// @brief Returns the next row.
+ BOOST_SQLITE_VIRTUAL result<void> next() BOOST_SQLITE_PURE;
+
+ /// @brief Check if the cursor is and the end
+ BOOST_SQLITE_VIRTUAL bool eof() BOOST_SQLITE_PURE;
+
+ /// @brief Returns the result of a value. It will use the set_result functionality to create a an sqlite function.
+ /// see [vtab_no_change](https://www.sqlite.org/c3ref/vtab_nochange.html)
+ BOOST_SQLITE_VIRTUAL result<column_type> column(int idx, bool no_change) BOOST_SQLITE_PURE;
+ /// @brief Returns the id of the current row
+ BOOST_SQLITE_VIRTUAL result<sqlite3_int64> row_id() BOOST_SQLITE_PURE;
+
+ /// Get the table the cursor is pointing to.
+ vtab::table<cursor> & table() { return *static_cast< vtab::table<cursor>*>(this->pVtab);}
+ const vtab::table<cursor> & table() const { return *static_cast<const vtab::table<cursor>*>(this->pVtab);}
+
+ friend struct detail::vtab_impl;
+};
+
+/// Cursor needs the following member. @ingroup reference
+template<>
+struct cursor<void> : protected sqlite3_vtab_cursor
+{
+ using column_type = void;
+
+ /// @brief Apply a filter to the cursor. Required when best_index is implemented.
+ BOOST_SQLITE_VIRTUAL result<void> filter(
+ int /*index*/, const char * /*index_data*/,
+ boost::span<sqlite::value> /*values*/)
+ {
+ return {system::in_place_error, SQLITE_OK};
+ }
+
+ /// @brief Returns the next row.
+ BOOST_SQLITE_VIRTUAL result<void> next() BOOST_SQLITE_PURE;
+
+ /// @brief Check if the cursor is and the end
+ BOOST_SQLITE_VIRTUAL bool eof() BOOST_SQLITE_PURE;
+
+ /// @brief Returns the result of a value. It will use the set_result functionality to create a an sqlite function.
+ /// see [vtab_no_change](https://www.sqlite.org/c3ref/vtab_nochange.html)
+ BOOST_SQLITE_VIRTUAL void column(context<> ctx, int idx, bool no_change) BOOST_SQLITE_PURE;
+ /// @brief Returns the id of the current row
+ BOOST_SQLITE_VIRTUAL result<sqlite3_int64> row_id() BOOST_SQLITE_PURE;
+
+ /// Get the table the cursor is pointing to.
+ vtab::table<cursor> & table() { return *static_cast< vtab::table<cursor>*>(this->pVtab);}
+ const vtab::table<cursor> & table() const { return *static_cast<const vtab::table<cursor>*>(this->pVtab);}
+
+ friend struct detail::vtab_impl;
+};
+
+
+/// Group of functions for modifications. @ingroup reference
+struct modifiable
+{
+ BOOST_SQLITE_VIRTUAL result<void> delete_(sqlite::value key) BOOST_SQLITE_PURE;
+ /// Insert a new row
+ BOOST_SQLITE_VIRTUAL result<sqlite_int64> insert(sqlite::value key, span<sqlite::value> values, int on_conflict) BOOST_SQLITE_PURE;
+ /// Update the row
+ BOOST_SQLITE_VIRTUAL result<sqlite_int64> update(sqlite::value old_key, sqlite::value new_key, span<sqlite::value> values, int on_conflict) BOOST_SQLITE_PURE;
+};
+
+/// Group of functions to support transactions. @ingroup reference
+struct transaction
+{
+ /// Begin a tranasction
+ BOOST_SQLITE_VIRTUAL result<void> begin() BOOST_SQLITE_PURE;
+ /// synchronize the state
+ BOOST_SQLITE_VIRTUAL result<void> sync() BOOST_SQLITE_PURE;
+ /// commit the transaction
+ BOOST_SQLITE_VIRTUAL result<void> commit() BOOST_SQLITE_PURE;
+ /// rollback the transaction
+ BOOST_SQLITE_VIRTUAL result<void> rollback() BOOST_SQLITE_PURE;
+};
+
+/// Fucntion to enable function overriding See `xFindFunction`.
+struct overload_functions
+{
+ /// @see https://www.sqlite.org/vtab.html#xfindfunction
+ BOOST_SQLITE_VIRTUAL result<void> find_function(
+ function_setter fs,
+ int arg, const char * name) BOOST_SQLITE_PURE;
+};
+
+/// Make the vtable renamable @ingroup reference
+struct renamable
+{
+ /// Function to rename the table. Optional
+ BOOST_SQLITE_VIRTUAL result<void> rename(const char * new_name) BOOST_SQLITE_PURE;
+
+};
+
+#if SQLITE_VERSION_NUMBER >= 3007007
+/// Support for recursive transactions @ingroup reference
+struct recursive_transaction
+{
+ /// Save the current state with to `i`
+ BOOST_SQLITE_VIRTUAL result<void> savepoint(int i) BOOST_SQLITE_PURE;
+ /// Release all saves states down to `i`
+ BOOST_SQLITE_VIRTUAL result<void> release(int i) BOOST_SQLITE_PURE;
+ /// Roll the transaction back to `i`.
+ BOOST_SQLITE_VIRTUAL result<void> rollback_to(int i) BOOST_SQLITE_PURE;
+};
+#endif
+
+}
+
+
+namespace detail
+{
+
+template<typename Module>
+const sqlite3_module make_module(const Module & mod);
+
+}
+
+////@{
+/** @brief Register a vtable
+ @ingroup reference
+ @see Sqlite vtab reference [vtab](https://www.sqlite.org/vtab.html)
+
+ @param conn The connection to install the vtable into
+ @param name The name for the vtable
+ @param module The module to install as a vtable. See @ref vtab_module_prototype for the structure required
+ @param ec The system::error_code used to deliver errors for the exception less overload.
+ @param info The error_info used to deliver errors for the exception less overload.
+
+ @param The requirements for `module`.
+
+ @tparam T The implementation type of the module. It must inherit
+
+ @returns A reference to the module as stored in the database. It's lifetime is managed by the database.
+
+
+*/
+template<typename T>
+auto create_module(connection & conn,
+ cstring_ref name,
+ T && module,
+ system::error_code & ec,
+ error_info & ei) -> typename std::decay<T>::type &
+{
+ using module_type = typename std::decay<T>::type;
+ static const sqlite3_module mod = detail::make_module(module);
+
+ std::unique_ptr<module_type> p{new (memory_tag{}) module_type(std::forward<T>(module))};
+ auto pp = p.get();
+
+ int res = sqlite3_create_module_v2(
+ conn.handle(), name.c_str(),
+ &mod, p.release(),
+ +[](void * ptr)
+ {
+ static_cast<module_type*>(ptr)->~module_type();
+ sqlite3_free(ptr);
+ });
+
+ if (res != SQLITE_OK)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+ ei.set_message(sqlite3_errmsg(conn.handle()));
+ }
+ return *pp;
+}
+
+template<typename T>
+auto create_module(connection & conn,
+ const char * name,
+ T && module) -> typename std::decay<T>::type &
+{
+ system::error_code ec;
+ error_info ei;
+ T & ref = create_module(conn, name, std::forward<T>(module), ec, ei);
+ if (ec)
+ detail::throw_error_code(ec, ei);
+ return ref;
+}
+///@}
+
+
+BOOST_SQLITE_END_NAMESPACE
+
+#include <boost/sqlite/detail/vtable.hpp>
+
+#endif //BOOST_SQLITE_VTABLE_HPP
+
diff --git a/subprojects/boost-sqlite/readme.md b/subprojects/boost-sqlite/readme.md
new file mode 100644
index 0000000..e1a62e1
--- /dev/null
+++ b/subprojects/boost-sqlite/readme.md
@@ -0,0 +1,277 @@
+# boost_sqlite
+
+This library provides a simple C++ sqlite wrapper.
+
+It includes:
+
+ - typed queries
+ - prepared statements
+ - json support
+ - custom functions (scalar, aggregrate, windows)
+ - event hooks
+ - virtual tables
+
+sqlite provides an excellent C-API, so this library does not attempt to hide, but to augment it.
+
+## Building the library
+
+You can either build the library and link against `boost_sqlite` for embedding it,
+or `boost_sqlite_ext` for extensions.
+
+If you want to use it for extensions you'll need to
+define `BOOST_SQLITE_COMPILE_EXTENSION` or include `boost/sqlite/extensions.hpp` first.
+
+## Quickstart
+
+First we open a database. Note that this can be `":memory:"` for an in-memory database.
+
+```cpp
+boost::sqlite::connection conn{"./my-database.db"};
+```
+
+Next we're creating tables using boost::sqlite::connection::execute,
+because it can execute multiple statements in one command:
+
+```cpp
+ conn.execute(R"(
+create table if not exists author (
+ id integer primary key autoincrement,
+ first_name text,
+ last_name text
+);
+
+create table if not exists library(
+ id integer primary key autoincrement,
+ name text unique,
+ author integer references author(id)
+);
+)"
+);
+```
+
+Next, we'll use a prepared statement to insert multiple values by index:
+
+```cpp
+conn.prepare("insert into author (first_name, last_name) values (?1, ?2), (?3, ?4), (?5, ?6), (?7, ?8)")
+ .execute({"vinnie", "falco", "richard", "hodges", "ruben", "perez", "peter", "dimov"});
+```
+
+Prepared statements can also be used multiple time and used with named parameters instead of indexed.
+
+```cpp
+{
+ conn.query("begin transaction;");
+
+ auto st = conn.prepare("insert into library (\"name\", author) values ($library, "
+ " (select id from author where first_name = $fname and last_name = $lname))");
+
+ st.execute({{"library", "beast"}, {"fname", "vinnie"}, {"lname", "falco"}});
+ st.execute({{"library", "mysql"}, {"fname", "ruben"}, {"lname", "perez"}});
+ st.execute({{"library", "mp11"}, {"fname", "peter"}, {"lname", "dimov"}});
+ st.execute({{"library", "variant2"}, {"fname", "peter"}, {"lname", "dimov"}});
+
+ conn.query("commit;");
+}
+```
+
+Now that we have the values in the table, let's add a custom aggregate function to create a comma separated list:
+
+```cpp
+struct collect_libs
+{
+ void step(std::string & name, span<sqlite::value, 1> args)
+ {
+ if (name.empty())
+ name = args[0].get_text();
+ else
+ (name += ", ") += args[0].get_text();
+ }
+ std::string final(std::string & name) { return name; }
+};
+sqlite::create_aggregate_function(conn, "collect_libs", collect_libs{});
+```
+
+Print out the query with aggregates libraries:
+
+```cpp
+for (boost::sqlite::row r : conn.query(
+ "select first_name, collect_libs(name) "
+ " from author inner join library l on author.id = l.author group by last_name"))
+ std::cout << r.at(0u).get_text() << " authored " << r.at(1u).get_text() << std::endl;
+```
+
+Alternatively a query result can also be read manually instead of using a loop:
+
+```cpp
+boost::sqlite::row r;
+boost::sqlite::query q = conn.query(
+ "select first_name, collect_libs(name) "
+ " from author inner join library l on author.id = l.author group by last_name")
+do
+{
+ auto r = q.current();''
+ std::cout << r.at(0u).get_text() << " authored " << r.at(1u).get_text() << std::endl;
+}
+while (q.read_next());
+
+```
+
+## Fields, values & parameters
+
+sqlite3 has a weak typesystem, where everything is one of
+following [value_types](@ref boost::sqlite::value_type):
+
+ - `integer`
+ - `floating`
+ - `text`
+ - `blob`
+ - `null`
+
+The result of a query is a [field](@ref boost::sqlite::field) type,
+while a [value](@ref boost::sqlite::value) is used in functions.
+
+Fields & values can have [subtypes](https://www.sqlite.org/c3ref/value_subtype.html),
+while parameter to prepared statements do not have thos associated.
+
+Because of this the values that can be bound to an [execute](@ref boost::sqlite::statement::execute)
+need to be convertible to a fixed set of types (see [param_ref](@ref boost::sqlite::param_ref) for details).
+
+When a [value](@ref boost::sqlite::value) is returned from a custom function,
+such as done through [create_scalar_function](@ref boost::sqlite::create_scalar_function), additional types
+can be added with the following tag_invoke function:
+
+```cpp
+void tag_invoke(const struct set_result_tag &, sqlite3_context * ctx, const my_type & value);
+```
+
+An implementation can look like this:
+
+```cpp
+void tag_invoke(const struct set_result_tag &, sqlite3_context * ctx, const my_type & value)
+{
+ auto data = value.to_string();
+ sqlite3_result_text(ctx, data.c_str(), data.size(), sqlite3_free);
+ sqlite3_result_subtype(ctx, my_subtype);
+}
+
+```
+
+## Typed queries
+
+Queries can be typed through tuples, describe or, if you're on C++20, by plain structs.
+The type to hold them is `static_resultset<T>` which will check if the columns match the result types before usage.
+Tuples are matched by position, structs by name.
+
+```cpp
+for (auto q : conn.query<std::tuple<std::string, std::string>>(
+ "select first_name, collect_libs(name) "
+ " from author inner join library l on author.id = l.author group by last_name"))
+ std::cout << std::get<0>(q) << " authored " << std::get<0>(q) << std::endl;
+```
+
+
+```cpp
+struct query_result { std::string first_name, lib_name;};
+BOOST_DESCRIBE_STRUCT(query_result, (), (first_name, lib_name)); // this can be omitted with C++20.
+
+for (auto q : conn.query<query_result>(
+ "select first_name, collect_libs(name) as lib_name"
+ " from author inner join library l on author.id = l.author group by last_name"))
+ std::cout << q.first_name << " authored " << q.lib_name << std::endl;
+```
+
+The following types are allowed in a static query result:
+
+ - `sqlite::value`
+ - `int`
+ - `sqlite_int64`
+ - `double`
+ - `std::string`
+ - `sqlite::string_view`
+ - `sqlite::blob`
+ - `sqlite::blob_view`
+
+
+You'll need to include `boost/sqlite/static_resultset.hpp` for this to work.
+
+## Custom functions
+
+Since sqlite is running in the same process you can add custom functions that can be used from within sqlite.
+
+ - [collation](@ref boost::sqlite::create_collation)
+ - [scalar function](@ref boost::sqlite::create_scalar_function)
+ - [aggregate function](@ref boost::sqlite::create_aggregate_function)
+ - [window function](@ref boost::sqlite::create_window_function)
+
+## Vtables
+
+This library also simplifies adding virtual tables significantly;
+virtual tables are table that are backed by code instead of data.
+
+See [create_module](@ref boost::sqlite::create_module) and [prototype](@ref boost::sqlite::vtab_module_prototype) for more details.
+
+## Modules
+
+This library can also be used to build a sql plugin:
+
+```cpp
+BOOST_SQLITE_EXTENSION(testlibrary, conn)
+{
+ // create a function that can be used in the plugin
+ create_scalar_function(
+ conn, "assert",
+ [](boost::sqlite::context<>, boost::span<boost::sqlite::value, 1u> sp)
+ {
+ if (sp.front().get_int() == 0)
+ throw std::logic_error("assertion failed");
+ });
+}
+```
+
+The plugin can then be loaded & used like this:
+
+```sql
+SELECT load_extension('./test_library');
+
+select assert((3 * 4) = 12);
+```
+
+To build a plugin you need to define `BOOST_SQLITE_COMPILE_EXTENSION`
+(e.g. by including `boost/sqlite/extension.hpp` or linking against `boost_sqlite_ext`).
+
+This will include the matching sqlite header (`sqlite3ext.h`) and
+will move all the symbols into an inline namespace `ext` inside `boost::sqlite`.
+
+<a name="api-reference"></a>
+## Reference
+
+* [Reference](#reference): Covers the topics discussed in this document.
+
+## Library Comparisons
+
+While there are many sqlite wrappers out there, most haven't been updated in the last five years - while sqlite has.
+
+Here are some actively maintained ones:
+
+ - [SQLiteCpp](https://github.com/SRombauts/SQLiteCpp)
+
+SQLiteCpp is the closest to this library, a C++11 wrapper only depending on sqlite & the STL.
+It's great and served as an inspiration for this library.
+boost.sqlite does provide more functionality when it comes to hooks, custom functions & virtual tables.
+Furthermore, boost.sqlite has a non-throwing interface and supports variants & json, as those are available through boost.
+
+ - [sqlite_modern_cpp](https://github.com/SqliteModernCpp/sqlite_modern_cpp)
+
+This library takes a different approach, by making everything an `iostream` interface.
+`iostream` interfaces have somewhat fallen out of favor.
+
+ - [sqlite_orm](https://github.com/fnc12/sqlite_orm)
+
+As the name says, it's an ORM. While there is nothing wrong with ORMs, they are one layer of abstraction
+above a client library like this.
+
+ - [SOCI](https://github.com/SOCI/soci)
+
+SOCI is an abstraction layer for multiple databases in C++, including sqlite.
+It's interfaces encourages dynamic building of query string, which should not be considered safe.
+
diff --git a/subprojects/boost-sqlite/src/backup.cpp b/subprojects/boost-sqlite/src/backup.cpp
new file mode 100644
index 0000000..76099b1
--- /dev/null
+++ b/subprojects/boost-sqlite/src/backup.cpp
@@ -0,0 +1,61 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/backup.hpp>
+#include <boost/sqlite/connection.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+
+void
+backup(connection & source,
+ connection & target,
+ cstring_ref source_name,
+ cstring_ref target_name,
+ system::error_code & ec,
+ error_info & ei)
+{
+ struct del
+ {
+ void operator()(sqlite3_backup * bp)
+ {
+ sqlite3_backup_finish(bp);
+ }
+ };
+
+ std::unique_ptr<sqlite3_backup, del> bu{
+ sqlite3_backup_init(target.handle(), target_name.c_str(),
+ source.handle(), source_name.c_str())};
+ if (bu == nullptr)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, sqlite3_errcode(target.handle()));
+ ei.set_message(sqlite3_errmsg(target.handle()));
+ return ;
+ }
+
+ const auto res = sqlite3_backup_step(bu.get(), -1);
+ if (SQLITE_DONE != res)
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+}
+
+
+void
+backup(connection & source,
+ connection & target,
+ cstring_ref source_name,
+ cstring_ref target_name)
+{
+ system::error_code ec;
+ error_info ei;
+ backup(source, target, source_name, target_name, ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
diff --git a/subprojects/boost-sqlite/src/blob.cpp b/subprojects/boost-sqlite/src/blob.cpp
new file mode 100644
index 0000000..397d85b
--- /dev/null
+++ b/subprojects/boost-sqlite/src/blob.cpp
@@ -0,0 +1,94 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/blob.hpp>
+#include <boost/sqlite/connection.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+blob_handle open_blob(connection & conn,
+ cstring_ref db,
+ cstring_ref table,
+ cstring_ref column,
+ sqlite3_int64 row,
+ bool read_only,
+ system::error_code & ec,
+ error_info & ei)
+{
+ sqlite3_blob * bb = nullptr;
+ blob_handle bh;
+
+ int res = sqlite3_blob_open(conn.handle(), db.c_str(), table.c_str(), column.c_str(),
+ row, read_only ? 0 : 1, &bb);
+ if (res != 0)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, sqlite3_errcode(conn.handle()));
+ ei.set_message(sqlite3_errmsg(conn.handle()));
+ }
+ else
+ bh = blob_handle(bb);
+
+ return bh;
+}
+
+blob_handle open_blob(connection & conn,
+ cstring_ref db,
+ cstring_ref table,
+ cstring_ref column,
+ sqlite3_int64 row,
+ bool read_only)
+{
+ boost::system::error_code ec;
+ error_info ei;
+ auto b = open_blob(conn, db, table, column, row, read_only, ec, ei);
+ if (ec)
+ boost::throw_exception(system::system_error(ec, ei.message()), BOOST_CURRENT_LOCATION);
+ return b;
+}
+
+void blob_handle::reopen(sqlite3_int64 row_id, system::error_code & ec)
+{
+ int res = sqlite3_blob_reopen(blob_.get(), row_id);
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+}
+void blob_handle::reopen(sqlite3_int64 row_id)
+{
+ boost::system::error_code ec;
+ reopen(row_id, ec);
+ if (ec)
+ boost::throw_exception(system::system_error(ec), BOOST_CURRENT_LOCATION);
+}
+
+void blob_handle::read_at(void *data, int len, int offset, system::error_code &ec)
+{
+ int res = sqlite3_blob_read(blob_.get(), data, len, offset);
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+}
+void blob_handle::read_at(void *data, int len, int offset)
+{
+ boost::system::error_code ec;
+ read_at(data, len, offset, ec);
+ if (ec)
+ boost::throw_exception(system::system_error(ec), BOOST_CURRENT_LOCATION);
+}
+
+void blob_handle::write_at(const void *data, int len, int offset, system::error_code &ec)
+{
+ int res = sqlite3_blob_write(blob_.get(), data, len, offset);
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+}
+void blob_handle::write_at(const void *data, int len, int offset)
+{
+ boost::system::error_code ec;
+ write_at(data, len, offset, ec);
+ if (ec)
+ boost::throw_exception(system::system_error(ec), BOOST_CURRENT_LOCATION);
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
diff --git a/subprojects/boost-sqlite/src/connection.cpp b/subprojects/boost-sqlite/src/connection.cpp
new file mode 100644
index 0000000..1ac0ae7
--- /dev/null
+++ b/subprojects/boost-sqlite/src/connection.cpp
@@ -0,0 +1,158 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/connection.hpp>
+#include <boost/sqlite/statement.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+
+
+void connection::connect(cstring_ref filename, int flags)
+{
+ system::error_code ec;
+ connect(filename, flags, ec);
+ if (ec)
+ throw_exception(system::system_error(ec, "connect"));
+}
+
+void connection::connect(cstring_ref filename, int flags, system::error_code & ec)
+{
+ sqlite3 * res;
+ auto r = sqlite3_open_v2(filename.c_str(), &res, flags,
+ nullptr);
+ if (r != SQLITE_OK)
+ BOOST_SQLITE_ASSIGN_EC(ec, r);
+ else
+ {
+ impl_.reset(res);
+ impl_.get_deleter().owned_ = true;
+ }
+ sqlite3_extended_result_codes(impl_.get(), true);
+}
+
+void connection::close()
+{
+ system::error_code ec;
+ error_info ei;
+ close(ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+}
+
+void connection::close(system::error_code & ec,
+ error_info & ei)
+{
+ if (impl_)
+ {
+ auto tmp = impl_.release();
+ auto cc = sqlite3_close(tmp);
+ if (SQLITE_OK != cc)
+ {
+ impl_.reset(tmp);
+ BOOST_SQLITE_ASSIGN_EC(ec, cc);
+ ei.set_message(sqlite3_errmsg(impl_.get()));
+ }
+ }
+}
+
+
+resultset connection::query(
+ core::string_view q,
+ system::error_code & ec,
+ error_info & ei)
+{
+ resultset res;
+ sqlite3_stmt * ss;
+ const auto cc = sqlite3_prepare_v2(impl_.get(),
+ q.data(), static_cast<int>(q.size()),
+ &ss, nullptr);
+
+ if (cc != SQLITE_OK)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, cc);
+ ei.set_message(sqlite3_errmsg(impl_.get()));
+ }
+ else
+ {
+ res.impl_.reset(ss);
+ if (!ec)
+ res.read_next(ec, ei);
+ }
+ return res;
+}
+
+resultset connection::query(core::string_view q)
+{
+ system::error_code ec;
+ error_info ei;
+ auto tmp = query(q, ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return tmp;
+}
+
+statement connection::prepare(
+ core::string_view q,
+ system::error_code & ec,
+ error_info & ei)
+{
+ statement res;
+ sqlite3_stmt * ss;
+ const auto cc = sqlite3_prepare_v2(impl_.get(),
+ q.data(), static_cast<int>(q.size()),
+ &ss, nullptr);
+
+ if (cc != SQLITE_OK)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, cc);
+ ei.set_message(sqlite3_errmsg(impl_.get()));
+ }
+ else
+ res.impl_.reset(ss);
+ return res;
+}
+
+statement connection::prepare(core::string_view q)
+{
+ system::error_code ec;
+ error_info ei;
+ auto tmp = prepare(q, ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return tmp;
+}
+
+void connection::execute(
+ cstring_ref q,
+ system::error_code & ec,
+ error_info & ei)
+{
+ char * msg = nullptr;
+
+ auto res = sqlite3_exec(impl_.get(), q.c_str(), nullptr, nullptr, &msg);
+ if (res != SQLITE_OK)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+ if (msg != nullptr)
+ ei.set_message(msg);
+ }
+}
+
+void connection::execute(cstring_ref q)
+{
+ system::error_code ec;
+ error_info ei;
+ execute(q, ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
+
diff --git a/subprojects/boost-sqlite/src/detail/exception.cpp b/subprojects/boost-sqlite/src/detail/exception.cpp
new file mode 100644
index 0000000..e360f38
--- /dev/null
+++ b/subprojects/boost-sqlite/src/detail/exception.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/detail/exception.hpp>
+#include <boost/system/system_error.hpp>
+
+#include <stdexcept>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+namespace detail
+{
+
+void throw_error_code(const boost::system::error_code & ec,
+ const boost::source_location & loc)
+{
+ boost::throw_exception(system::system_error(ec),
+ ec.has_location() ? ec.location() : loc);
+}
+
+void throw_error_code(const boost::system::error_code & ec,
+ const error_info & ei,
+ const boost::source_location & loc)
+{
+ boost::throw_exception(system::system_error(ec, ei.message()),
+ ec.has_location() ? ec.location() : loc);
+}
+
+void throw_out_of_range(const char * msg,
+ const boost::source_location & loc)
+{
+ boost::throw_exception(std::out_of_range(msg), loc);
+}
+
+void throw_invalid_argument(const char * msg,
+ const boost::source_location & loc)
+{
+ {
+ boost::throw_exception(std::invalid_argument(msg), loc);
+ }
+}
+
+
+}
+BOOST_SQLITE_END_NAMESPACE
diff --git a/subprojects/boost-sqlite/src/error.cpp b/subprojects/boost-sqlite/src/error.cpp
new file mode 100644
index 0000000..9344fbe
--- /dev/null
+++ b/subprojects/boost-sqlite/src/error.cpp
@@ -0,0 +1,83 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/detail/config.hpp>
+#include <boost/sqlite/error.hpp>
+#include <boost/core/detail/string_view.hpp>
+#include <algorithm>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+struct sqlite_category_t final : system::error_category
+{
+#if defined(BOOST_SQLITE_COMPILE_EXTENSION)
+ sqlite_category_t() : system::error_category(0x7d4c7b49d8a3edull) {}
+#else
+ sqlite_category_t() : system::error_category(0x7d4c7b49d8a3fdull) {}
+#endif
+
+ bool failed( int ev ) const noexcept final
+ {
+ return ev != SQLITE_OK
+ && ev != SQLITE_NOTICE
+ && ev != SQLITE_WARNING
+ && ev != SQLITE_ROW
+ && ev != SQLITE_DONE;
+ }
+
+ std::string message( int ev ) const final
+ {
+ return sqlite3_errstr(ev);
+ }
+ char const * message( int ev, char * buffer, std::size_t len ) const noexcept final
+ {
+ std::snprintf( buffer, len, "%s", sqlite3_errstr( ev ) );
+ return buffer;
+ }
+
+ const char * name() const BOOST_NOEXCEPT override
+ {
+ return "sqlite3";
+ }
+
+ system::error_condition default_error_condition( int ev ) const noexcept final
+ {
+ namespace errc = boost::system::errc;
+ switch (ev & 0xFF)
+ {
+ case SQLITE_OK: return {};
+ case SQLITE_PERM: return errc::permission_denied;
+ case SQLITE_BUSY: return errc::device_or_resource_busy;
+ case SQLITE_NOMEM: return errc::not_enough_memory;
+ case SQLITE_INTERRUPT: return errc::interrupted;
+ case SQLITE_IOERR: return errc::io_error;
+ }
+
+ return system::error_condition(ev, *this);
+ }
+
+};
+
+
+system::error_category & sqlite_category()
+{
+ static sqlite_category_t cat;
+ return cat;
+}
+
+void throw_exception_from_error( error const & e, boost::source_location const & loc )
+{
+ boost::throw_exception(
+ system::system_error(e.code,
+ sqlite_category(),
+ e.info.message().c_str()), loc);
+}
+
+
+BOOST_SQLITE_END_NAMESPACE
+
diff --git a/subprojects/boost-sqlite/src/ext.cpp b/subprojects/boost-sqlite/src/ext.cpp
new file mode 100644
index 0000000..0152994
--- /dev/null
+++ b/subprojects/boost-sqlite/src/ext.cpp
@@ -0,0 +1,14 @@
+//
+// Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/connection.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+BOOST_SYMBOL_EXPORT const sqlite3_api_routines *sqlite3_api{nullptr};
+
+BOOST_SQLITE_END_NAMESPACE
diff --git a/subprojects/boost-sqlite/src/field.cpp b/subprojects/boost-sqlite/src/field.cpp
new file mode 100644
index 0000000..7ff433f
--- /dev/null
+++ b/subprojects/boost-sqlite/src/field.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/field.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+cstring_ref field::get_text() const
+{
+ const auto ptr = sqlite3_column_text(stm_, col_);
+ if (ptr == nullptr)
+ {
+ if (sqlite3_errcode(sqlite3_db_handle(stm_)) != SQLITE_NOMEM)
+ return "";
+ else
+ throw_exception(std::bad_alloc(), BOOST_CURRENT_LOCATION);
+ }
+ return reinterpret_cast<const char*>(ptr);
+}
+
+
+blob_view field::get_blob() const
+{
+ const auto ptr = sqlite3_column_blob(stm_, col_);
+ if (ptr == nullptr)
+ {
+ if (sqlite3_errcode(sqlite3_db_handle(stm_)) != SQLITE_NOMEM)
+ return {nullptr, 0u};
+ else
+ throw_exception(std::bad_alloc(), BOOST_CURRENT_LOCATION);
+ }
+ const auto sz = sqlite3_column_bytes(stm_, col_);
+ return blob_view(ptr, sz);
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
diff --git a/subprojects/boost-sqlite/src/meta_data.cpp b/subprojects/boost-sqlite/src/meta_data.cpp
new file mode 100644
index 0000000..c16bf27
--- /dev/null
+++ b/subprojects/boost-sqlite/src/meta_data.cpp
@@ -0,0 +1,83 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/meta_data.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+
+
+
+auto table_column_meta_data(connection& conn,
+ cstring_ref db_name, cstring_ref table_name, cstring_ref column_name,
+ system::error_code & ec, error_info &ei) -> column_meta_data
+{
+ const char * data_type= "", *collation = "";
+ int nn, pk, ai;
+
+ int res = sqlite3_table_column_metadata(conn.handle(), db_name.c_str(), table_name.c_str(),
+ column_name.c_str(),
+ &data_type, &collation, &nn, &pk, &ai);
+
+ if (res != SQLITE_OK)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+ ei.set_message(sqlite3_errmsg(conn.handle()));
+ }
+
+ return {data_type, collation, nn != 0, pk != 0, ai != 0};
+}
+
+
+
+auto table_column_meta_data(connection& conn,
+ cstring_ref table_name, cstring_ref column_name,
+ system::error_code & ec, error_info &ei) -> column_meta_data
+{
+ const char * data_type= "", *collation = "";
+ int nn, pk, ai;
+
+ int res = sqlite3_table_column_metadata(conn.handle(), nullptr, table_name.c_str(), column_name.c_str(),
+ &data_type, &collation, &nn, &pk, &ai);
+
+ if (res != SQLITE_OK)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, res);
+ ei.set_message(sqlite3_errmsg(conn.handle()));
+ }
+
+ return {data_type, collation, nn != 0, pk != 0, ai != 0};
+}
+
+
+
+
+auto table_column_meta_data(connection& conn,
+ cstring_ref db_name, cstring_ref table_name, cstring_ref column_name) -> column_meta_data
+{
+ system::error_code ec;
+ error_info ei;
+ auto res = table_column_meta_data(conn, db_name, table_name, column_name, ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return res;
+}
+
+auto table_column_meta_data(connection& conn,
+ cstring_ref table_name, cstring_ref column_name) -> column_meta_data
+{
+ system::error_code ec;
+ error_info ei;
+ auto res = table_column_meta_data(conn, table_name, column_name, ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return res;
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
diff --git a/subprojects/boost-sqlite/src/resultset.cpp b/subprojects/boost-sqlite/src/resultset.cpp
new file mode 100644
index 0000000..9528a2a
--- /dev/null
+++ b/subprojects/boost-sqlite/src/resultset.cpp
@@ -0,0 +1,61 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/resultset.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+
+bool resultset::read_next(
+ system::error_code & ec,
+ error_info & ei) // could also return row* instead!
+{
+ if (done_)
+ return false;
+ auto cc = sqlite3_step(impl_.get());
+ if (cc == SQLITE_DONE)
+ {
+ done_ = true;
+ return false;
+ }
+ else if (cc != SQLITE_ROW)
+ {
+ BOOST_SQLITE_ASSIGN_EC(ec, cc);
+ ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get())));
+ }
+ return !done_;
+}
+
+bool resultset::read_next()
+{
+ system::error_code ec;
+ error_info ei;
+ auto tmp = read_next(ec, ei);
+ if (ec)
+ throw_exception(system::system_error(ec, ei.message()));
+ return tmp;
+}
+
+resultset::iterator resultset::iterator::operator++()
+{
+ if (sentinel_)
+ return *this;
+
+ auto cc = sqlite3_step(row_.stm_);
+ if (cc == SQLITE_DONE)
+ sentinel_ = true;
+ else if (cc != SQLITE_ROW)
+ {
+ system::error_code ec;
+ BOOST_SQLITE_ASSIGN_EC(ec, cc);
+ throw_exception(system::system_error(ec, sqlite3_errmsg(sqlite3_db_handle(row_.stm_))));
+ }
+ return *this;
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
diff --git a/subprojects/boost-sqlite/src/row.cpp b/subprojects/boost-sqlite/src/row.cpp
new file mode 100644
index 0000000..a674e9c
--- /dev/null
+++ b/subprojects/boost-sqlite/src/row.cpp
@@ -0,0 +1,24 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/row.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+field row::at(std::size_t idx) const
+{
+ if (idx >= size())
+ throw_exception(std::out_of_range("column out of range"), BOOST_CURRENT_LOCATION);
+ else
+ {
+ field f;
+ f.stm_ = stm_;
+ f.col_ = static_cast<int>(idx);
+ return f;
+ }
+}
+
+BOOST_SQLITE_END_NAMESPACE
+
diff --git a/subprojects/boost-sqlite/src/value.cpp b/subprojects/boost-sqlite/src/value.cpp
new file mode 100644
index 0000000..4ad5294
--- /dev/null
+++ b/subprojects/boost-sqlite/src/value.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/value.hpp>
+
+BOOST_SQLITE_BEGIN_NAMESPACE
+
+cstring_ref value::get_text() const
+{
+ const auto ptr = sqlite3_value_text(value_);
+ if (ptr == nullptr)
+ {
+ if (is_null())
+ return "";
+ else
+ throw_exception(std::bad_alloc(), BOOST_CURRENT_LOCATION);
+ }
+ return reinterpret_cast<const char*>(ptr);
+}
+
+blob_view value::get_blob() const
+{
+ const auto ptr = sqlite3_value_blob(value_);
+ if (ptr == nullptr)
+ {
+ if (is_null())
+ return {nullptr, 0u};
+ else
+ throw_exception(std::bad_alloc(), BOOST_CURRENT_LOCATION);
+ }
+ const auto sz = sqlite3_value_bytes(value_);
+ return blob_view(ptr, sz);
+}
+
+
+BOOST_SQLITE_END_NAMESPACE
+
diff --git a/subprojects/boost-sqlite/test/CMakeLists.txt b/subprojects/boost-sqlite/test/CMakeLists.txt
new file mode 100644
index 0000000..79750fb
--- /dev/null
+++ b/subprojects/boost-sqlite/test/CMakeLists.txt
@@ -0,0 +1,11 @@
+file(GLOB ALL_TEST_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
+
+add_executable(boost_sqlite_tests ${ALL_TEST_FILES})
+target_link_libraries(boost_sqlite_tests PUBLIC SQLite::SQLite3
+ Boost::json Boost::sqlite Boost::unit_test_framework)
+target_compile_definitions(boost_sqlite_tests PUBLIC BOOST_SQLITE_SEPARATE_COMPILATION=1)
+
+add_test(NAME boost_sqlite_tests COMMAND boost_sqlite_tests)
+
+add_subdirectory(extension)
+
diff --git a/subprojects/boost-sqlite/test/Jamfile b/subprojects/boost-sqlite/test/Jamfile
new file mode 100644
index 0000000..85d19ed
--- /dev/null
+++ b/subprojects/boost-sqlite/test/Jamfile
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+#
+# Distributed under the Boost Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#
+# Official repository: https://github.com/boostorg/json
+#
+
+project :
+requirements <include>../../..
+
+;
+
+import testing ;
+
+lib sqlite3 ;
+
+run [ glob *.cpp ] sqlite3 /boost//sqlite /boost//json /boost//unit_test_framework ;
+
+
+lib simple_scalar : extension/simple_scalar.cpp /boost/sqlite//extension /boost//json
+ : <link>shared ;
+
+# TODO run simple_scalar as sqlite3 :memory: ".read extension/simple_scalar.sql" \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/allocator.cpp b/subprojects/boost-sqlite/test/allocator.cpp
new file mode 100644
index 0000000..a9d9b7c
--- /dev/null
+++ b/subprojects/boost-sqlite/test/allocator.cpp
@@ -0,0 +1,23 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/allocator.hpp>
+#include <boost/test/unit_test.hpp>
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(allocator)
+{
+
+ sqlite::allocator<int> alloc;
+
+ auto p = alloc.allocate(32);
+ BOOST_CHECK(p != nullptr);
+ alloc.deallocate(p, 32);
+
+ BOOST_CHECK_THROW(boost::ignore_unused(alloc.allocate((std::numeric_limits<std::size_t>::max)())), std::bad_alloc);
+}
diff --git a/subprojects/boost-sqlite/test/backup.cpp b/subprojects/boost-sqlite/test/backup.cpp
new file mode 100644
index 0000000..5a42c50
--- /dev/null
+++ b/subprojects/boost-sqlite/test/backup.cpp
@@ -0,0 +1,46 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/backup.hpp>
+#include <boost/sqlite/connection.hpp>
+
+#include <string>
+#include <vector>
+
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(backup)
+{
+ sqlite::connection conn1{":memory:"};
+ conn1.execute(
+#include "test-db.sql"
+ );
+ // language=sqlite
+ conn1.query("select * from author;");
+
+
+ sqlite::connection conn2{":memory:"};
+ sqlite::backup(conn1, conn2);
+
+ std::vector<std::string> names1, names2;
+
+ // language=sqlite
+ for (auto r : conn1.query("select first_name from author;"))
+ names1.emplace_back(r.at(0u).get_text());
+
+ // language=sqlite
+ for (auto r : conn2.query("select first_name from author;"))
+ names2.emplace_back(r.at(0u).get_text());
+
+ BOOST_CHECK(!names1.empty());
+ BOOST_CHECK(!names2.front().empty());
+ BOOST_CHECK(names1 == names2);
+ BOOST_CHECK_THROW(sqlite::backup(conn1, conn2, "foo", "bar"), boost::system::system_error);
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/blob.cpp b/subprojects/boost-sqlite/test/blob.cpp
new file mode 100644
index 0000000..ca6ce83
--- /dev/null
+++ b/subprojects/boost-sqlite/test/blob.cpp
@@ -0,0 +1,56 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/blob.hpp>
+#include <boost/sqlite/connection.hpp>
+
+#include <random>
+
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(blob)
+{
+ sqlite::connection conn{":memory:"};
+ // language=sqlite
+ conn.execute("create table blobs(id integer primary key autoincrement, bb blob);");
+
+ std::vector<unsigned char> blobby;
+ blobby.resize(4096*4096);
+ std::random_device dev;
+ std::mt19937 rng(dev());
+ std::uniform_int_distribution<std::mt19937::result_type> dist(0,255); // distribution in range [1, 6]
+
+ std::generate(blobby.begin(), blobby.end(),
+ [&]{return static_cast<unsigned char>(dist(rng));});
+
+
+ conn.prepare("insert into blobs(bb) values ($1);").execute(std::make_tuple(sqlite::zero_blob(4096 * 4096 )));
+
+ auto bh = open_blob(conn, "main", "blobs", "bb", 1);
+
+ BOOST_CHECK(bh.size() == 4096 * 4096);
+
+
+ unsigned char buf[4096];
+ std::generate(std::begin(buf), std::end(buf), [&]{return static_cast<unsigned char>(dist(rng));});
+ bh.read_at(buf, 4096, 4096);
+ BOOST_CHECK(std::all_of(std::begin(buf), std::end(buf), [](unsigned char c) {return c == 0u;}));
+
+ bh.write_at(blobby.data(), blobby.size(), 0u);
+ bh.read_at(buf, 4096, 4096);
+ BOOST_CHECK(std::memcmp(buf, blobby.data() + 4096, 4096) == 0);
+
+ BOOST_CHECK_THROW(open_blob(conn, "main", "doesnt-exit", "blobber", 2), boost::system::system_error);
+
+ sqlite::blob_handle bb;
+ BOOST_CHECK_THROW(bb.read_at(blobby.data(), blobby.size(), 0), boost::system::system_error);
+ BOOST_CHECK_THROW(bb.write_at(blobby.data(), blobby.size(), 0), boost::system::system_error);
+
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/catch.cpp b/subprojects/boost-sqlite/test/catch.cpp
new file mode 100644
index 0000000..37b8dbb
--- /dev/null
+++ b/subprojects/boost-sqlite/test/catch.cpp
@@ -0,0 +1,18 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/detail/catch.hpp>
+#include <boost/test/unit_test.hpp>
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(prefix)
+{
+ system::system_error se(SQLITE_TOOBIG, sqlite::sqlite_category());
+ BOOST_CHECK(sqlite::detail::get_message(se).empty());
+
+ se = system::system_error(SQLITE_TOOBIG, sqlite::sqlite_category(), "foobar");
+
+ BOOST_CHECK(sqlite::detail::get_message(se) == "foobar");
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/collation.cpp b/subprojects/boost-sqlite/test/collation.cpp
new file mode 100644
index 0000000..8893e42
--- /dev/null
+++ b/subprojects/boost-sqlite/test/collation.cpp
@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/collation.hpp>
+#include <string>
+#include <vector>
+
+#include "test.hpp"
+
+using namespace boost;
+
+struct collate_length
+{
+ int operator()(core::string_view l, core::string_view r) noexcept
+ {
+ return std::stoull(r) - l.size();
+ }
+};
+
+BOOST_AUTO_TEST_CASE(collation)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ sqlite::create_collation(conn, "length", collate_length{});
+
+ std::vector<std::string> names;
+
+ // language=sqlite
+ for (auto r : conn.query("select first_name from author where first_name = 5 collate length order by last_name asc;"))
+ names.emplace_back(r.at(0).get_text());
+
+ std::vector<std::string> cmp = {"peter", "ruben"};
+ BOOST_CHECK(names == cmp);
+
+ sqlite::delete_collation(conn, "length");
+
+ BOOST_CHECK_THROW(conn.query("select first_name from author where first_name = 5 collate length order by last_name asc;"), system::system_error);
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/connection.cpp b/subprojects/boost-sqlite/test/connection.cpp
new file mode 100644
index 0000000..b3c63ca
--- /dev/null
+++ b/subprojects/boost-sqlite/test/connection.cpp
@@ -0,0 +1,32 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+
+#include <filesystem>
+
+using namespace boost;
+
+
+BOOST_AUTO_TEST_CASE(connection)
+{
+ sqlite::connection conn;
+ conn.connect(std::filesystem::path(":memory:"));
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ BOOST_CHECK_THROW(conn.execute("elect * from nothing;"), boost::system::system_error);
+ conn.close();
+}
+
+BOOST_AUTO_TEST_CASE(exc)
+{
+ sqlite::connection conn;
+ conn.connect(sqlite::in_memory);
+ BOOST_CHECK_THROW(conn.execute("select 932 fro 12;"), boost::system::system_error);
+ conn.close();
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/extension/CMakeLists.txt b/subprojects/boost-sqlite/test/extension/CMakeLists.txt
new file mode 100644
index 0000000..a52a92c
--- /dev/null
+++ b/subprojects/boost-sqlite/test/extension/CMakeLists.txt
@@ -0,0 +1,19 @@
+
+file(GLOB_RECURSE ALL_SQLITE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.sql)
+file(GLOB_RECURSE ALL_CPP_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
+
+foreach(module ${ALL_CPP_FILES})
+ get_filename_component(stem ${module} NAME_WE)
+ add_library(boost_sqlite_test_extension_${stem} SHARED ${module})
+ target_link_libraries(boost_sqlite_test_extension_${stem} PUBLIC Boost::sqlite_ext)
+ target_include_directories(boost_sqlite_test_extension_${stem} PUBLIC ../../include)
+ set_property(TARGET boost_sqlite_test_extension_${stem} PROPERTY PREFIX "")
+ set_target_properties(boost_sqlite_test_extension_${stem} PROPERTIES OUTPUT_NAME ${stem})
+endforeach()
+
+foreach(script ${ALL_SQLITE_FILES})
+ get_filename_component(stem ${script} NAME_WE)
+ add_test(NAME boost_sqlite_test_extension_${stem} COMMAND
+ sqlite3 :memory: ".read ${CMAKE_CURRENT_SOURCE_DIR}/${script}"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+endforeach()
diff --git a/subprojects/boost-sqlite/test/extension/simple_scalar.cpp b/subprojects/boost-sqlite/test/extension/simple_scalar.cpp
new file mode 100644
index 0000000..4a05afe
--- /dev/null
+++ b/subprojects/boost-sqlite/test/extension/simple_scalar.cpp
@@ -0,0 +1,27 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/extension.hpp>
+#include <boost/sqlite/function.hpp>
+
+BOOST_SQLITE_EXTENSION(simplescalar, conn)
+{
+ create_scalar_function(
+ conn, "assert",
+ [](boost::sqlite::context<>, boost::span<boost::sqlite::value, 1u> sp)
+ {
+ if (sp.front().get_int() == 0)
+ throw std::logic_error("test failed");
+ });
+
+ create_scalar_function(
+ conn, "my_add",
+ [](boost::sqlite::context<>, boost::span<boost::sqlite::value, 2u> sp)-> sqlite3_int64
+ {
+ return sp[0].get_int() + sp[1].get_int();
+ });
+}
diff --git a/subprojects/boost-sqlite/test/extension/simple_scalar.sql b/subprojects/boost-sqlite/test/extension/simple_scalar.sql
new file mode 100644
index 0000000..00064ef
--- /dev/null
+++ b/subprojects/boost-sqlite/test/extension/simple_scalar.sql
@@ -0,0 +1,4 @@
+SELECT load_extension('./simple_scalar');
+
+select assert(5 = (select my_add(2, 3)));
+select assert(7 = (select my_add(4, 3)));
diff --git a/subprojects/boost-sqlite/test/field.cpp b/subprojects/boost-sqlite/test/field.cpp
new file mode 100644
index 0000000..a3fae32
--- /dev/null
+++ b/subprojects/boost-sqlite/test/field.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/field.hpp>
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(field)
+{
+ sqlite::connection conn(":memory:");
+ // language=sqlite
+ conn.execute(R"(
+create table type_tester(
+ id integer primary key autoincrement,
+ num real,
+ nl null,
+ txt text,
+ blb blob);
+
+ insert into type_tester values(42, 1.2, null, 'text', x'04050607');
+)");
+
+ auto res = conn.query("select * from type_tester");
+ auto r = res.current();
+
+ BOOST_CHECK(r[0].type() == sqlite::value_type::integer);
+ BOOST_CHECK(r[0].get_int() == 42);
+
+ BOOST_CHECK(r[1].type() == sqlite::value_type::floating);
+ BOOST_CHECK(r[1].get_double() == 1.2);
+
+ BOOST_CHECK(r[2].type() == sqlite::value_type::null);
+ BOOST_CHECK(r[3].type() == sqlite::value_type::text);
+ BOOST_CHECK(r[3].get_text() == "text");
+
+ BOOST_CHECK(r[4].type() == sqlite::value_type::blob);
+
+ sqlite::blob bl{4u};
+ char raw_data[4] = {4,5,6,7};
+ std::memcpy(bl.data(), raw_data, 4);
+ BOOST_CHECK(std::memcmp(bl.data(), r[4].get_blob().data(), 4u) == 0u);
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/function.cpp b/subprojects/boost-sqlite/test/function.cpp
new file mode 100644
index 0000000..b9a4c57
--- /dev/null
+++ b/subprojects/boost-sqlite/test/function.cpp
@@ -0,0 +1,306 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/function.hpp>
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+
+#include <string>
+#include <vector>
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(scalar)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ sqlite::create_scalar_function(
+ conn,
+ "to_upper",
+ [](sqlite::context<>, boost::span<sqlite::value, 1u> val)
+ -> variant2::variant<variant2::monostate, sqlite::value, std::string>
+ {
+ if (val.empty())
+ return {};
+
+ if (val[0].type() != sqlite::value_type::text)
+ return val[0];
+
+ auto txt = val[0].get_text();
+ std::string res;
+ res.resize(txt.size());
+ std::transform(txt.begin(), txt.end(), res.begin(), [](char c){return std::toupper(c);});
+ return res;
+ });
+
+ std::vector<std::string> names;
+
+ // language=sqlite
+ for (auto r : conn.query("select to_upper(first_name) from author order by last_name asc;"))
+ names.emplace_back(r.at(0).get_text());
+
+
+ std::vector<std::string> nm = {"PETER", "VINNIE", "RICHARD", "RUBEN"};
+ BOOST_CHECK(nm == names);
+}
+
+
+BOOST_AUTO_TEST_CASE(scalar_pointer)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ sqlite::create_scalar_function(
+ conn,
+ "to_upper",
+ +[](sqlite::context<>, boost::span<sqlite::value, 1u> val)
+ -> variant2::variant<variant2::monostate, sqlite::value, std::string>
+ {
+ if (val.empty())
+ return {};
+
+ if (val[0].type() != sqlite::value_type::text)
+ return val[0];
+
+ auto txt = val[0].get_text();
+ std::string res;
+ res.resize(txt.size());
+ std::transform(txt.begin(), txt.end(), res.begin(), [](char c){return std::toupper(c);});
+ return res;
+ });
+
+ std::vector<std::string> names;
+
+ // language=sqlite
+ for (auto r : conn.query("select to_upper(first_name) from author order by last_name asc;"))
+ names.emplace_back(r.at(0).get_text());
+
+
+ std::vector<std::string> nm = {"PETER", "VINNIE", "RICHARD", "RUBEN"};
+ BOOST_CHECK(nm == names);
+}
+
+
+BOOST_AUTO_TEST_CASE(scalar_void)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ sqlite::create_scalar_function(
+ conn,
+ "to_upper",
+ [](sqlite::context<>, boost::span<sqlite::value, 1u> )
+ {
+ return ;
+ });
+
+
+ // language=sqlite
+ for (auto r : conn.query("select to_upper(first_name) from author order by last_name asc;"))
+ BOOST_CHECK(r[0].is_null());
+}
+
+
+BOOST_AUTO_TEST_CASE(scalar_void_pointer)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ sqlite::create_scalar_function(
+ conn,
+ "to_upper",
+ +[](sqlite::context<>, boost::span<sqlite::value, 1u> )
+ {
+ return ;
+ });
+
+ std::vector<std::string> names;
+
+ // language=sqlite
+ for (auto r : conn.query("select to_upper(first_name) from author order by last_name asc;"))
+ BOOST_CHECK(r[0].is_null());
+}
+
+
+
+BOOST_AUTO_TEST_CASE(aggregate)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ struct aggregate_func
+ {
+ aggregate_func(int value) : counter(value) {}
+ std::size_t counter;
+ void step(boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ }
+
+ std::int64_t final()
+ {
+ return counter;
+ }
+ };
+
+ sqlite::create_aggregate_function<aggregate_func>(
+ conn,
+ "char_counter", std::make_tuple(0));
+
+ std::vector<std::size_t> lens;
+
+ // language=sqlite
+ for (auto r : conn.query("select char_counter(first_name) from author;"))
+ lens.emplace_back(r.at(0).get_int());
+
+ BOOST_CHECK(lens.size() == 1u);
+ BOOST_CHECK(lens[0] == (5 + 6 + 7 + 5));
+}
+
+BOOST_AUTO_TEST_CASE(aggregate_result)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ struct aggregate_func
+ {
+ aggregate_func(int value) : counter(value) {}
+ std::size_t counter;
+ sqlite::result<void> step(boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ return {};
+ }
+
+ sqlite::result<std::int64_t> final()
+ {
+ return counter;
+ }
+ };
+
+ sqlite::create_aggregate_function<aggregate_func>(
+ conn,
+ "char_counter", std::make_tuple(0));
+
+ std::vector<std::size_t> lens;
+
+ // language=sqlite
+ for (auto r : conn.query("select char_counter(first_name) from author;"))
+ lens.emplace_back(r.at(0).get_int());
+
+ BOOST_CHECK(lens.size() == 1u);
+ BOOST_CHECK(lens[0] == (5 + 6 + 7 + 5));
+}
+
+#if SQLITE_VERSION_NUMBER >= 3025000
+BOOST_AUTO_TEST_CASE(window)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ struct window_func
+ {
+ std::size_t counter;
+ void step(boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ }
+
+ void inverse(boost::span<sqlite::value, 1u> val)
+ {
+ counter -= val[0].get_text().size();
+ }
+
+
+ std::int64_t value()
+ {
+ return counter;
+ }
+ };
+
+ sqlite::create_window_function<window_func>(
+ conn,
+ "win_counter");
+
+ std::vector<std::size_t> lens;
+
+ // language=sqlite
+ for (auto r : conn.query(R"(
+select win_counter(first_name) over (
+ order by last_name rows between 1 preceding and 1 following ) as subrows
+ from author order by last_name asc;)"))
+ lens.emplace_back(r.at(0).get_int());
+
+ BOOST_CHECK(lens.size() == 4u);
+ BOOST_CHECK(lens[0] == 11);
+ BOOST_CHECK(lens[1] == 18);
+ BOOST_CHECK(lens[2] == 18);
+ BOOST_CHECK(lens[3] == 12);
+}
+
+BOOST_AUTO_TEST_CASE(window_result)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ struct window_func
+ {
+ std::size_t counter;
+ sqlite::result<void> step(boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ return{};
+ }
+
+ sqlite::result<void> inverse(boost::span<sqlite::value, 1u> val)
+ {
+ counter -= val[0].get_text().size();
+ return {};
+ }
+
+
+ sqlite::result<std::int64_t> value()
+ {
+ return counter;
+ }
+ };
+
+ sqlite::create_window_function<window_func>(
+ conn,
+ "win_counter");
+
+ std::vector<std::size_t> lens;
+
+ // language=sqlite
+ for (auto r : conn.query(R"(
+select win_counter(first_name) over (
+ order by last_name rows between 1 preceding and 1 following ) as subrows
+ from author order by last_name asc;)"))
+ lens.emplace_back(r.at(0).get_int());
+
+ BOOST_CHECK(lens.size() == 4u);
+ BOOST_CHECK(lens[0] == 11);
+ BOOST_CHECK(lens[1] == 18);
+ BOOST_CHECK(lens[2] == 18);
+ BOOST_CHECK(lens[3] == 12);
+}
+#endif
diff --git a/subprojects/boost-sqlite/test/hooks.cpp b/subprojects/boost-sqlite/test/hooks.cpp
new file mode 100644
index 0000000..2aa371f
--- /dev/null
+++ b/subprojects/boost-sqlite/test/hooks.cpp
@@ -0,0 +1,52 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/hooks.hpp>
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(hooks)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ bool called = false;
+ auto l =
+ [&](int op, core::string_view db, core::string_view table, sqlite3_int64 ) noexcept
+ {
+ BOOST_CHECK(op == SQLITE_INSERT);
+ BOOST_CHECK(db == "main");
+ BOOST_CHECK(table == "library");
+ called = true;
+ };
+
+
+ sqlite::update_hook(conn, l);
+ // language=sqlite
+ conn.query(R"(
+ insert into library ("name", "author") values
+ ('mustache',(select id from author where first_name = 'peter' and last_name = 'dimov'));
+ )");
+
+ BOOST_CHECK(called);
+
+#if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+ auto hk = [](sqlite::preupdate_context ctx,
+ int op,
+ const char * db_name,
+ const char * table_name,
+ sqlite3_int64 current_key,
+ sqlite3_int64 new_key) noexcept
+ {
+
+ };
+
+ preupdate_hook(conn, hk);
+#endif
+}
diff --git a/subprojects/boost-sqlite/test/json.cpp b/subprojects/boost-sqlite/test/json.cpp
new file mode 100644
index 0000000..fe924ab
--- /dev/null
+++ b/subprojects/boost-sqlite/test/json.cpp
@@ -0,0 +1,104 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/json.hpp>
+#include <boost/sqlite/connection.hpp>
+#include <boost/sqlite/function.hpp>
+#include <boost/json.hpp>
+#include "test.hpp"
+
+using namespace boost;
+
+
+BOOST_AUTO_TEST_SUITE(json_);
+
+BOOST_AUTO_TEST_CASE(to_value)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ auto q = conn.prepare("select 'foo', json_array($1, '2', null)").execute(std::make_tuple(1));
+
+ sqlite::row r = q.current();
+
+ BOOST_CHECK(!sqlite::is_json(r[0]));
+ BOOST_CHECK( sqlite::is_json(r[1]));
+ BOOST_CHECK(sqlite::as_json(r[1]) == (json::array{1, "2", nullptr}));
+ BOOST_CHECK(json::value_from(r[1]) == (json::array{1, "2", nullptr}));
+ BOOST_CHECK(!q.read_next());
+ BOOST_CHECK(!q.read_next());
+
+ // language=sqlite
+ q = conn.query(R"(select first_name, "name" from library inner join author a on a.id = library.author order by library.name asc)");
+
+ auto js = json::value_from(std::move(q));
+
+ json::array aa {
+ {
+ {"first_name", "vinnie"},
+ {"name", "beast"}
+ },
+ {
+ {"first_name", "peter"},
+ {"name", "mp11"}
+ },
+ {
+ {"first_name", "ruben"},
+ {"name", "mysql"}
+ },
+ {
+ {"first_name","peter"},
+ {"name", "variant2"}
+ },
+ };
+
+ BOOST_CHECK(aa == aa);
+};
+
+BOOST_AUTO_TEST_CASE(blob)
+{
+ sqlite::connection conn(":memory:");
+ BOOST_CHECK_THROW(json::value_from(conn.prepare("select $1;").execute({sqlite::zero_blob(1024)})), std::invalid_argument);
+ BOOST_CHECK(nullptr == json::value_from(conn.query("select null;").current().at(0)));
+ BOOST_CHECK(1234 == json::value_from(conn.query("select 1234;"). current().at(0)));
+ BOOST_CHECK(12.4 == json::value_from(conn.query("select 12.4;"). current().at(0)));
+}
+
+BOOST_AUTO_TEST_CASE(value)
+{
+ sqlite::connection conn(":memory:");
+ BOOST_CHECK_THROW(json::value_from(conn.prepare("select $1;").execute({sqlite::zero_blob(1024)}).current().at(0)), std::invalid_argument);
+ BOOST_CHECK(nullptr == json::value_from(conn.query("select null;").current().at(0).get_value()));
+ BOOST_CHECK(1234 == json::value_from(conn.query("select 1234;"). current().at(0).get_value()));
+ BOOST_CHECK(12.4 == json::value_from(conn.query("select 12.4;"). current().at(0).get_value()));
+ BOOST_CHECK("txt" == json::value_from(conn.query("select 'txt';"). current().at(0).get_value()));
+}
+
+
+BOOST_AUTO_TEST_CASE(subtype)
+{
+ sqlite::connection conn(":memory:");
+ BOOST_CHECK(!sqlite::is_json(conn.prepare("select $1;").execute({"foobar"}).current().at(0)));
+ BOOST_CHECK(sqlite::is_json(conn.prepare("select json_array($1);").execute({"foobar"}).current().at(0)));
+}
+
+
+BOOST_AUTO_TEST_CASE(function)
+{
+ sqlite::connection conn(":memory:");
+ sqlite::create_scalar_function(conn, "my_json_parse",
+ [](boost::sqlite::context<> , boost::span<boost::sqlite::value, 1u> s)
+ {
+ return json::parse(s[0].get_text());
+ });
+
+ BOOST_CHECK(sqlite::is_json(conn.prepare("select my_json_parse($1);")
+ .execute({R"({"foo" : 42, "bar" : "xyz"})"}).current().at(0)));
+}
+
+
+BOOST_AUTO_TEST_SUITE_END(); \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/main_test.cpp b/subprojects/boost-sqlite/test/main_test.cpp
new file mode 100644
index 0000000..23c2f70
--- /dev/null
+++ b/subprojects/boost-sqlite/test/main_test.cpp
@@ -0,0 +1,9 @@
+// Copyright (c) 2021 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#define BOOST_TEST_MODULE sqlite_test
+#include <boost/test/unit_test.hpp>
+
+#include <boost/sqlite.hpp>
diff --git a/subprojects/boost-sqlite/test/meta_data.cpp b/subprojects/boost-sqlite/test/meta_data.cpp
new file mode 100644
index 0000000..2861a1c
--- /dev/null
+++ b/subprojects/boost-sqlite/test/meta_data.cpp
@@ -0,0 +1,45 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/meta_data.hpp>
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+#include <boost/algorithm/string.hpp>
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(meta_data)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+
+
+ auto fn = table_column_meta_data(conn, "author", "first_name");
+ BOOST_CHECK_MESSAGE(boost::iequals(fn.data_type, "TEXT"), fn.data_type);
+ BOOST_CHECK(!fn.auto_increment);
+ BOOST_CHECK_MESSAGE(boost::iequals(fn.collation, "BINARY"), fn.collation);
+ BOOST_CHECK(!fn.primary_key);
+ BOOST_CHECK( fn.not_null);
+
+ auto ln = table_column_meta_data(conn, "main", "author", "last_name");
+ BOOST_CHECK_MESSAGE(boost::iequals(ln.data_type, "TEXT"), ln.data_type);
+ BOOST_CHECK(!ln.auto_increment);
+ BOOST_CHECK_MESSAGE(boost::iequals(ln.collation, "BINARY"), ln.collation);
+ BOOST_CHECK(!ln.primary_key);
+ BOOST_CHECK(!ln.not_null);
+
+ auto id = table_column_meta_data(conn, "main", "author", "id");
+ BOOST_CHECK(boost::iequals(id.data_type, "INTEGER"));
+ BOOST_CHECK( id.auto_increment);
+ BOOST_CHECK(boost::iequals(id.collation, "BINARY"));
+ BOOST_CHECK( id.primary_key);
+ BOOST_CHECK( id.not_null);
+
+ conn.close();
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/mutex.cpp b/subprojects/boost-sqlite/test/mutex.cpp
new file mode 100644
index 0000000..89023e1
--- /dev/null
+++ b/subprojects/boost-sqlite/test/mutex.cpp
@@ -0,0 +1,31 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/mutex.hpp>
+
+#include <boost/test/unit_test.hpp>
+#include <mutex>
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(mutex)
+{
+ sqlite::mutex mtx;
+ sqlite::recursive_mutex rmtx;
+ BOOST_CHECK(mtx.try_lock());
+ BOOST_CHECK(!mtx.try_lock());
+ mtx.unlock();
+
+ std::lock_guard<sqlite::mutex> l1{mtx};
+ std::lock_guard<sqlite::recursive_mutex> l2{rmtx};
+ std::lock_guard<sqlite::recursive_mutex> l{rmtx};
+
+ BOOST_CHECK(rmtx.try_lock());
+ BOOST_CHECK(rmtx.try_lock());
+ BOOST_CHECK(rmtx.try_lock());
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/statement.cpp b/subprojects/boost-sqlite/test/statement.cpp
new file mode 100644
index 0000000..b9e96f6
--- /dev/null
+++ b/subprojects/boost-sqlite/test/statement.cpp
@@ -0,0 +1,74 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+
+#include <boost/json.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <unordered_map>
+
+
+using namespace boost;
+
+
+BOOST_AUTO_TEST_CASE(statement)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+#if SQLITE_VERSION_NUMBER >= 3020000
+ std::unique_ptr<int> data{new int(42)};
+
+ auto ip = data.get();
+ auto q = conn.prepare("select $1;").execute(std::make_tuple(std::move(data)));
+ sqlite::row r = q.current();
+ BOOST_CHECK(r.size() == 1u);
+
+ auto v = r.at(0).get_value();
+ BOOST_CHECK(v.type() == sqlite::value_type::null);
+ BOOST_CHECK(v.get_pointer<int>() != nullptr);
+ BOOST_CHECK(v.get_pointer<int>() == ip);
+ BOOST_CHECK(v.get_pointer<double>() == nullptr);
+#endif
+ BOOST_CHECK_THROW(conn.prepare("select * from nothing where name = $name;").execute({}), boost::system::system_error);
+}
+
+
+BOOST_AUTO_TEST_CASE(decltype_)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+ auto q = conn.prepare("select* from author;");
+
+ BOOST_CHECK(boost::iequals(q.declared_type(0), "INTEGER"));
+ BOOST_CHECK(boost::iequals(q.declared_type(1), "TEXT"));
+ BOOST_CHECK(boost::iequals(q.declared_type(2), "TEXT"));
+
+ BOOST_CHECK_THROW(conn.prepare("elect * from nothing;"), boost::system::system_error);
+}
+
+
+BOOST_AUTO_TEST_CASE(map)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+ auto q = conn.prepare("select * from author where first_name = $name;").execute({{"name", 42}});
+ BOOST_CHECK_THROW(conn.prepare("select * from nothing where name = $name;").execute({{"n4ame", 123}}), boost::system::system_error);
+
+ std::unordered_map<std::string, variant2::variant<int, std::string>> params = {{"name", 42}};
+ q = conn.prepare("select * from author where first_name = $name;").execute(params);
+ BOOST_CHECK_THROW(conn.prepare("select * from nothing where name = $name;").execute(params), boost::system::system_error);
+
+ BOOST_CHECK_THROW(conn.prepare("elect * from nothing;"), boost::system::system_error);
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/static_resultset.cpp b/subprojects/boost-sqlite/test/static_resultset.cpp
new file mode 100644
index 0000000..6a9b7c0
--- /dev/null
+++ b/subprojects/boost-sqlite/test/static_resultset.cpp
@@ -0,0 +1,95 @@
+//
+// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/static_resultset.hpp>
+#include <boost/sqlite/connection.hpp>
+
+#include <boost/describe/class.hpp>
+
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(tuple)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ using tup = std::tuple<sqlite_int64, sqlite::string_view, sqlite::string_view>;
+
+ bool found = false;
+ for (tup t : conn.query<tup>("select id, first_name, last_name from author where last_name = 'hodges';"))
+ {
+ BOOST_CHECK(std::get<1>(t) == "richard");
+ found = true;
+ }
+ BOOST_CHECK(found);
+
+ found = false;
+ for (tup t : conn.prepare("select id, first_name, last_name from author where last_name = ?;")
+ .execute<tup>({"hodges"}))
+ {
+ BOOST_CHECK(std::get<1>(t) == "richard");
+ found = true;
+ }
+ BOOST_CHECK(found);
+
+ BOOST_CHECK_THROW(conn.query<tup>("select first_name, last_name from author where last_name = 'hodges';"),
+ system::system_error);
+ conn.close();
+}
+
+struct author
+{
+ std::string last_name;
+ std::string first_name;
+};
+
+#if __cplusplus < 202002L
+BOOST_DESCRIBE_STRUCT(author, (), (last_name, first_name));
+#endif
+
+#if __cplusplus > 201402L
+
+BOOST_AUTO_TEST_CASE(reflection)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+
+ bool found = false;
+ for (author t : conn.query<author>("select first_name, last_name from author where last_name = 'hodges';"))
+ {
+ BOOST_CHECK(t.first_name == "richard");
+ BOOST_CHECK(t.last_name == "hodges");
+ found = true;
+ }
+ BOOST_CHECK(found);
+
+ found = false;
+ for (author t : conn.prepare("select first_name, last_name from author where last_name = ?;")
+ .execute<author>({"hodges"}))
+ {
+ BOOST_CHECK(t.first_name == "richard");
+ BOOST_CHECK(t.last_name == "hodges");
+ found = true;
+ }
+ BOOST_CHECK(found);
+
+ BOOST_CHECK_THROW(conn.query<author>("select id, first_name, last_name from author where last_name = 'hodges';"),
+ system::system_error);
+ conn.close();
+}
+
+#endif
diff --git a/subprojects/boost-sqlite/test/test-db.sql b/subprojects/boost-sqlite/test/test-db.sql
new file mode 100644
index 0000000..251313c
--- /dev/null
+++ b/subprojects/boost-sqlite/test/test-db.sql
@@ -0,0 +1,29 @@
+--query_helper(R"(
+
+create table author (
+ id integer primary key autoincrement not null,
+ first_name text unique not null,
+ last_name text
+);
+
+insert into author (first_name, last_name) values
+ ('vinnie', 'falco'),
+ ('richard', 'hodges'),
+ ('ruben', 'perez'),
+ ('peter', 'dimov')
+;
+
+create table library(
+ id integer primary key autoincrement,
+ name text unique,
+ author integer references author(id)
+);
+
+insert into library ("name", author) values
+ ('beast', (select id from author where first_name = 'vinnie' and last_name = 'falco')),
+ ('mysql', (select id from author where first_name = 'ruben' and last_name = 'perez')),
+ ('mp11', (select id from author where first_name = 'peter' and last_name = 'dimov')),
+ ('variant2', (select id from author where first_name = 'peter' and last_name = 'dimov'))
+;
+
+--)") \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/test.hpp b/subprojects/boost-sqlite/test/test.hpp
new file mode 100644
index 0000000..3244d9e
--- /dev/null
+++ b/subprojects/boost-sqlite/test/test.hpp
@@ -0,0 +1,22 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_TEST_HPP
+#define BOOST_SQLITE_TEST_HPP
+
+#include <boost/test/unit_test.hpp>
+
+struct query_helper
+{
+ const char * query;
+ query_helper(const char * query) : query(query) {}
+
+ const char * operator--() const {return query;}
+};
+
+
+#endif //BOOST_SQLITE_TEST_HPP
diff --git a/subprojects/boost-sqlite/test/transaction.cpp b/subprojects/boost-sqlite/test/transaction.cpp
new file mode 100644
index 0000000..f1331b7
--- /dev/null
+++ b/subprojects/boost-sqlite/test/transaction.cpp
@@ -0,0 +1,89 @@
+//
+// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/transaction.hpp>
+
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(transaction)
+{
+ sqlite::connection conn{":memory:"};
+ conn.execute("create table test(nr integer);");
+
+ auto check_size = [&]{
+ std::size_t n = 0ull;
+ for (auto l : conn.query("select * from test"))
+ {
+ boost::ignore_unused(l);
+ n++;
+ }
+
+ return n;
+ };
+
+ {
+ sqlite::transaction t{conn};
+ BOOST_CHECK_THROW(sqlite::transaction{conn}, system::system_error);
+
+ conn.execute("insert into test values(1), (2)");
+ BOOST_CHECK(check_size() == 2);
+
+ {
+ sqlite::savepoint sq{conn, "s1"};
+ conn.execute("insert into test values(3)");
+ BOOST_CHECK(check_size() == 3);
+ }
+
+ BOOST_CHECK(check_size() == 2);
+
+ {
+ sqlite::savepoint sq{conn, "s1"};
+ conn.execute("insert into test values(4)");
+ BOOST_CHECK(check_size() == 3);
+ sq.commit();
+ }
+
+ BOOST_CHECK(check_size() == 3);
+
+ {
+ sqlite::savepoint sq{conn, "s1"};
+ conn.execute("insert into test values(5)");
+ BOOST_CHECK(check_size() == 4);
+ {
+ sqlite::savepoint sq2{conn, "s2"};
+ conn.execute("insert into test values (6), (7)");
+ BOOST_CHECK(check_size() == 6);
+ sq2.commit();
+ }
+ BOOST_CHECK_EQUAL(check_size(), 6u);
+ }
+ BOOST_CHECK_EQUAL(check_size(), 3u);
+
+ }
+
+ BOOST_CHECK(conn.query("select * from test").done());
+
+ {
+ system::error_code ec;
+ sqlite::error_info ei;
+ conn.execute("BEGIN", ec, ei);
+ BOOST_CHECK(!ec);
+ conn.execute("BEGIN", ec, ei);
+ BOOST_CHECK(ec);
+
+ BOOST_CHECK_THROW(sqlite::transaction{conn}, system::system_error);
+ sqlite::transaction t{conn, sqlite::transaction::adopt_transaction};
+
+ conn.execute("insert into test values (42), (3);");
+ t.commit();
+ }
+
+ BOOST_CHECK_EQUAL(check_size(), 2u);
+}
+
diff --git a/subprojects/boost-sqlite/test/vtable.cpp b/subprojects/boost-sqlite/test/vtable.cpp
new file mode 100644
index 0000000..4f38732
--- /dev/null
+++ b/subprojects/boost-sqlite/test/vtable.cpp
@@ -0,0 +1,256 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/test/unit_test.hpp>
+#include "test.hpp"
+
+#include <iostream>
+#include <boost/sqlite.hpp>
+#include <boost/unordered_map.hpp>
+#include <boost/intrusive/list.hpp>
+
+#include <string>
+#include <vector>
+
+using namespace boost;
+
+
+BOOST_AUTO_TEST_SUITE(vtable_);
+
+struct del_info : sqlite3_index_info
+{
+ del_info() : sqlite3_index_info()
+ {
+
+ }
+
+ ~del_info()
+ {
+ if (needToFreeIdxStr)
+ sqlite3_free(idxStr);
+ }
+};
+
+struct trivial_struct
+{
+ trivial_struct() = default;
+ int i{1}, j{2}, k{3};
+};
+
+
+
+struct simple_cursor final : sqlite::vtab::cursor<core::string_view>
+{
+
+ simple_cursor(std::vector<std::string>::const_iterator itr,
+ std::vector<std::string>::const_iterator end) : itr(itr), end(end) {}
+ std::vector<std::string>::const_iterator itr, end;
+
+ sqlite::result<void> next() {itr++; return {};}
+ sqlite::result<sqlite3_int64> row_id() {return *reinterpret_cast<sqlite3_int64*>(&itr);}
+
+ sqlite::result<core::string_view> column(int i, bool /* no_change */)
+ {
+ if (i > 0)
+ throw_exception(std::out_of_range("column out of range"));
+
+ return *itr;
+ }
+
+ bool eof() noexcept {return itr == end;}
+};
+
+struct simple_table final : sqlite::vtab::table<simple_cursor>
+{
+ const char * declaration()
+ {
+ return R"(create table x(name text);)";
+ }
+
+ simple_table(std::vector<std::string> & names) : names(names) {}
+ std::vector<std::string> & names;
+
+ sqlite::result<cursor_type> open()
+ {
+ return cursor_type{names.begin(), names.end()};
+ }
+
+};
+
+struct simple_test_impl final : sqlite::vtab::eponymous_module<simple_table>
+{
+ std::vector<std::string> names = {"ruben", "vinnie", "richard", "klemens"};
+
+
+
+
+ sqlite::result<table_type> connect(sqlite::connection,
+ int /*argc*/, const char * const * /*argv*/)
+ {
+ return table_type{names};
+ }
+
+};
+
+
+BOOST_AUTO_TEST_CASE(simple_reader)
+{
+ sqlite::connection conn(":memory:");
+ auto & m = create_module(conn, "test_table", simple_test_impl{});
+
+ auto itr = m.names.begin();
+ for (auto q : conn.query("select * from test_table ;"))
+ {
+ BOOST_CHECK(q.size() == 1);
+ BOOST_CHECK(q.at(0).get_text() == *itr++);
+ }
+
+ m.names.emplace_back("marcelo");
+ itr = m.names.begin();
+ for (auto q : conn.query("select * from test_table ;"))
+ {
+ BOOST_CHECK(q.size() == 1);
+ BOOST_CHECK(q.at(0).get_text() == *itr++);
+ }
+
+ BOOST_CHECK_THROW(conn.query("insert into test_table values('chris')"), boost::system::system_error);
+}
+
+struct modifyable_table;
+
+struct modifyable_cursor final : sqlite::vtab::cursor<variant2::variant<std::int64_t, core::string_view>>
+{
+ using iterator = boost::unordered_map<std::int64_t, std::string>::const_iterator;
+
+ modifyable_cursor(iterator itr, iterator end) : itr(itr), end(end) {}
+
+ iterator itr, end;
+
+ sqlite::result<void> next() noexcept {itr++; return {};}
+ sqlite::result<sqlite3_int64> row_id() noexcept {return itr->first;}
+
+ sqlite::result<column_type> column(int i, bool /* no_change */) noexcept
+ {
+ switch (i)
+ {
+ case 0: return itr->first;
+ case 1: return itr->second;
+ default:
+ return sqlite::error(SQLITE_RANGE, sqlite::error_info("column out of range"));
+
+ }
+ }
+
+ bool eof() noexcept {return itr == end;}
+};
+
+struct modifyable_table final :
+ sqlite::vtab::table<modifyable_cursor>,
+ intrusive::list_base_hook<intrusive::link_mode<intrusive::auto_unlink> >,
+ sqlite::vtab::modifiable
+{
+ const char * declaration()
+ {
+ return R"(create table x(id integer primary key autoincrement, name text);)";
+ }
+
+ std::string name;
+ boost::unordered_map<std::int64_t, std::string> names;
+
+ int last_index = 0;
+
+ modifyable_table() = default;
+ modifyable_table(modifyable_table && lhs)
+ : name(std::move(lhs.name)), names(std::move(lhs.names)), last_index(lhs.last_index )
+ {
+ this->swap_nodes(lhs);
+ }
+
+
+ sqlite::result<modifyable_cursor> open()
+ {
+ return modifyable_cursor{names.begin(), names.end()};
+ }
+
+ sqlite::result<void> delete_(sqlite::value key)
+ {
+ BOOST_CHECK(names.erase(key.get_int()) == 1);
+ return {};
+ }
+ sqlite::result<sqlite_int64> insert(sqlite::value /*key*/, span<sqlite::value> values,
+ int /*on_conflict*/)
+ {
+ int id = values[0].is_null() ? last_index++ : values[0].get_int();
+ auto itr = names.emplace(id, values[1].get_text()).first;
+ return itr->first;
+ }
+ sqlite::result<sqlite_int64> update(sqlite::value old_key, sqlite::value new_key,
+ span<sqlite::value> values,
+ int /*on_conflict*/)
+ {
+ if (new_key.get_int() != old_key.get_int())
+ names.erase(old_key.get_int());
+ names.insert_or_assign(new_key.get_int(), values[1].get_text());
+ return 0u;
+ }
+};
+
+struct modifyable_test_impl final : sqlite::vtab::eponymous_module<modifyable_table>
+{
+ modifyable_test_impl() = default;
+
+ intrusive::list<table_type, intrusive::constant_time_size<false>> list;
+
+ sqlite::result<table_type> connect(sqlite::connection,
+ int /*argc*/, const char * const argv[]) noexcept
+ {
+ table_type tt{};
+ tt.name.assign(argv[2]);
+ list.push_back(tt);
+ return sqlite::result<table_type>(std::move(tt));
+ }
+};
+
+
+BOOST_AUTO_TEST_CASE(modifyable_reader)
+{
+ sqlite::connection conn(":memory:");
+
+ auto & m = create_module(conn, "name_table", modifyable_test_impl{});
+ conn.execute("create virtual table test_table USING name_table; ");
+
+ BOOST_CHECK(m.list.size() == 1);
+ BOOST_CHECK(m.list.front().name == "test_table");
+ BOOST_CHECK(m.list.front().names.empty());
+
+ conn.prepare("insert into test_table(name) values ($1), ($2), ($3), ($4);")
+ .execute(std::make_tuple("vinnie", "richard", "ruben", "peter"));
+
+ auto & t = m.list.front();
+ BOOST_CHECK(t.names.size() == 4);
+ BOOST_CHECK(t.names[0] == "vinnie");
+ BOOST_CHECK(t.names[1] == "richard");
+ BOOST_CHECK(t.names[2] == "ruben");
+ BOOST_CHECK(t.names[3] == "peter");
+
+ conn.prepare("delete from test_table where name = $1;").execute(std::make_tuple("richard"));
+
+ BOOST_CHECK(t.names.size() == 3);
+ BOOST_CHECK(t.names[0] == "vinnie");
+ BOOST_CHECK(t.names[2] == "ruben");
+ BOOST_CHECK(t.names[3] == "peter");
+
+ conn.prepare("update test_table set name = $1 where id = 0;").execute(std::make_tuple("richard"));
+
+ BOOST_CHECK(t.names.size() == 3);
+ BOOST_CHECK(t.names[0] == "richard");
+ BOOST_CHECK(t.names[2] == "ruben");
+ BOOST_CHECK(t.names[3] == "peter");
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/subprojects/boost-sqlite/tools/get-boost.sh b/subprojects/boost-sqlite/tools/get-boost.sh
new file mode 100755
index 0000000..fb0c8ed
--- /dev/null
+++ b/subprojects/boost-sqlite/tools/get-boost.sh
@@ -0,0 +1,50 @@
+#! /bin/sh
+
+set -e
+
+build_dir=$2
+
+branch="master"
+
+if [ "$1" != "master" -a "$1" != "refs/heads/master" ]; then
+ branch="develop"
+fi
+
+echo "BUILD_DIR: $build_dir"
+echo "BRANCH: $branch"
+
+git clone -b $branch --depth 1 https://github.com/boostorg/boost.git boost-root
+cd boost-root
+
+# Use a reasonably large depth to prevent intermittent update failures due to
+# commits being on a submodule's master before the superproject is updated.
+git submodule update --init --depth 20 --jobs 4 \
+ libs/array \
+ libs/headers \
+ tools/build \
+ tools/boost_install \
+ tools/boostdep \
+ libs/align \
+ libs/atomic \
+ libs/asio \
+ libs/assert \
+ libs/config \
+ libs/container \
+ libs/container_hash \
+ libs/core \
+ libs/callable_traits \
+ libs/describe \
+ libs/filesystem \
+ libs/intrusive \
+ libs/optional \
+ libs/system \
+ libs/move \
+ libs/mp11 \
+ libs/variant2 \
+ libs/throw_exception \
+ libs/json
+
+echo Submodule update complete
+
+rm -rf libs/sqlite
+cp -r $build_dir libs/sqlite
diff --git a/subprojects/boost-sqlite/tools/user-config.jam b/subprojects/boost-sqlite/tools/user-config.jam
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/subprojects/boost-sqlite/tools/user-config.jam