From efcea3a80da7c4479d5fe168435ecc9fd06bdc72 Mon Sep 17 00:00:00 2001 From: John Turner Date: Sun, 14 Sep 2025 00:16:10 -0400 Subject: Squashed 'subprojects/boost-sqlite/' content from commit 3378e35 git-subtree-dir: subprojects/boost-sqlite git-subtree-split: 3378e353705271e569cf4ba15c467b840a39798c --- .drone.star | 34 ++ .drone/drone.bat | 35 ++ .drone/drone.sh | 211 +++++++ CMakeLists.txt | 112 ++++ build/Jamfile | 36 ++ doc/Jamfile | 18 + doc/extensions.adoc | 41 ++ doc/functions.adoc | 88 +++ doc/index.adoc | 28 + doc/reference.adoc | 28 + doc/reference/allocator.adoc | 31 + doc/reference/backup.adoc | 43 ++ doc/reference/blob.adoc | 141 +++++ doc/reference/collation.adoc | 45 ++ doc/reference/connection.adoc | 100 ++++ doc/reference/cstring_ref.adoc | 16 + doc/reference/error.adoc | 91 +++ doc/reference/extension.adoc | 36 ++ doc/reference/field.adoc | 42 ++ doc/reference/function.adoc | 263 +++++++++ doc/reference/hooks.adoc | 105 ++++ doc/reference/json.adoc | 28 + doc/reference/memory.adoc | 29 + doc/reference/meta_data.adoc | 36 ++ doc/reference/mutex.adoc | 16 + doc/reference/result.adoc | 40 ++ doc/reference/resultset.adoc | 72 +++ doc/reference/row.adoc | 56 ++ doc/reference/statement.adoc | 165 ++++++ doc/reference/static_resultset.adoc | 98 ++++ doc/reference/string.adoc | 18 + doc/reference/transaction.adoc | 104 ++++ doc/reference/value.adoc | 85 +++ doc/reference/vtable.adoc | 275 +++++++++ doc/tutorial.adoc | 139 +++++ doc/vtable.adoc | 167 ++++++ example/CMakeLists.txt | 20 + example/csv.cpp | 273 +++++++++ example/describe.cpp | 202 +++++++ example/multi_index.cpp | 433 ++++++++++++++ example/ordered_map.cpp | 304 ++++++++++ example/tutorial.cpp | 136 +++++ example/tutorial_ec.cpp | 197 +++++++ example/url.cpp | 315 ++++++++++ example/url.sql | 19 + include/boost/sqlite.hpp | 38 ++ include/boost/sqlite/allocator.hpp | 48 ++ include/boost/sqlite/backup.hpp | 74 +++ include/boost/sqlite/blob.hpp | 156 +++++ include/boost/sqlite/collation.hpp | 140 +++++ include/boost/sqlite/connection.hpp | 210 +++++++ include/boost/sqlite/cstring_ref.hpp | 223 +++++++ include/boost/sqlite/detail/aggregate_function.hpp | 166 ++++++ include/boost/sqlite/detail/catch.hpp | 169 ++++++ include/boost/sqlite/detail/config.hpp | 83 +++ include/boost/sqlite/detail/exception.hpp | 48 ++ include/boost/sqlite/detail/scalar_function.hpp | 172 ++++++ include/boost/sqlite/detail/vtable.hpp | 611 +++++++++++++++++++ include/boost/sqlite/detail/window_function.hpp | 238 ++++++++ include/boost/sqlite/error.hpp | 178 ++++++ include/boost/sqlite/extension.hpp | 67 +++ include/boost/sqlite/field.hpp | 85 +++ include/boost/sqlite/function.hpp | 452 ++++++++++++++ include/boost/sqlite/hooks.hpp | 370 ++++++++++++ include/boost/sqlite/json.hpp | 140 +++++ include/boost/sqlite/memory.hpp | 105 ++++ include/boost/sqlite/meta_data.hpp | 58 ++ include/boost/sqlite/mutex.hpp | 55 ++ include/boost/sqlite/result.hpp | 147 +++++ include/boost/sqlite/resultset.hpp | 150 +++++ include/boost/sqlite/row.hpp | 167 ++++++ include/boost/sqlite/statement.hpp | 651 +++++++++++++++++++++ include/boost/sqlite/static_resultset.hpp | 497 ++++++++++++++++ include/boost/sqlite/string.hpp | 49 ++ include/boost/sqlite/transaction.hpp | 198 +++++++ include/boost/sqlite/value.hpp | 130 ++++ include/boost/sqlite/vtable.hpp | 615 +++++++++++++++++++ readme.md | 277 +++++++++ src/backup.cpp | 61 ++ src/blob.cpp | 94 +++ src/connection.cpp | 158 +++++ src/detail/exception.cpp | 46 ++ src/error.cpp | 83 +++ src/ext.cpp | 14 + src/field.cpp | 39 ++ src/meta_data.cpp | 83 +++ src/resultset.cpp | 61 ++ src/row.cpp | 24 + src/value.cpp | 39 ++ test/CMakeLists.txt | 11 + test/Jamfile | 25 + test/allocator.cpp | 23 + test/backup.cpp | 46 ++ test/blob.cpp | 56 ++ test/catch.cpp | 18 + test/collation.cpp | 45 ++ test/connection.cpp | 32 + test/extension/CMakeLists.txt | 19 + test/extension/simple_scalar.cpp | 27 + test/extension/simple_scalar.sql | 4 + test/field.cpp | 46 ++ test/function.cpp | 306 ++++++++++ test/hooks.cpp | 52 ++ test/json.cpp | 104 ++++ test/main_test.cpp | 9 + test/meta_data.cpp | 45 ++ test/mutex.cpp | 31 + test/statement.cpp | 74 +++ test/static_resultset.cpp | 95 +++ test/test-db.sql | 29 + test/test.hpp | 22 + test/transaction.cpp | 89 +++ test/vtable.cpp | 256 ++++++++ tools/get-boost.sh | 50 ++ tools/user-config.jam | 0 115 files changed, 13754 insertions(+) create mode 100644 .drone.star create mode 100755 .drone/drone.bat create mode 100755 .drone/drone.sh create mode 100644 CMakeLists.txt create mode 100644 build/Jamfile create mode 100644 doc/Jamfile create mode 100644 doc/extensions.adoc create mode 100644 doc/functions.adoc create mode 100644 doc/index.adoc create mode 100644 doc/reference.adoc create mode 100644 doc/reference/allocator.adoc create mode 100644 doc/reference/backup.adoc create mode 100644 doc/reference/blob.adoc create mode 100644 doc/reference/collation.adoc create mode 100644 doc/reference/connection.adoc create mode 100644 doc/reference/cstring_ref.adoc create mode 100644 doc/reference/error.adoc create mode 100644 doc/reference/extension.adoc create mode 100644 doc/reference/field.adoc create mode 100644 doc/reference/function.adoc create mode 100644 doc/reference/hooks.adoc create mode 100644 doc/reference/json.adoc create mode 100644 doc/reference/memory.adoc create mode 100644 doc/reference/meta_data.adoc create mode 100644 doc/reference/mutex.adoc create mode 100644 doc/reference/result.adoc create mode 100644 doc/reference/resultset.adoc create mode 100644 doc/reference/row.adoc create mode 100644 doc/reference/statement.adoc create mode 100644 doc/reference/static_resultset.adoc create mode 100644 doc/reference/string.adoc create mode 100644 doc/reference/transaction.adoc create mode 100644 doc/reference/value.adoc create mode 100644 doc/reference/vtable.adoc create mode 100644 doc/tutorial.adoc create mode 100644 doc/vtable.adoc create mode 100644 example/CMakeLists.txt create mode 100644 example/csv.cpp create mode 100644 example/describe.cpp create mode 100644 example/multi_index.cpp create mode 100644 example/ordered_map.cpp create mode 100644 example/tutorial.cpp create mode 100644 example/tutorial_ec.cpp create mode 100644 example/url.cpp create mode 100644 example/url.sql create mode 100644 include/boost/sqlite.hpp create mode 100644 include/boost/sqlite/allocator.hpp create mode 100644 include/boost/sqlite/backup.hpp create mode 100644 include/boost/sqlite/blob.hpp create mode 100644 include/boost/sqlite/collation.hpp create mode 100644 include/boost/sqlite/connection.hpp create mode 100644 include/boost/sqlite/cstring_ref.hpp create mode 100644 include/boost/sqlite/detail/aggregate_function.hpp create mode 100644 include/boost/sqlite/detail/catch.hpp create mode 100644 include/boost/sqlite/detail/config.hpp create mode 100644 include/boost/sqlite/detail/exception.hpp create mode 100644 include/boost/sqlite/detail/scalar_function.hpp create mode 100644 include/boost/sqlite/detail/vtable.hpp create mode 100644 include/boost/sqlite/detail/window_function.hpp create mode 100644 include/boost/sqlite/error.hpp create mode 100644 include/boost/sqlite/extension.hpp create mode 100644 include/boost/sqlite/field.hpp create mode 100644 include/boost/sqlite/function.hpp create mode 100644 include/boost/sqlite/hooks.hpp create mode 100644 include/boost/sqlite/json.hpp create mode 100644 include/boost/sqlite/memory.hpp create mode 100644 include/boost/sqlite/meta_data.hpp create mode 100644 include/boost/sqlite/mutex.hpp create mode 100644 include/boost/sqlite/result.hpp create mode 100644 include/boost/sqlite/resultset.hpp create mode 100644 include/boost/sqlite/row.hpp create mode 100644 include/boost/sqlite/statement.hpp create mode 100644 include/boost/sqlite/static_resultset.hpp create mode 100644 include/boost/sqlite/string.hpp create mode 100644 include/boost/sqlite/transaction.hpp create mode 100644 include/boost/sqlite/value.hpp create mode 100644 include/boost/sqlite/vtable.hpp create mode 100644 readme.md create mode 100644 src/backup.cpp create mode 100644 src/blob.cpp create mode 100644 src/connection.cpp create mode 100644 src/detail/exception.cpp create mode 100644 src/error.cpp create mode 100644 src/ext.cpp create mode 100644 src/field.cpp create mode 100644 src/meta_data.cpp create mode 100644 src/resultset.cpp create mode 100644 src/row.cpp create mode 100644 src/value.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/Jamfile create mode 100644 test/allocator.cpp create mode 100644 test/backup.cpp create mode 100644 test/blob.cpp create mode 100644 test/catch.cpp create mode 100644 test/collation.cpp create mode 100644 test/connection.cpp create mode 100644 test/extension/CMakeLists.txt create mode 100644 test/extension/simple_scalar.cpp create mode 100644 test/extension/simple_scalar.sql create mode 100644 test/field.cpp create mode 100644 test/function.cpp create mode 100644 test/hooks.cpp create mode 100644 test/json.cpp create mode 100644 test/main_test.cpp create mode 100644 test/meta_data.cpp create mode 100644 test/mutex.cpp create mode 100644 test/statement.cpp create mode 100644 test/static_resultset.cpp create mode 100644 test/test-db.sql create mode 100644 test/test.hpp create mode 100644 test/transaction.cpp create mode 100644 test/vtable.cpp create mode 100755 tools/get-boost.sh create mode 100644 tools/user-config.jam diff --git a/.drone.star b/.drone.star new file mode 100644 index 0000000..174896e --- /dev/null +++ b/.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": "-stdlib=libc++ -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/.drone/drone.bat b/.drone/drone.bat new file mode 100755 index 0000000..8aea39c --- /dev/null +++ b/.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/.drone/drone.sh b/.drone/drone.sh new file mode 100755 index 0000000..c97a210 --- /dev/null +++ b/.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 < 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/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..34a7463 --- /dev/null +++ b/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/build/Jamfile b/build/Jamfile new file mode 100644 index 0000000..3fae7e4 --- /dev/null +++ b/build/Jamfile @@ -0,0 +1,36 @@ + +project boost/sqlite + : requirements + BOOST_FILESYSTEM_NO_DEPRECATED=1 + BOOST_SQLITE_SEPARATE_COMPILATION=1 + shared:BOOST_SQLITE_DYN_LINK=1 + static:BOOST_SQLITE_STATIC_LINK=1 + BOOST_SQLITE_SOURCE=1 + : usage-requirements + shared:BOOST_SQLITE_DYN_LINK=1 + static: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 : BOOST_SQLITE_COMPILE_EXTENSION=1 ; +alias extension : boost_sqlite_ext ; + +boost-install boost_sqlite boost_sqlite_ext ; + diff --git a/doc/Jamfile b/doc/Jamfile new file mode 100644 index 0000000..9a9f9d3 --- /dev/null +++ b/doc/Jamfile @@ -0,0 +1,18 @@ +import asciidoctor ; + +html index.html : index.adoc ; + +install html_ : index.html : html ; + + +pdf sqlite.pdf : index.adoc ; +explicit sqlite.pdf ; + +install pdf_ : sqlite.pdf : pdf ; +explicit pdf_ ; + +alias boostdoc ; +explicit boostdoc ; +alias boostrelease : html_ ; +explicit boostrelease ; + diff --git a/doc/extensions.adoc b/doc/extensions.adoc new file mode 100644 index 0000000..528fb39 --- /dev/null +++ b/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 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/doc/functions.adoc b/doc/functions.adoc new file mode 100644 index 0000000..9b58b62 --- /dev/null +++ b/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` (see the https://www.sqlite.org/bindptr.html[pointer passing interface]) +- `variant2::variant` if all `Ts` are supported +- `result` +- `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/doc/index.adoc b/doc/index.adoc new file mode 100644 index 0000000..1ec5ec7 --- /dev/null +++ b/doc/index.adoc @@ -0,0 +1,28 @@ += Documentation boost.sqlite +Klemens Morgenstern +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/doc/reference.adoc b/doc/reference.adoc new file mode 100644 index 0000000..17de91c --- /dev/null +++ b/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/doc/reference/allocator.adoc b/doc/reference/allocator.adoc new file mode 100644 index 0000000..ab9a4e9 --- /dev/null +++ b/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 +struct allocator +{ + constexpr allocator() noexcept {} + constexpr allocator( const allocator& other ) noexcept {} + template< class U > + constexpr allocator( const allocator& 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/doc/reference/backup.adoc b/doc/reference/backup.adoc new file mode 100644 index 0000000..bc74619 --- /dev/null +++ b/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/doc/reference/blob.adoc b/doc/reference/blob.adoc new file mode 100644 index 0000000..8f73105 --- /dev/null +++ b/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 + 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 <> or as a parameter for a <>. + +.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/doc/reference/collation.adoc b/doc/reference/collation.adoc new file mode 100644 index 0000000..6578f59 --- /dev/null +++ b/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 +void create_collation(connection & conn, cstring_ref name, Func && func); +template +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/doc/reference/connection.adoc b/doc/reference/connection.adoc new file mode 100644 index 0000000..6d62c10 --- /dev/null +++ b/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 + 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 + void connect(const Path & pth, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + template + 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 + static_resultset query(core::string_view q, system::error_code & ec, error_info & ei); + template + static_resultset 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 <>. +<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/doc/reference/cstring_ref.adoc b/doc/reference/cstring_ref.adoc new file mode 100644 index 0000000..402663d --- /dev/null +++ b/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/doc/reference/error.adoc b/doc/reference/error.adoc new file mode 100644 index 0000000..424c7dd --- /dev/null +++ b/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 +using result = system::result; +---- +<1> If code.category() is not `sqlite_category`, the code will be set to `SQLITE_FAIL`. + + diff --git a/doc/reference/extension.adoc b/doc/reference/extension.adoc new file mode 100644 index 0000000..7ee2500 --- /dev/null +++ b/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 sp) + { + if (sp.front().get_int() == 0) + throw std::logic_error("assertion failed"); + }); +} +---- + diff --git a/doc/reference/field.adoc b/doc/reference/field.adoc new file mode 100644 index 0000000..572719d --- /dev/null +++ b/doc/reference/field.adoc @@ -0,0 +1,42 @@ +== `sqlite/field.hpp` + +A `field` is a type representing a <> 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/doc/reference/function.adoc b/doc/reference/function.adoc new file mode 100644 index 0000000..311aebc --- /dev/null +++ b/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 +struct context +{ + template + using element = mp11::mp_take_c, N>; + + // Set the value in the context at position `Idx` + template + void set(element value); + + // Returns the value in the context at position `Idx`. Throws if the value isn't set. + template + auto get() -> element &; + + // Returns the value in the context at position `Idx`. Returns nullptr .value isn't set. + template + auto get_if() -> element *; + + explicit context(sqlite3_context * ctx) noexcept; + + // Set the result through the context, instead of returning it. + template + 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 ctx, + boost::span 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 +auto create_scalar_function( + connection & conn, + cstring_ref name, + Func && func, + function_flags flags = {}); + +template +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` as the first and a `span` 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 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> +void create_aggregate_function( +connection & conn, +cstring_ref name, +Args && args= {}, +function_flags flags = {}); + +template> +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 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 val) + { + counter += val[0].get_text().size(); + } + + std::int64_t final() + { + return counter; + } +}; + +sqlite::create_function(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> +void create_window_function( + connection & conn, + cstring_ref name, + Args && args = {}, + function_flags flags = {}); + +template> +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 args); +void inverse(boost::span args); +T final(); +---- + + +.Example +[source,cpp] +---- +extern sqlite::connection conn; + +struct window_func +{ + std::int64_t counter; + void step(boost::span val) + { + counter += val[0].get_text().size(); + } + void inverse(boost::span 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/doc/reference/hooks.adoc b/doc/reference/hooks.adoc new file mode 100644 index 0000000..c74d712 --- /dev/null +++ b/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 +bool commit_hook(connection & conn, Func && func); + +template +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 +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 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 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 +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/doc/reference/json.adoc b/doc/reference/json.adoc new file mode 100644 index 0000000..1c15f52 --- /dev/null +++ b/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('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/doc/reference/memory.adoc b/doc/reference/memory.adoc new file mode 100644 index 0000000..7bf62ef --- /dev/null +++ b/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 +using unique_ptr = std::unique_ptr; +template +unique_ptr make_unique(Args && ... args); + +// Get the size of the allocated memory. +template +std::size_t msize(const unique_ptr & ptr); + + +--- +<1> `new (sqlite::memory_tag{}) T()` uses sqlite3_malloc + + diff --git a/doc/reference/meta_data.adoc b/doc/reference/meta_data.adoc new file mode 100644 index 0000000..6375108 --- /dev/null +++ b/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/doc/reference/mutex.adoc b/doc/reference/mutex.adoc new file mode 100644 index 0000000..d0493d6 --- /dev/null +++ b/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/doc/reference/result.adoc b/doc/reference/result.adoc new file mode 100644 index 0000000..0471b95 --- /dev/null +++ b/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 +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 +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, const variant2::variant & var); + +template +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr ptr); +template +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr ptr); +template +inline auto tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr ptr); +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, error err); +template +inline void tag_invoke(set_result_tag tag, sqlite3_context * ctx, result res); +inline void tag_invoke(set_result_tag tag, sqlite3_context * ctx, result res): +---- + + diff --git a/doc/reference/resultset.adoc b/doc/reference/resultset.adoc new file mode 100644 index 0000000..fb793a5 --- /dev/null +++ b/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/doc/reference/row.adoc b/doc/reference/row.adoc new file mode 100644 index 0000000..d3af982 --- /dev/null +++ b/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/doc/reference/statement.adoc b/doc/reference/statement.adoc new file mode 100644 index 0000000..6ce89e4 --- /dev/null +++ b/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 + param_ref(I value); + // Bind a blob. + param_ref(blob_view blob); + // Bind a string. + param_ref(string_view text); + + template + param_ref(StringLike && text); + template + 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 + param_ref(std::unique_ptr ptr); + + + + // Apply the param_ref to a statement. + int apply(sqlite3_stmt * stmt, int c) const; + + // Construct param_ref from a variant + template + 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 > + resultset execute(ArgRange && params, system::error_code& ec, error_info& info) &&; // <1> + template + resultset execute(ArgRange && params) &&; // <1> + resultset execute( + std::initializer_list> params, + system::error_code& ec, + error_info& info) &&; // <2> + + resultset execute(std::initializer_list> params) &&; + template> + static_resultset execute( + ArgRange && params, + system::error_code & ec, + error_info & ei) &&; // <1> + + template> + static_resultset execute(ArgRange && params) &&; // <1> + + template + static_resultset execute( + std::initializer_list> params, + system::error_code & ec, + error_info & ei) &&; // <2> + template + static_resultset execute(std::initializer_list> params) &&; // <2> + + + template > + resultset execute( + ArgRange && params, + system::error_code& ec, + error_info& info) &; // <1> + + + template > + resultset execute(ArgRange && params) &; // <1> + + + resultset execute( + std::initializer_list> params, + system::error_code& ec, + error_info& info) &; // <2> + + resultset execute(std::initializer_list> params) &; // <2> + + template> + static_resultset execute( + ArgRange && params, + system::error_code & ec, + error_info & ei) &; // <1> + + template> + static_resultset execute(ArgRange && params) &; // <1> + + template + static_resultset execute( + std::initializer_list> params, + system::error_code & ec, + error_info & ei) &; // <2> + template + static_resultset execute(std::initializer_list> 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/doc/reference/static_resultset.adoc b/doc/reference/static_resultset.adoc new file mode 100644 index 0000000..0e15e25 --- /dev/null +++ b/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 +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 && 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 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/doc/reference/string.adoc b/doc/reference/string.adoc new file mode 100644 index 0000000..21660f9 --- /dev/null +++ b/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/doc/reference/transaction.adoc b/doc/reference/transaction.adoc new file mode 100644 index 0000000..b6bc449 --- /dev/null +++ b/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/doc/reference/value.adoc b/doc/reference/value.adoc new file mode 100644 index 0000000..f5cb3de --- /dev/null +++ b/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 + T * get_pointer(); + +}; +---- + diff --git a/doc/reference/vtable.adoc b/doc/reference/vtable.adoc new file mode 100644 index 0000000..5063ffc --- /dev/null +++ b/doc/reference/vtable.adoc @@ -0,0 +1,275 @@ +== `sqlite/vtable.hpp` + +Please read the <> 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` + * for scalar functions, + * or a `context` as first, and the span as second for aggegrate functions. + * + */ + template + void set(Func & func); + template + void set(void(* ptr)(context, span)) noexcept; + template + void set(T(* ptr)(context, span)); + + template + void set(void(* ptr)(span)); + template + void set(T(* ptr)(span)); +}; + + +// 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 constraints() const; + + // Returns ordering of the index. + span order_by() const; + + span 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 +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 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 connect(sqlite::connection db, int argc, const char * const argv[]) = 0; +}; + +template +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 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 +struct table : protected sqlite3_vtab +{ + using cursor_type = Cursor; + + virtual result 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 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 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 open() = 0; + + // Get the connection of the vtable + sqlite::connection connection() const; + + table(const sqlite::connection & conn); +}; + + +// Cursor needs the following member. +template +struct cursor : protected sqlite3_vtab_cursor +{ + using column_type = ColumnType; + + // Apply a filter to the cursor. Required when best_index is implemented. + virtual result filter( + int /*index*/, const char * /*index_data*/, + boost::span /*values*/) + { + return {system::in_place_error, SQLITE_OK}; + } + + // Returns the next row. + virtual result 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(int idx, bool no_change) = 0; + // Returns the id of the current row + virtual result row_id() = 0; + + // Get the table the cursor is pointing to. + vtab::table & table(); + const vtab::table & table() const; +}; + + +// Group of functions for modifications. The table must inherit this to be modifiable. +struct modifiable +{ + virtual result delete_(sqlite::value key) = 0; + // Insert a new row + virtual result insert(sqlite::value key, span values, int on_conflict) = 0; + // Update the row + virtual result update(sqlite::value old_key, sqlite::value new_key, span 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 begin() = 0; + // synchronize the state + virtual result sync() = 0; + // commit the transaction + virtual result commit() = 0; + // rollback the transaction + virtual result rollback() = 0; +}; + +// Base class to enable function overriding See `xFindFunction`. +struct overload_functions +{ + virtual result 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 savepoint(int i) = 0; + // Release all saves states down to `i` + virtual result release(int i) = 0; + // Roll the transaction back to `i`. + virtual result 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 +auto create_module(connection & conn, + cstring_ref name, + T && module, + system::error_code & ec, + error_info & ei) -> typename std::decay::type &; +template +auto create_module(connection & conn, + const char * name, + T && module) -> typename std::decay::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/doc/tutorial.adoc b/doc/tutorial.adoc new file mode 100644 index 0000000..4ff91ff --- /dev/null +++ b/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 <> + +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 <> 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 <> 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 <> type is a range of <>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`) which will yield a +`static_resultset`. `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` +- <>, <> +- <>, <> +- `std::optional`/`boost::optional` 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 by using `.execute`. + diff --git a/doc/vtable.adoc b/doc/vtable.adoc new file mode 100644 index 0000000..d759de1 --- /dev/null +++ b/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 <>. +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 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 +{ + // create the csv file + sqlite::result create(sqlite::connection /*db*/, + int argc, const char * const argv[]); + + // connect to an existing csv file. + sqlite::result 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/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..480ed7e --- /dev/null +++ b/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/example/csv.cpp b/example/csv.cpp new file mode 100644 index 0000000..47fe288 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#include +#include +#include + +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; + row_type names; + container::flat_map 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::const_iterator; + +struct csv_cursor final : sqlite::vtab::cursor +{ + iterator_type itr, end; + + csv_cursor(iterator_type itr, + iterator_type end) : itr(itr), end(end) + {} + + sqlite::result next() {itr++; return {};} + sqlite::result row_id() + { + return itr->first; + } + + sqlite::result column(int i, bool /* no_change */) + { + return itr->second.at(i); + } + + bool eof() noexcept {return itr == end;} +}; + +struct csv_table final + : sqlite::vtab::table, + 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 open() + { + return cursor_type{data.rows.cbegin(), data.rows.cend()}; + } + + sqlite::result delete_(sqlite::value key) + { + data.rows.erase(key.get_int()); + return {}; + } + sqlite::result insert( + sqlite::value /*key*/, span 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 update( + sqlite::value update, sqlite::value new_key, + span 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 begin() noexcept {transaction_copy = data; return {};} + sqlite::result sync() noexcept {return {};} + sqlite::result commit() noexcept + { + // ok, let's write to disk. + //fs.(0); + std::ofstream fs{path, std::fstream::trunc}; + fs << data << std::flush; + return {}; + } + sqlite::result rollback() noexcept + { + data = std::move(transaction_copy); + return {}; + } + + sqlite::result 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 +{ + + sqlite::result 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 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/example/describe.cpp b/example/describe.cpp new file mode 100644 index 0000000..9b00f8b --- /dev/null +++ b/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 +#include +#include +#include + +#include +#include + +// 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 +struct describe_cursor final : sqlite::vtab::cursor<> +{ + describe_cursor( + typename boost::unordered_map::const_iterator itr, + typename boost::unordered_map::const_iterator end + ) : itr(itr), end(end) + { + } + + typename boost::unordered_map::const_iterator itr, end; + constexpr static std::size_t column_count = mp11::mp_size>::value; + + sqlite::result next() {itr++; return {};} + sqlite::result row_id() {return itr->first;} + + void column(sqlite::context<> ctx, int i, bool /* no_change */) + { + mp11::mp_with_index( + i, [&](auto Idx) + { + using type = mp11::mp_at_c, Idx>; + ctx.set_result(itr->second.*type::pointer); + }); + } + + bool eof() noexcept {return itr == end;} +}; + +template +struct describe_table final : + sqlite::vtab::table>, + sqlite::vtab::modifiable +{ + boost::unordered_map &data; + describe_table(boost::unordered_map &data) : data(data) {} + + std::string decl; + const char * declaration() + { + if (!decl.empty()) + return decl.c_str(); + mp11::mp_for_each>( + [&](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> open() + { + return describe_cursor{data.begin(), data.end()}; + } + + sqlite::result delete_(sqlite::value key) + { + data.erase(key.get_int()); + return {}; + } + sqlite::result insert(sqlite::value key, span 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>( + [&](auto elem) + { + assign_value(res.*elem.pointer , *vtr); + vtr++; + }); + + auto itr = data.emplace(id, std::move(res)).first; + return itr->first; + + } + sqlite::result update(sqlite::value old_key, sqlite::value new_key, + span 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>( + [&](auto elem) + { + assign_value(res.*elem.pointer , *vtr); + vtr++; + }); + return 0u; + } +}; + +template +struct describe_module final : sqlite::vtab::eponymous_module> +{ + boost::unordered_map data; + constexpr static std::size_t column_count = mp11::mp_size>::value; + sqlite::result> connect(sqlite::connection , + int, const char * const []) + { + return describe_table{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()); + + { + 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/example/multi_index.cpp b/example/multi_index.cpp new file mode 100644 index 0000000..0ce4a84 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include + +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::ordered_non_unique> + > +>; + + +struct multi_index_cursor final + : sqlite::vtab::cursor +{ + 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 next() + { + switch (index) + { + case 0: inverse ? end-- : begin++; break; + case 1: inverse ? end1-- : begin1++; break; + case 2: inverse ? end2-- : begin2++; break; + } + return {}; + } + + sqlite::result row_id() + { + static_assert(sizeof(const_iterator) <= sizeof(sqlite3_int64), ""); + + switch (index) + { + case 0: return reinterpret_cast(inverse ? &*std::prev(end ) : &*begin); + case 1: return reinterpret_cast(inverse ? &*std::prev(end1) : &*begin1); + case 2: return reinterpret_cast(inverse ? &*std::prev(end2) : &*begin2); + } + + return sqlite3_int64(); + } + sqlite::result 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 filter(int idx, const char * idxStr, span values) + { + inverse = (idx == 0b1000) & idx; + index = idx & 0b11; + + boost::optional 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, + 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 open() override + { + return cursor_type(data); + } + + sqlite::result delete_(sqlite::value key) override + { + data.erase(key.get_text()); + return {}; + } + sqlite::result insert(sqlite::value /*key*/, span values, + int /*on_conflict*/) override + { + data.insert({values[0].get_text(), values[1].get_text()}); + return 0; + } + + sqlite::result update(sqlite::value old_key, sqlite::value new_key, + span values, int /*on_conflict*/) override + { + if (new_key.get_int() != old_key.get_int()) + { + + auto node = reinterpret_cast(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 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 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 +{ + sqlite::result connect(sqlite::connection, int, const char * const *) + { + return map_impl{}; + } +}; + + +std::initializer_list> 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/example/ordered_map.cpp b/example/ordered_map.cpp new file mode 100644 index 0000000..26a285b --- /dev/null +++ b/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 +#include +#include + +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 // <1> +{ + container::flat_map &data; + ordered_map_cursor(container::flat_map &data) : data(data) {} + bool inverse = false; + + using const_iterator = typename container::flat_map::const_iterator; + const_iterator begin{data.begin()}, end{data.end()}; // <2> + + sqlite::result next() override { if (inverse) end--; else begin++; return {};} // <3> + + sqlite::result row_id() override + { + return {system::in_place_error, SQLITE_MISUSE, // <4> + "this shouldn't be called, we're omitting the row id"}; + } + sqlite::result 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 filter(int idx, const char * idxStr, span 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, + sqlite::vtab::modifiable // <1> + +{ + container::flat_map &data; + map_impl(container::flat_map &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 open() override // <3> + { + return cursor_type{data}; + } + + sqlite::result delete_(sqlite::value key) override // <4> + { + data.erase(key.get_text()); + return {}; + } + sqlite::result insert(sqlite::value /*key*/, span values, + int /*on_conflict*/) override // <5> + { + data.emplace(values[0].get_text(), values[1].get_text()); + return 0; + } + + sqlite::result update(sqlite::value old_key, sqlite::value new_key, + span 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 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 str; // <1> + if (info.constraints().size() > 0) + { + const auto sz = info.constraints().size()+1; + str.reset(static_cast(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 +{ + container::flat_map data; + template + ordered_map_module(Args && ...args) : data(std::forward(args)...) {} + + sqlite::result connect( + sqlite::connection /*conn*/, int /*argc*/, const char * const */*argv*/) + { + return map_impl{data}; + } +}; +// end::module[] + + + +std::initializer_list> 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/example/tutorial.cpp b/example/tutorial.cpp new file mode 100644 index 0000000..fbfa4a3 --- /dev/null +++ b/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 +#include + +#include + +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>("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("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("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 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 args) + { + if (args[0].get_int() >= retirement_age) + count += 1; + } + std::int64_t final() { return count; } + }; + sqlite::create_aggregate_function(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/example/tutorial_ec.cpp b/example/tutorial_ec.cpp new file mode 100644 index 0000000..2c6af7f --- /dev/null +++ b/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 +#include + +#include + +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>("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("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("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 val // <2> + ) -> sqlite::result + { + 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 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(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/example/url.cpp b/example/url.cpp new file mode 100644 index 0000000..f5ed4f9 --- /dev/null +++ b/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 +#include +#include +#include + +using namespace boost; + +// tag::subtype[] +constexpr int pct_subtype = static_cast('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 + > +{ + url_cursor(urls::url_view view) : view(view ) {} + + urls::url_view view; + bool done{false}; + + sqlite::result next() { done = true; return {};} + + sqlite::result row_id() {return static_cast(0);} + sqlite::result 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 filter(int /*idx*/, const char * /*idxStr*/, span 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 +{ + 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 open() override + { + return url_cursor{value}; + } + + sqlite::result 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 +{ + sqlite::result connect(sqlite::connection /*db*/, + int /*argc*/, const char * const */*argv*/) + { + return url_wrapper{}; + } +}; + +struct segements_cursor final : sqlite::vtab::cursor< + variant2::variant> +{ + 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 next() override { itr++; return {};} + + sqlite::result row_id() override {return std::distance(view.begin(), itr);} + sqlite::result 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 filter(int /*idx*/, const char * /*idxStr*/, + span 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 +{ + urls::segments_encoded_view value; + const char * declaration() override + { + return R"( + create table segments( + idx integer, + segment text, + segments text hidden);)"; + } + + + + sqlite::result open() override + { + return segements_cursor{value}; + } + + sqlite::result 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 +{ + sqlite::result 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 + > +{ + urls::params_encoded_view view; + urls::params_encoded_view::const_iterator itr{view.begin()}; + + sqlite::result next() override { itr++; return {};} + + sqlite::result row_id() override {return std::distance(view.begin(), itr);} + sqlite::result 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 filter(int /*idx*/, const char * /*idxStr*/, + span 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 +{ + const char * declaration() override + { + return R"( + create table queries( + idx integer, + name text, + value text, + query_string text hidden);)"; // <1> + } + + sqlite::result open() override + { + return query_cursor{}; + } + + sqlite::result 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 +{ + sqlite::result 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 s) + { + return urls::pct_string_view(s[0].get_text()).decode(); + }); + + sqlite::create_scalar_function( + conn, "pct_encode", + +[](boost::sqlite::context<> , boost::span s) + { + return urls::encode(s[0].get_text(), urls::pchars); + }); +} \ No newline at end of file diff --git a/example/url.sql b/example/url.sql new file mode 100644 index 0000000..813917e --- /dev/null +++ b/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/include/boost/sqlite.hpp b/include/boost/sqlite.hpp new file mode 100644 index 0000000..474102a --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_SQLITE_COMPILE_EXTENSION) +#include +#endif + +#endif //BOOST_SQLITE_HPP diff --git a/include/boost/sqlite/allocator.hpp b/include/boost/sqlite/allocator.hpp new file mode 100644 index 0000000..f449475 --- /dev/null +++ b/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 + +#include +#include + +BOOST_SQLITE_BEGIN_NAMESPACE + +template +struct allocator +{ + constexpr allocator() noexcept {} + constexpr allocator( const allocator& other ) noexcept {} + template< class U > + constexpr allocator( const allocator& 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(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/include/boost/sqlite/backup.hpp b/include/boost/sqlite/backup.hpp new file mode 100644 index 0000000..737a28e --- /dev/null +++ b/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 +#include +#include + +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/include/boost/sqlite/blob.hpp b/include/boost/sqlite/blob.hpp new file mode 100644 index 0000000..cd82363 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +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 + explicit blob_view(const T & value, + typename std::enable_if< + std::is_pointer().data())>::value + && std::is_convertible().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(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(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 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(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 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/include/boost/sqlite/collation.hpp b/include/boost/sqlite/collation.hpp new file mode 100644 index 0000000..a4624c4 --- /dev/null +++ b/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 +#include + +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 +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::type; + unique_ptr f{new (memory_tag{}) func_type(std::forward(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(str_l), len_l); + string_view r(static_cast(str_r), len_r); + auto & impl = (*static_cast(data)); + static_assert(noexcept(impl(l, r)), + "Collation function must be noexcept"); + return impl(l, r); + }, + +[](void * p) { delete_(static_cast(p)); } + ); + + if (res != SQLITE_OK) + BOOST_SQLITE_ASSIGN_EC(ec, res); + else + f.release(); +} + + +template +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), 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/include/boost/sqlite/connection.hpp b/include/boost/sqlite/connection.hpp new file mode 100644 index 0000000..0f6ccf8 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +BOOST_SQLITE_BEGIN_NAMESPACE + +template +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::value && + std::is_constructible().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::value && + std::is_constructible().string())>::value + >> + void connect(const Path & pth, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) + { + connect(pth.string(), flags); + } + + + template::value && + std::is_constructible().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 + static_resultset query( + core::string_view q, + system::error_code & ec, + error_info & ei) + { + static_resultset tmp = query(q, ec, ei); + if (ec) + return {}; + tmp.check_columns_(ec, ei); + if (ec) + return {}; + + return tmp; + } + + template + static_resultset 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; + } + ///@} + + ///@{ + /// 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().c_str())> + void execute( + const StringLike& q, + system::error_code & ec, + error_info & ei) + { + execute(q.c_str(), ec, ei); + } + template().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 impl_{nullptr, deleter_{}}; +}; + +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_CONNECTION_HPP diff --git a/include/boost/sqlite/cstring_ref.hpp b/include/boost/sqlite/cstring_ref.hpp new file mode 100644 index 0000000..6747f45 --- /dev/null +++ b/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 +#include + +#include +#include +#include + + +#if __cplusplus >= 201702L +#include +#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; + 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().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; + using reverse_iterator = typename std::reverse_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(-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 to_string() const + { + return std::basic_string(begin(), end()); + } + + template + std::basic_string to_string(const Allocator& a) const + { + return std::basic_string(begin(), end(), a); + } + + template operator std::basic_string, A>() const + { + return std::basic_string, 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 +inline std::basic_ostream& operator<<( std::basic_ostream & os, cstring_ref str ) +{ + return os << core::string_view(str); +} + + +} +} + + +#endif //BOOST_SQLITE_CSTRING_REF_HPP diff --git a/include/boost/sqlite/detail/aggregate_function.hpp b/include/boost/sqlite/detail/aggregate_function.hpp new file mode 100644 index 0000000..de0c447 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +BOOST_SQLITE_BEGIN_NAMESPACE + +namespace detail +{ + +template +struct aggregate_function_maker +{ + void * mem; + + template + Func* operator()(Args && ... args) + { + return new (mem) Func(std::forward(args)...); + } +}; + +template +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; + using span_type = typename std::tuple_element<1U, args_type>::type; + using func_type = typename std::decay::type; + using func_args_type = typename std::decay::type; + + return sqlite3_create_function_v2( + db, name.c_str(), + span_type::extent == boost::dynamic_extent ? -1 : static_cast(span_type::extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_args_type(std::forward(args)), + nullptr, + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto aa = reinterpret_cast(args); + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> result + { + 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{p}, *fa); + } + c->step(span_type{aa, static_cast(len)}); + return {}; + }); + }, + [](sqlite3_context* ctx) + { + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> resultfinal())> + { + 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{p}, *fa); + } + struct reaper {void operator()(func_type * c) { c->~func_type();}}; + std::unique_ptr cl{c}; + return c->final(); + }); + }, + [](void * ptr) noexcept { delete_(static_cast(ptr));} + ); +} + + +template +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; + using span_type = typename std::tuple_element<1U, args_type>::type; + using func_type = typename std::decay::type; + using func_args_type = typename std::decay::type; + + return sqlite3_create_function_v2( + db, name.c_str(), + span_type::extent == boost::dynamic_extent ? -1 : static_cast(span_type::extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_args_type(std::forward(args)), + nullptr, + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto aa = reinterpret_cast(args); + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> result + { + 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{p}, *fa); + } + return c->step(span_type{aa, static_cast(len)}); + }); + }, + [](sqlite3_context* ctx) + { + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> resultfinal())> + { + 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{p}, *fa); + } + struct reaper {void operator()(func_type * c) { c->~func_type();}}; + std::unique_ptr cl{c}; + return c->final(); + }); + }, + [](void * ptr) noexcept { delete_(static_cast(ptr));} + ); +} + +} + +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_DETAIL_AGGREGATE_FUNCTION_HPP diff --git a/include/boost/sqlite/detail/catch.hpp b/include/boost/sqlite/detail/catch.hpp new file mode 100644 index 0000000..e5b053f --- /dev/null +++ b/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 +#include +#include +#include + +#include + +#include + + +BOOST_SQLITE_BEGIN_NAMESPACE +namespace detail +{ + +template +void execute_context_function_impl(std::false_type /* is_void */, + sqlite3_context * ctx, + Func && func, Args && ... args) + noexcept(noexcept(std::forward(func)(std::forward(args)...))) +{ + set_result(ctx, std::forward(func)(std::forward(args)...)); +} + +template +void execute_context_function_impl(std::true_type /* is_void */, + sqlite3_context * ctx, + Func && func, Args && ... args) + noexcept(noexcept(std::forward(func)(std::forward(args)...))) +{ + std::forward(func)(std::forward(args)...); +} + +template +int extract_error(char * &errMsg, result & res) +{ + error err = std::move(res).error(); + if (err.info) + errMsg = err.info.release(); + return err.code; +} + + +template +void execute_context_function(sqlite3_context * ctx, + Func && func, Args && ... args) noexcept +{ + using return_type = decltype(func(std::forward(args)...)); +#if !defined(BOOST_NO_EXCEPTIONS) + try + { +#endif + execute_context_function_impl(std::is_void{}, ctx, + std::forward(func), + std::forward(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/include/boost/sqlite/detail/config.hpp b/include/boost/sqlite/detail/config.hpp new file mode 100644 index 0000000..2126f60 --- /dev/null +++ b/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 +#include + +#if defined(BOOST_SQLITE_COMPILE_EXTENSION) +#include +#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 +#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 +# 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/include/boost/sqlite/detail/exception.hpp b/include/boost/sqlite/detail/exception.hpp new file mode 100644 index 0000000..9f29cbe --- /dev/null +++ b/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 +#include + +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/include/boost/sqlite/detail/scalar_function.hpp b/include/boost/sqlite/detail/scalar_function.hpp new file mode 100644 index 0000000..ee16a23 --- /dev/null +++ b/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 +#include +#include +#include +#include + +#include +#include +#include +#include + + +BOOST_SQLITE_BEGIN_NAMESPACE + +template +struct context; + +namespace detail +{ + +template +auto create_scalar_function_impl(sqlite3 * db, + cstring_ref name, + Func && func, + int flags, + std::tuple, boost::span> * , + std::false_type /* void return */, + std::false_type /* is pointer */) -> int +{ + using func_type = typename std::decay::type; + return sqlite3_create_function_v2( + db, name.c_str(), + Extent == boost::dynamic_extent ? -1 : static_cast(Extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_type(std::forward(func)), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto cc = context(ctx); + auto aa = reinterpret_cast(args); + auto &f = *reinterpret_cast(sqlite3_user_data(ctx)); + boost::span vals{aa, static_cast(len)}; + + execute_context_function(ctx, f, cc, vals); + }, nullptr, nullptr, + +[](void * ptr) noexcept {delete_(static_cast(ptr));} + ); +} + +template +auto create_scalar_function_impl(sqlite3 * db, + cstring_ref name, + Func && func, int flags, + std::tuple, boost::span> * , + std::true_type /* void return */, + std::false_type /* is pointer */) -> int +{ + using func_type = typename std::decay::type; + return sqlite3_create_function_v2( + db, + name.c_str(), + (Extent == boost::dynamic_extent) ? -1 : static_cast(Extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_type(std::forward(func)), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto cc = context(ctx); + auto aa = reinterpret_cast(args); + auto &f = *reinterpret_cast(sqlite3_user_data(ctx)); + boost::span vals{aa, static_cast(len)}; + + execute_context_function( + ctx, + [&]() + { + f(cc, vals); + return result(); + }); + + }, nullptr, nullptr, + +[](void * ptr){delete_(static_cast(ptr));} + ); +} + + +template +auto create_scalar_function_impl(sqlite3 * db, + cstring_ref name, + Func * func, int flags, + std::tuple, boost::span> * , + 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(Extent), + SQLITE_UTF8 | flags, + reinterpret_cast(func), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto cc = context(ctx); + auto aa = reinterpret_cast(args); + auto f = reinterpret_cast(sqlite3_user_data(ctx)); + + boost::span vals{aa, static_cast(len)}; + + execute_context_function( + ctx, f, cc, vals); + }, nullptr, nullptr, nullptr); +} + +template +auto create_scalar_function_impl(sqlite3 * db, + cstring_ref name, + Func * func, int flags, + std::tuple, boost::span> * , + 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(Extent), + SQLITE_UTF8 | flags, + reinterpret_cast(func), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto cc = context(ctx); + auto aa = reinterpret_cast(args); + auto f = *reinterpret_cast(sqlite3_user_data(ctx)); + boost::span vals{aa, static_cast(len)}; + execute_context_function( + ctx, + [&]() + { + f(cc, vals); + return result(); + }); + + }, nullptr, nullptr, nullptr); +} + +template +auto create_scalar_function(sqlite3 * db, + cstring_ref name, + Func && func, + int flags) + -> decltype(create_scalar_function_impl( + db, name, std::forward(func), flags, + static_cast*>(nullptr), + callable_traits::has_void_return{}, + std::is_pointer::type>{} + )) +{ + return create_scalar_function_impl(db, name, std::forward(func), flags, + static_cast*>(nullptr), + callable_traits::has_void_return{}, + std::is_pointer::type>{}); +} + + +} + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_DETAIL_SCALAR_FUNCTION_HPP diff --git a/include/boost/sqlite/detail/vtable.hpp b/include/boost/sqlite/detail/vtable.hpp new file mode 100644 index 0000000..f85f44c --- /dev/null +++ b/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 +#include +#include + +BOOST_SQLITE_BEGIN_NAMESPACE +namespace detail +{ +struct vtab_impl +{ + +template +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(pAux); + BOOST_SQLITE_TRY + { + result rtab = impl.connect( + sqlite::connection(db, false), + argc, argv); + + if (rtab.has_error()) + return extract_error(*errMsg, rtab); + + auto tab = make_unique(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 +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(pAux); + BOOST_SQLITE_TRY + { + result rtab = impl.create( + sqlite::connection(db, false), + argc, argv); + + if (rtab.has_error()) + return extract_error(*errMsg, rtab); + + auto tab = make_unique(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 +static int disconnect(sqlite3_vtab * tab) +{ + BOOST_SQLITE_TRY + { + auto tb = static_cast(tab); + tb->~Table(); + sqlite3_free(tb); + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(tab->zErrMsg); +} + +template +static int destroy(sqlite3_vtab * tab) +{ + BOOST_SQLITE_TRY + { + auto tb = static_cast(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 +static void assign_create(sqlite3_module & md, const Module &, + const sqlite::vtab::eponymous_module & base) +{ + md.xConnect = md.xCreate = &connect; + md.xDisconnect = md.xDestroy = &disconnect
; + if (base.eponymous_only()) + md.xCreate = nullptr; +} + +template +static void assign_create(sqlite3_module & md, const Module &, + const sqlite::vtab::module
&) +{ + md.xConnect = &connect; + md.xDisconnect = &disconnect
; + md.xCreate = &create; + md.xDestroy = &destroy
; +} + +template +static int open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) +{ + auto tab = static_cast
(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 +static int close(sqlite3_vtab_cursor * cursor) +{ + auto p = static_cast(cursor); + + BOOST_SQLITE_TRY + { + p->~Cursor(); + } + BOOST_SQLITE_CATCH_AND_RETURN(); + + sqlite3_free(p); + return SQLITE_OK; + +} + +template +static int best_index(sqlite3_vtab *pVTab, sqlite3_index_info* info) +{ + BOOST_SQLITE_TRY + { + auto tb = static_cast(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 +static int filter(sqlite3_vtab_cursor* pCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(pCursor); + + auto r = cr->filter(idxNum, idxStr, + boost::span{reinterpret_cast(argv), + static_cast(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 +static int next(sqlite3_vtab_cursor* pCursor) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(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 +static int eof(sqlite3_vtab_cursor* pCursor) +{ + return static_cast(pCursor)->eof() ? 1 : 0; +} + +template +static auto column(sqlite3_vtab_cursor* pCursor, + sqlite3_context * ctx, int idx) + -> typename std::enable_if::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(pCursor); + execute_context_function( + ctx, + [&]{ + return cr->column(idx, no_change); + }); + + return SQLITE_OK; +} + + +template +static auto column(sqlite3_vtab_cursor* pCursor, + sqlite3_context * ctx, int idx) + -> typename std::enable_if::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(pCursor); + BOOST_SQLITE_TRY + { + cr->column(context<>{ctx}, idx, no_change); + } + BOOST_SQLITE_CATCH_RESULT(ctx); + return SQLITE_OK; +} + +template +static int row_id(sqlite3_vtab_cursor* pCursor, sqlite3_int64 *pRowid) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(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 +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(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{reinterpret_cast(argv + 2), + static_cast(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{reinterpret_cast(argv + 2), + static_cast(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 +static void assign_update(sqlite3_module & md, const Module &, + std::true_type /* modifiable */) +{ + md.xUpdate = &update; +} + +template +static void assign_update(sqlite3_module & /*md*/, const Module &, + std::false_type /* modifiable */) +{ +} + +template +static int begin(sqlite3_vtab* pVTab) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(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 +static int sync(sqlite3_vtab* pVTab) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(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 +static int commit(sqlite3_vtab* pVTab) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(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 +static int rollback(sqlite3_vtab* pVTab) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(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 +static void assign_transaction(sqlite3_module & /*md*/, const Module &, + std::false_type /* modifiable */) +{ +} + +template +static void assign_transaction(sqlite3_module & md, const Module &, + std::true_type /* modifiable */) +{ + md.xBegin = &begin ; + md.xSync = &sync ; + md.xCommit = &commit ; + md.xRollback = &rollback; +} + +template +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(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 +static void assign_find_function(sqlite3_module & /*md*/, const Module &, + std::false_type /* overloadable */) +{ +} + +template +static void assign_find_function(sqlite3_module & md, const Module &, + std::true_type /* overloadable */) +{ + md.xFindFunction = &find_function; +} + +template +static int rename(sqlite3_vtab* pVTab, const char * name) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(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 +static void assign_rename(sqlite3_module & /*md*/, const Module &, + std::false_type /* renamable */) +{ +} + +template +static void assign_rename(sqlite3_module & md, const Module &, + std::true_type /* renamable */) +{ + md.xRename = &rename; +} +#if SQLITE_VERSION_NUMBER >= 3007007 + +template +static int savepoint(sqlite3_vtab* pVTab, int i) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(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 +static int release(sqlite3_vtab* pVTab, int i) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(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 +static int rollback_to(sqlite3_vtab* pVTab, int i) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast(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 +static void assign_recursive_transaction(sqlite3_module & /*md*/, const Module &, + std::false_type /* recursive_transaction */) +{ +} + +template +static void assign_recursive_transaction(sqlite3_module & md, const Module &, + std::true_type /* recursive_transaction */) +{ + md.xSavepoint = &savepoint ; + md.xRelease = &release ; + md.xRollbackTo = &rollback_to; +} + +#endif + +#if SQLITE_VERSION_NUMBER >= 3026000 + +template +static void assign_shadow_name(sqlite3_module & /*md*/, const sqlite::vtab::module
&) {} + +template +static void assign_shadow_name(sqlite3_module & /*md*/, const sqlite::vtab::eponymous_module
&) {} + +template +static void assign_shadow_name(sqlite3_module & md, const Module & mod) +{ + md.xShadowName = +[](const char * name){return Func(name) != 0;}; +} + +#endif + +}; + +template +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; + md.xOpen = &vtab_impl::open ; + md.xClose = &vtab_impl::close ; + md.xFilter = &vtab_impl::filter ; + md.xNext = &vtab_impl::next ; + md.xEof = &vtab_impl::eof ; + md.xColumn = &vtab_impl::column ; + md.xRowid = &vtab_impl::row_id ; + vtab_impl::assign_update (md, mod, std::is_base_of{}); + vtab_impl::assign_transaction (md, mod, std::is_base_of{}); + vtab_impl::assign_find_function(md, mod, std::is_base_of{}); + vtab_impl::assign_rename (md, mod, std::is_base_of{}); +#if SQLITE_VERSION_NUMBER >= 3007007 + vtab_impl::assign_recursive_transaction(md, mod, std::is_base_of{}); +#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/include/boost/sqlite/detail/window_function.hpp b/include/boost/sqlite/detail/window_function.hpp new file mode 100644 index 0000000..df8475c --- /dev/null +++ b/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 +#include +#include +#include +#include + +BOOST_SQLITE_BEGIN_NAMESPACE + +namespace detail +{ + +template +struct window_function_maker +{ + void * mem; + + template + Func* operator()(Args && ... args) + { + return new (mem) Func(std::forward(args)...); + } +}; + +template +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; + using span_type = typename std::tuple_element<1U, args_type>::type; + using func_type = typename std::decay::type; + using func_args_type = typename std::decay::type; + + return sqlite3_create_window_function( + db, name.c_str(), + span_type::extent == boost::dynamic_extent ? -1 : static_cast(span_type::extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_args_type(std::forward(args)), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept //xStep + { + auto aa = reinterpret_cast(args); + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> result + { + 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{p}, *fa); + } + c->step(span_type{aa, static_cast(len)}); + return {}; + }); + + }, + [](sqlite3_context* ctx) // xFinal + { + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> resultvalue())> + { + 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{p}, *fa); + } + struct reaper {void operator()(func_type * c) { c->~func_type();}}; + std::unique_ptr cl{c}; + + return c->value(); + }); + }, + [](sqlite3_context* ctx) //xValue + { + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + execute_context_function( + ctx, + [&]() -> resultvalue())> + { + 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{p}, *fa); + } + return c->value(); + }); + + }, + +[](sqlite3_context* ctx, int len, sqlite3_value** args) // xInverse + { + auto aa = reinterpret_cast(args); + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + execute_context_function( + ctx, + [&]() -> resultinverse(span_type{aa, static_cast(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{p}, *fa); + } + c->inverse(span_type{aa, static_cast(len)}); + return {}; + }); + + }, + [](void * ptr) /* xDestroy */ { delete_(static_cast(ptr));} + ); +} + +template +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; + using span_type = typename std::tuple_element<1U, args_type>::type; + using func_type = typename std::decay::type; + using func_args_type = typename std::decay::type; + + return sqlite3_create_window_function( + db, name.c_str(), + span_type::extent == boost::dynamic_extent ? -1 : static_cast(span_type::extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_args_type(std::forward(args)), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept //xStep + { + auto aa = reinterpret_cast(args); + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> resultstep(span_type{aa, static_cast(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{p}, *fa); + } + return c->step(span_type{aa, static_cast(len)}); + }); + + }, + [](sqlite3_context* ctx) // xFinal + { + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + execute_context_function( + ctx, + [&]() -> resultvalue())> + { + 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{p}, *fa); + } + + struct reaper {void operator()(func_type * c) { c->~func_type();}}; + std::unique_ptr cl{c}; + return c->value(); + }); + }, + [](sqlite3_context* ctx) //xValue + { + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + execute_context_function( + ctx, + [&]() -> resultvalue())> + { + 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{p}, *fa); + } + return c->value(); + }); + + }, + +[](sqlite3_context* ctx, int len, sqlite3_value** args) // xInverse + { + auto aa = reinterpret_cast(args); + auto fa = reinterpret_cast(sqlite3_user_data(ctx)); + auto c = static_cast(sqlite3_aggregate_context(ctx, 0)); + execute_context_function( + ctx, + [&]() -> resultinverse(span_type{aa, static_cast(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{p}, *fa); + } + return c->inverse(span_type{aa, static_cast(len)}); + }); + + }, + [](void * ptr) /* xDestroy */ { delete_(static_cast(ptr));} + ); +} + + +} + +BOOST_SQLITE_END_NAMESPACE +#endif // SQLITE_VERSION +#endif //BOOST_SQLITE_IMPL_WINDOW_FUNCTION_HPP diff --git a/include/boost/sqlite/error.hpp b/include/boost/sqlite/error.hpp new file mode 100644 index 0000000..9fe6211 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + + +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(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(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(sqlite3_realloc64(msg_.release(), sz))); + } + else + msg_.reset(static_cast(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 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 +using result = system::result; + +template +struct is_result_type : std::false_type {}; + +template +struct is_result_type> : std::true_type {}; + + +BOOST_SQLITE_END_NAMESPACE + + +#endif // BOOST_SQLITE_ERROR_HPP diff --git a/include/boost/sqlite/extension.hpp b/include/boost/sqlite/extension.hpp new file mode 100644 index 0000000..32553fa --- /dev/null +++ b/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 +#include +#include +#include + + +/** @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 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/include/boost/sqlite/field.hpp b/include/boost/sqlite/field.hpp new file mode 100644 index 0000000..20fe4bd --- /dev/null +++ b/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 +#include +#include +#include + +#include + + +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( 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/include/boost/sqlite/function.hpp b/include/boost/sqlite/function.hpp new file mode 100644 index 0000000..14060b6 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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 ctx, + boost::span 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 +struct context +{ + template + using element = mp11::mp_take_c, N>; + + /// Set the value in the context at position `Idx` + template + void set(element value) + { + sqlite3_set_auxdata(ctx_, Idx, *static_cast(&value), + new (memory_tag{}) element(std::move(value)), + +[](void * ptr) + { + delete_(static_cast *>(ptr)); + }); + } + + /// Returns the value in the context at position `Idx`. Throws if the value isn't set. + template + auto get() -> element & + { + using type = element ; + auto p = static_cast(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 + auto get_if() -> element * + { + using type = element ; + return static_cast(sqlite3_get_auxdata(ctx_, Idx)); + } + + + explicit context(sqlite3_context * ctx) noexcept : ctx_(ctx) {} + + /// Set the result through the context, instead of returning it. + template + auto set_result(T && val) +#if !defined(BOOST_SQLITE_GENERATING_DOCS) + -> decltype(detail::set_result(static_cast(nullptr), std::forward(val))) +#endif + { + detail::set_result(ctx_, std::forward(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` as the first and a `span` 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 ctx, + boost::span 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 +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(nullptr), name, + std::declval(), flags) + ), int>::value>::type +#endif +{ + auto res = detail::create_scalar_function(conn.handle(), name, + std::forward(func), static_cast(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` as the first and a `span` 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 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 +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(nullptr), name, + std::declval(), flags) + ), int>::value>::type +{ + system::error_code ec; + error_info ei; + create_scalar_function(conn, name, std::forward(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 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 val) + { + counter += val[0].get_text().size(); + } + + std::size_t final() + { + return counter; + } + }; + + sqlite::create_function( + conn, "char_counter", std::make_tuple(42)); + + @endcode + + */ +template> +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::type; + auto res = detail::create_aggregate_function( + conn.handle(), name, std::forward(args), static_cast(flags), + callable_traits::has_void_return{} + ); + if (res != 0) + { + BOOST_SQLITE_ASSIGN_EC(ec, res); + ei.set_message(sqlite3_errmsg(conn.handle())); + } +} + +template> +void create_aggregate_function( + connection & conn, + cstring_ref name, + Args && args= {}, + function_flags flags = {}) +{ + system::error_code ec; + error_info ei; + create_aggregate_function(conn, name, std::forward(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 args); + void inverse(boost::span 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 val) + { + counter += val[0].get_text().size(); + } + void inverse(boost::span val) + { + counter -= val[0].get_text().size(); + } + + std::size_t final() + { + return counter; + } + }; + + sqlite::create_function( + conn, "win_char_counter", + aggregate_func{}); + @endcode + */ +template> +void create_window_function( + connection & conn, + cstring_ref name, + Args && args, + function_flags flags, + system::error_code & ec) +{ + using func_type = typename std::decay::type; + auto res = detail::create_window_function( + conn.handle(), name, std::forward(args), static_cast(flags), + callable_traits::has_void_return{}); + if (res != 0) + BOOST_SQLITE_ASSIGN_EC(ec, res); +} + +template> +void create_window_function( + connection & conn, + cstring_ref name, + Args && args = {}, + function_flags flags = {}) +{ + system::error_code ec; + create_window_function(conn, name, std::forward(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/include/boost/sqlite/hooks.hpp b/include/boost/sqlite/hooks.hpp new file mode 100644 index 0000000..50cb4fc --- /dev/null +++ b/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 +#include +#include + +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 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 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 +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(data))() ? 1 : 0; }, nullptr) != nullptr; +} + + +template +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(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 +bool commit_hook(sqlite3 * db, + Func && func) +{ + using func_type = typename std::decay::type; + return commit_hook_impl(db, std::forward(func), std::is_pointer{}); +} + + + +template +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(data))(); }, nullptr) != nullptr; +} + + +template +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(data))(); }, + &func) != nullptr; +} + +inline bool rollback_hook_impl(sqlite3 * db, std::nullptr_t, std::false_type) +{ + return sqlite3_rollback_hook(db, nullptr, nullptr); +} + +template +bool rollback_hook(sqlite3 * db, + Func && func) +{ + using func_type = typename std::decay::type; + return rollback_hook_impl(db, std::forward(func), std::is_pointer{}); +} + +#if defined(SQLITE_ENABLE_PREUPDATE_HOOK) + +template +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(data))(preupdate_context(db), op, db_name, table_name, key1, key2); + }, nullptr) != nullptr; +} + + +template +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::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(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 +bool preupdate_hook(sqlite3 * db, + Func && func) +{ + using func_type = typename std::decay::type; + return preupdate_hook_impl(db, std::forward(func), std::is_pointer{}); +} + +#endif + +template +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(data))(op, db, name, key); + }, nullptr) != nullptr; +} + + +template +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::type; + + return sqlite3_update_hook( + db, + [](void * data, + int op, + const char * db, + const char * name, + sqlite_int64 key) + { + (*static_cast(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 +bool update_hook(sqlite3 * db, + Func && func) +{ + using func_type = typename std::decay::type; + return update_hook_impl(db, std::forward(func), std::is_pointer{}); +} + + +} + +/** + @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 +bool commit_hook(connection & conn, Func && func) +{ + return detail::commit_hook(conn.handle(), std::forward(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 +bool rollback_hook(connection & conn, Func && func) +{ + return detail::rollback_hook(conn.handle(), std::forward(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 +bool preupdate_hook(connection & conn, Func && func) +{ + return detail::preupdate_hook(conn.handle(), std::forward(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 +bool update_hook(connection & conn, Func && func) +{ + return detail::update_hook(conn.handle(), std::forward(func)); +} + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_HOOKS_HPP diff --git a/include/boost/sqlite/json.hpp b/include/boost/sqlite/json.hpp new file mode 100644 index 0000000..3409e56 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include + +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('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 c{static_cast(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(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/include/boost/sqlite/memory.hpp b/include/boost/sqlite/memory.hpp new file mode 100644 index 0000000..e136a0e --- /dev/null +++ b/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 + +#include + +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 +void delete_(T * t) +{ + struct scoped_free + { + void * p; + ~scoped_free() + { + sqlite3_free(p); + } + }; + scoped_free _{t}; + t->~T(); +} + +namespace detail +{ + +template +struct deleter +{ + void operator()(T* t) + { + delete_(t); + } +}; + +template +struct deleter +{ + static_assert(std::is_trivially_destructible::value, "T[] needs to be trivially destructible"); + void operator()(T* t) + { + sqlite3_free(t); + } +}; + +template<> +struct deleter +{ + void operator()(void* t) + { + sqlite3_free(t); + } +}; +} + +template +using unique_ptr = std::unique_ptr>; + +template +inline std::size_t msize(const unique_ptr & ptr) +{ + return sqlite3_msize(ptr.get()); +} + +template +unique_ptr make_unique(Args && ... args) +{ + unique_ptr up{sqlite3_malloc64(sizeof(T))}; + unique_ptr res{new (up.get()) T(std::forward(args)...)}; + up.release(); + return res; +} + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_MEMORY_HPP diff --git a/include/boost/sqlite/meta_data.hpp b/include/boost/sqlite/meta_data.hpp new file mode 100644 index 0000000..00f6527 --- /dev/null +++ b/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 +#include +#include + +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/include/boost/sqlite/mutex.hpp b/include/boost/sqlite/mutex.hpp new file mode 100644 index 0000000..63caf0e --- /dev/null +++ b/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 +#include + +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 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 impl_; +}; + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_MUTEX_HPP diff --git a/include/boost/sqlite/result.hpp b/include/boost/sqlite/result.hpp new file mode 100644 index 0000000..b609eb4 --- /dev/null +++ b/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 +#include +#include + +#include + +#include + + +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(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(value)); +} + +template::value>> +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::int64_t value) + +{ + sqlite3_result_int64(ctx, static_cast(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 +inline auto tag_invoke(set_result_tag, sqlite3_context * ctx, String && str) + -> typename std::enable_if::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 + void operator()(T && val) + { + tag_invoke(set_result_tag{}, ctx, std::forward(val)); + } +}; + +template +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, const variant2::variant & var) +{ + visit(set_variant_result{ctx}, var); +} + +template +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr ptr) +{ + sqlite3_result_pointer(ctx, ptr.release(), typeid(T).name(), +[](void * ptr){delete static_cast(ptr);}); +} + +template +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr ptr) +{ + sqlite3_result_pointer(ctx, ptr.release(), typeid(T).name(), static_cast(ptr.get_deleter())); +} + +template +inline auto tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr ptr) + -> typename std::enable_if::value && + std::is_default_constructible::value>::type +{ + sqlite3_result_pointer(ctx, ptr.release(), typeid(T).name(), +[](void * ptr){Deleter()(static_cast(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 +inline void tag_invoke(set_result_tag tag, sqlite3_context * ctx, result 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 res) +{ + if (res.has_error()) + tag_invoke(tag, ctx, std::move(res).error()); +} + + +namespace detail +{ + +template +inline auto set_result(sqlite3_context * ctx, Value && value) + -> decltype(tag_invoke(set_result_tag{}, ctx, std::forward(value))) +{ + tag_invoke(set_result_tag{}, ctx, std::forward(value)); +} + + +} + + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_RESULT_HPP diff --git a/include/boost/sqlite/resultset.hpp b/include/boost/sqlite/resultset.hpp new file mode 100644 index 0000000..7c514f0 --- /dev/null +++ b/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 +#include +#include + +#include + +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(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(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(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 impl_; + bool done_ = false; +}; + +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_RESULTSET_HPP diff --git a/include/boost/sqlite/row.hpp b/include/boost/sqlite/row.hpp new file mode 100644 index 0000000..6893f96 --- /dev/null +++ b/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_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(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/include/boost/sqlite/statement.hpp b/include/boost/sqlite/statement.hpp new file mode 100644 index 0000000..b77bbe8 --- /dev/null +++ b/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 +#include +#include +#include + +#include +#include +#include + + +#include + +BOOST_SQLITE_BEGIN_NAMESPACE +struct connection; +template +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{}} {} + /// Bind null + param_ref(std::nullptr_t) : impl_{variant2::in_place_type_t{}} {} + /// Bind an integer. + template::value>::type> + param_ref(I value) + { + BOOST_IF_CONSTEXPR ((sizeof(I) == sizeof(int) && std::is_unsigned::value) + || (sizeof(I) > sizeof(int))) + impl_.emplace(static_cast(value)); + else + impl_.emplace(static_cast(value)); + } + /// Bind a blob. + param_ref(blob_view blob) : impl_(blob) { } + /// Bind a string. + param_ref(string_view text) : impl_(text) { } + + template + param_ref(StringLike && text, + typename std::enable_if::value>::type * = nullptr) + : impl_(variant2::in_place_type_t{}, text) {} + + template + param_ref(BlobLike && text, + typename std::enable_if< + !std::is_constructible::value + && std::is_constructible::value>::type * = nullptr) + : impl_(variant2::in_place_type_t{}, 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 + param_ref(std::unique_ptr ptr) + : impl_(variant2::in_place_index_t<7>{}, + std::unique_ptr( + static_cast(ptr.release()), + +[](void * ptr){delete static_cast(ptr);}), + typeid(T).name()) + { + } + + /// Bind pointer value with a function as deleter to the parameter. @see https://www.sqlite.org/bindptr.html + template + param_ref(std::unique_ptr ptr) + : impl_(variant2::in_place_index_t<7>{}, + std::unique_ptr( + static_cast(ptr.release()), + +[](void * ptr){delete static_cast(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 + param_ref(std::unique_ptr ptr, + typename std::enable_if::value && + std::is_default_constructible::value, int>::type * = nullptr) + : impl_(variant2::in_place_index_t<7>{}, + std::unique_ptr( + static_cast(ptr.release()), + +[](void * ptr){delete static_cast(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 + auto operator()(T&& t) const -> typename std::enable_if::value, param_ref>::type + { + return param_ref(std::forward(t)); + } + }; + + public: + /// Construct param_ref from a variant + template + param_ref(T && t, + decltype(variant2::visit(make_visitor(), std::forward(t))) * = nullptr) + : param_ref(variant2::visit(make_visitor(), std::forward(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::numeric_limits::max())) + return sqlite3_bind_blob64(stmt, col, blob.data(), blob.size(), SQLITE_STATIC); + else + return sqlite3_bind_blob(stmt, col, blob.data(), static_cast(blob.size()), SQLITE_STATIC); + } + + int operator()(string_view text) + { + if (text.size() > std::numeric_limits::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(text.size()), SQLITE_STATIC); + } + int operator()(double value) + { + return sqlite3_bind_double(stmt, col, value); + } + int operator()(zero_blob zb) + { + if (static_cast(zb) > static_cast(std::numeric_limits::max())) + return sqlite3_bind_zeroblob64(stmt, col, static_cast(zb)); + else + return sqlite3_bind_zeroblob(stmt, col, static_cast(zb)); + } +#if SQLITE_VERSION_NUMBER >= 3020000 + int operator()(std::pair, 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= 3020000 + , std::pair, 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 > + resultset execute( + ArgRange && params, + system::error_code& ec, + error_info& info) && + { + bind_impl(std::forward(params), ec, info); + resultset rs; + rs.impl_.reset(impl_.release()); + if (!ec) + rs.read_next(ec, info); + return rs; + } + + template > + resultset execute(ArgRange && params) && + { + system::error_code ec; + error_info ei; + auto tmp = std::move(*this).execute(std::forward(params), ec, ei); + if (ec) + detail::throw_error_code(ec, ei); + return tmp; + } + + resultset execute( + std::initializer_list> 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> 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> + static_resultset execute( + ArgRange && params, + system::error_code & ec, + error_info & ei) && + { + static_resultset tmp = std::move(*this).execute(std::forward(params), ec, ei); + if (ec) + return {}; + tmp.check_columns_(ec, ei); + if (ec) + return {}; + + return tmp; + } + + template> + static_resultset execute(ArgRange && params) && + { + system::error_code ec; + error_info ei; + auto tmp = std::move(*this).execute(std::forward(params), ec, ei); + if (ec) + throw_exception(system::system_error(ec, ei.message())); + return tmp; + } + + template + static_resultset execute( + std::initializer_list> params, + system::error_code & ec, + error_info & ei) && + { + static_resultset tmp = std::move(*this).execute(std::move(params), ec, ei); + if (ec) + return {}; + tmp.check_columns_(ec, ei); + if (ec) + return {}; + + return tmp; + } + + template + static_resultset execute(std::initializer_list> params) && + { + system::error_code ec; + error_info ei; + auto tmp = std::move(*this).execute(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 > + resultset execute( + ArgRange && params, + system::error_code& ec, + error_info& info) & + { + bind_impl(std::forward(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 > + resultset execute(ArgRange && params) & + { + system::error_code ec; + error_info ei; + auto tmp = execute(std::forward(params), ec, ei); + if (ec) + detail::throw_error_code(ec, ei); + return tmp; + } + + + resultset execute( + std::initializer_list> 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> 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> + static_resultset execute( + ArgRange && params, + system::error_code & ec, + error_info & ei) & + { + static_resultset tmp = execute(std::forward(params), ec, ei); + if (ec) + return {}; + tmp.check_columns_(ec, ei); + if (ec) + return {}; + + return tmp; + } + + template> + static_resultset execute(ArgRange && params) & + { + system::error_code ec; + error_info ei; + auto tmp = execute(std::forward(params), ec, ei); + if (ec) + throw_exception(system::system_error(ec, ei.message())); + return tmp; + } + + template + static_resultset execute( + std::initializer_list> params, + system::error_code & ec, + error_info & ei) & + { + static_resultset tmp = execute(std::move(params), ec, ei); + if (ec) + return {}; + tmp.check_columns_(ec, ei); + if (ec) + return {}; + + return tmp; + } + + template + static_resultset execute(std::initializer_list> params) & + { + system::error_code ec; + error_info ei; + auto tmp = execute(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 + void bind_impl(std::tuple && vec, + system::error_code & ec, + error_info & ei) + { + const auto sz = sqlite3_bind_parameter_count(impl_.get()); + if (sizeof...(Args) < static_cast(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 + void bind_impl(const std::tuple & vec, + system::error_code & ec, + error_info & ei) + { + const auto sz = sqlite3_bind_parameter_count(impl_.get()); + if (static_cast(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 + void bind_impl(ParamVector && vec, system::error_code & ec, error_info & ei, + typename std::enable_if::type::value_type, param_ref>::value>::type * = nullptr) + { + const auto sz = sqlite3_bind_parameter_count(impl_.get()); + if (vec.size() < static_cast(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(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 + void bind_impl(ParamMap && vec, system::error_code & ec, error_info & ei, + typename std::enable_if< + std::is_convertible::type::key_type, string_view>::value && + std::is_convertible::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::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> 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 & 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 impl_; +}; + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_STATEMENT_HPP diff --git a/include/boost/sqlite/static_resultset.hpp b/include/boost/sqlite/static_resultset.hpp new file mode 100644 index 0000000..fd6c359 --- /dev/null +++ b/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 +#include +#include + +#include + +#include +#include + +#if __cplusplus >= 202002L +#include +#include +#include +#endif + + +namespace boost { template class optional;} + +#if __cplusplus >= 201702L +#include +#endif + +BOOST_SQLITE_BEGIN_NAMESPACE + +namespace detail +{ + +inline void convert_field(sqlite_int64 & target, const field & f) {target = f.get_int();} + +template::value>> +inline void convert_field(std::int64_t & target, const field & f) +{ + target = static_cast(f.get_int()); +} + +inline void convert_field(double & target, const field & f) {target = f.get_double();} + + +template, typename Allocator = std::allocator> +inline void convert_field(std::basic_string & 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 +inline void convert_field(std::optional & target, const field & f) +{ + if (f.is_null()) + target.reset(); + else + convert_field(target.emplace(), f); +} +#endif + +template +inline void convert_field(boost::optional & target, const field & f) +{ + if (f.is_null()) + target.reset(); + else + return convert_field(target.emplace_back(), f); +} + +template +inline constexpr bool field_type_is_nullable(const T& ) {return false;} +#if __cplusplus >= 201702L +template +inline bool field_type_is_nullable(const std::optional &) { return true; } +#endif +template +inline bool field_type_is_nullable(const boost::optional &) { return true; } + +inline value_type required_field_type(const sqlite3_int64 &) {return value_type::integer;} + +template::value>> +inline value_type required_field_type(const std::int64_t &) {return value_type::integer;} + +template +inline value_type required_field_type(const std::basic_string & ) +{ + 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 +void check_columns(const std::tuple *, 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 +void convert_row(std::tuple & 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::value>::type> +void check_columns(const T *, const resultset & r, + system::error_code &ec, error_info & ei) +{ + using mems = boost::describe::describe_members; + constexpr std::size_t sz = mp11::mp_size(); + 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 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>( + [&](auto sz) + { + auto d = mp11::mp_at_c(); + 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( + std::distance(found.begin(), itr), + [&](auto sz) + { + auto d = mp11::mp_at_c(); + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH); + ei.format("Described field '%s' not found in resultset struct.", d.name); + }); + } +} + +template::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 >( + [&](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 + requires (std::is_aggregate_v && !describe::has_describe_members::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; + 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 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>( + [&](auto sz) + { + if (pfr::get_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 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( + std::distance(found.begin(), itr), + [&](auto sz) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH); + auto nm = pfr::get_name(); + ei.format("PFR field %.*s not found in resultset struct.", static_cast(nm.size()), nm.data()); + }); + } +} + +template + requires (std::is_aggregate_v && !describe::has_describe_members::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>>( + [&](auto D) + { + if (pfr::get_name() == f.column_name().c_str()) + { + auto & r = pfr::get(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 +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(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 && 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(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(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 strict() && + { + return {std::move(result_)}; + } + private: + + friend struct static_resultset; + friend struct connection; + friend struct statement; + resultset result_; + void check_columns_( system::error_code & ec, error_info & ei) + { + detail::check_columns(static_cast(nullptr), result_, ec, ei); + } +}; + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_STATIC_RESULTSET_HPP diff --git a/include/boost/sqlite/string.hpp b/include/boost/sqlite/string.hpp new file mode 100644 index 0000000..5a81978 --- /dev/null +++ b/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 +#include + +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(n)); +} + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_STRING_HPP diff --git a/include/boost/sqlite/transaction.hpp b/include/boost/sqlite/transaction.hpp new file mode 100644 index 0000000..b90bdbf --- /dev/null +++ b/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_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/include/boost/sqlite/value.hpp b/include/boost/sqlite/value.hpp new file mode 100644 index 0000000..16dc1c1 --- /dev/null +++ b/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 +#include +#include + + +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(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(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 + T * get_pointer() {return static_cast(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/include/boost/sqlite/vtable.hpp b/include/boost/sqlite/vtable.hpp new file mode 100644 index 0000000..a984f4d --- /dev/null +++ b/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 +#include +#include + +#include +#include + +#include + +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` + * for scalar functions, + * or a `context` as first, and the span as second for aggegrate functions. + * + */ + template + void set(Func & func) + { + set_impl(func, static_cast*>(nullptr)); + } + + template + void set(void(* ptr)(context, span)) noexcept + { + *ppArg_ = reinterpret_cast(ptr); + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto cc = context(ctx); + auto aa = reinterpret_cast(args); + auto &f = *reinterpret_cast, span)>(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, cc, boost::span{aa, static_cast(len)}); + }; + } + + template + void set(T(* ptr)(context, span)) + { + *ppArg_ = reinterpret_cast(ptr); + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept + { + auto cc = context(ctx); + auto aa = reinterpret_cast(args); + auto &f = *reinterpret_cast, span)>(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, cc, boost::span{aa, static_cast(len)}); + }; + } + + template + void set(void(* ptr)(span)) + { + *ppArg_ = reinterpret_cast(ptr); + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept + { + auto aa = reinterpret_cast(args); + auto &f = *reinterpret_cast)>(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, boost::span{aa, static_cast(len)}); + }; + } + + template + void set(T(* ptr)(span)) + { + *ppArg_ = reinterpret_cast(ptr); + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept + { + auto aa = reinterpret_cast(args); + auto &f = *reinterpret_cast)>(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, boost::span{aa, static_cast(len)}); + }; + } + + explicit function_setter(void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg) : pxFunc_(pxFunc), ppArg_(ppArg) {} + private: + + template + void set_impl(Func & func, std::tuple, span>) + { + *ppArg_ = &func; + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept + { + auto cc = context(ctx); + auto aa = reinterpret_cast(args); + auto &f = *reinterpret_cast(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, cc, boost::span{aa, static_cast(len)}); + }; + } + template + void set_impl(Func & func, std::tuple> * ) + { + *ppArg_ = &func; + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept + { + auto aa = reinterpret_cast(args); + auto &f = *reinterpret_cast(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, boost::span{aa, static_cast(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 constraints() const + { + return {info_->aConstraint, static_cast(info_->nConstraint)}; + } + + /// Returns ordering of the index. + BOOST_ATTRIBUTE_NODISCARD + span order_by() const + { + return {info_->aOrderBy, static_cast(info_->nOrderBy)}; + } + + BOOST_ATTRIBUTE_NODISCARD + span usage() + { + return {info_->aConstraintUsage, static_cast(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(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(idx), + reinterpret_cast(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 +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 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 connect(sqlite::connection db, int argc, const char * const argv[]) BOOST_SQLITE_PURE; +}; + +template +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 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 +struct cursor; + +/// The basis for vtable +template +struct table : protected sqlite3_vtab +{ + using cursor_type = Cursor; + + BOOST_SQLITE_VIRTUAL result 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 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 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 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 + friend struct cursor; + friend struct detail::vtab_impl; + + sqlite3 * db_{nullptr}; +}; + +/// Cursor needs the following member. @ingroup reference +template +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 filter( + int /*index*/, const char * /*index_data*/, + boost::span /*values*/) + { + return {system::in_place_error, SQLITE_OK}; + } + + /// @brief Returns the next row. + BOOST_SQLITE_VIRTUAL result 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(int idx, bool no_change) BOOST_SQLITE_PURE; + /// @brief Returns the id of the current row + BOOST_SQLITE_VIRTUAL result row_id() BOOST_SQLITE_PURE; + + /// Get the table the cursor is pointing to. + vtab::table & table() { return *static_cast< vtab::table*>(this->pVtab);} + const vtab::table & table() const { return *static_cast*>(this->pVtab);} + + friend struct detail::vtab_impl; +}; + +/// Cursor needs the following member. @ingroup reference +template<> +struct cursor : 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 filter( + int /*index*/, const char * /*index_data*/, + boost::span /*values*/) + { + return {system::in_place_error, SQLITE_OK}; + } + + /// @brief Returns the next row. + BOOST_SQLITE_VIRTUAL result 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 row_id() BOOST_SQLITE_PURE; + + /// Get the table the cursor is pointing to. + vtab::table & table() { return *static_cast< vtab::table*>(this->pVtab);} + const vtab::table & table() const { return *static_cast*>(this->pVtab);} + + friend struct detail::vtab_impl; +}; + + +/// Group of functions for modifications. @ingroup reference +struct modifiable +{ + BOOST_SQLITE_VIRTUAL result delete_(sqlite::value key) BOOST_SQLITE_PURE; + /// Insert a new row + BOOST_SQLITE_VIRTUAL result insert(sqlite::value key, span values, int on_conflict) BOOST_SQLITE_PURE; + /// Update the row + BOOST_SQLITE_VIRTUAL result update(sqlite::value old_key, sqlite::value new_key, span values, int on_conflict) BOOST_SQLITE_PURE; +}; + +/// Group of functions to support transactions. @ingroup reference +struct transaction +{ + /// Begin a tranasction + BOOST_SQLITE_VIRTUAL result begin() BOOST_SQLITE_PURE; + /// synchronize the state + BOOST_SQLITE_VIRTUAL result sync() BOOST_SQLITE_PURE; + /// commit the transaction + BOOST_SQLITE_VIRTUAL result commit() BOOST_SQLITE_PURE; + /// rollback the transaction + BOOST_SQLITE_VIRTUAL result 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 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 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 savepoint(int i) BOOST_SQLITE_PURE; + /// Release all saves states down to `i` + BOOST_SQLITE_VIRTUAL result release(int i) BOOST_SQLITE_PURE; + /// Roll the transaction back to `i`. + BOOST_SQLITE_VIRTUAL result rollback_to(int i) BOOST_SQLITE_PURE; +}; +#endif + +} + + +namespace detail +{ + +template +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 +auto create_module(connection & conn, + cstring_ref name, + T && module, + system::error_code & ec, + error_info & ei) -> typename std::decay::type & +{ + using module_type = typename std::decay::type; + static const sqlite3_module mod = detail::make_module(module); + + std::unique_ptr p{new (memory_tag{}) module_type(std::forward(module))}; + auto pp = p.get(); + + int res = sqlite3_create_module_v2( + conn.handle(), name.c_str(), + &mod, p.release(), + +[](void * ptr) + { + static_cast(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 +auto create_module(connection & conn, + const char * name, + T && module) -> typename std::decay::type & +{ + system::error_code ec; + error_info ei; + T & ref = create_module(conn, name, std::forward(module), ec, ei); + if (ec) + detail::throw_error_code(ec, ei); + return ref; +} +///@} + + +BOOST_SQLITE_END_NAMESPACE + +#include + +#endif //BOOST_SQLITE_VTABLE_HPP + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e1a62e1 --- /dev/null +++ b/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 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` 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>( + "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( + "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 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`. + + +## 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/src/backup.cpp b/src/backup.cpp new file mode 100644 index 0000000..76099b1 --- /dev/null +++ b/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 +#include + +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 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/src/blob.cpp b/src/blob.cpp new file mode 100644 index 0000000..397d85b --- /dev/null +++ b/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 +#include + +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/src/connection.cpp b/src/connection.cpp new file mode 100644 index 0000000..1ac0ae7 --- /dev/null +++ b/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 +#include + +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(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(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/src/detail/exception.cpp b/src/detail/exception.cpp new file mode 100644 index 0000000..e360f38 --- /dev/null +++ b/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 +#include + +#include + +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/src/error.cpp b/src/error.cpp new file mode 100644 index 0000000..9344fbe --- /dev/null +++ b/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 +#include +#include +#include + +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/src/ext.cpp b/src/ext.cpp new file mode 100644 index 0000000..0152994 --- /dev/null +++ b/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_BEGIN_NAMESPACE + +BOOST_SYMBOL_EXPORT const sqlite3_api_routines *sqlite3_api{nullptr}; + +BOOST_SQLITE_END_NAMESPACE diff --git a/src/field.cpp b/src/field.cpp new file mode 100644 index 0000000..7ff433f --- /dev/null +++ b/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_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(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/src/meta_data.cpp b/src/meta_data.cpp new file mode 100644 index 0000000..c16bf27 --- /dev/null +++ b/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_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/src/resultset.cpp b/src/resultset.cpp new file mode 100644 index 0000000..9528a2a --- /dev/null +++ b/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_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/src/row.cpp b/src/row.cpp new file mode 100644 index 0000000..a674e9c --- /dev/null +++ b/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_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(idx); + return f; + } +} + +BOOST_SQLITE_END_NAMESPACE + diff --git a/src/value.cpp b/src/value.cpp new file mode 100644 index 0000000..4ad5294 --- /dev/null +++ b/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_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(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/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..79750fb --- /dev/null +++ b/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/test/Jamfile b/test/Jamfile new file mode 100644 index 0000000..85d19ed --- /dev/null +++ b/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 ../../.. + +; + +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 + : shared ; + +# TODO run simple_scalar as sqlite3 :memory: ".read extension/simple_scalar.sql" \ No newline at end of file diff --git a/test/allocator.cpp b/test/allocator.cpp new file mode 100644 index 0000000..a9d9b7c --- /dev/null +++ b/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 +#include + +using namespace boost; + +BOOST_AUTO_TEST_CASE(allocator) +{ + + sqlite::allocator 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::max)())), std::bad_alloc); +} diff --git a/test/backup.cpp b/test/backup.cpp new file mode 100644 index 0000000..5a42c50 --- /dev/null +++ b/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 +#include + +#include +#include + +#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 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/test/blob.cpp b/test/blob.cpp new file mode 100644 index 0000000..ca6ce83 --- /dev/null +++ b/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 +#include + +#include + +#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 blobby; + blobby.resize(4096*4096); + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_int_distribution dist(0,255); // distribution in range [1, 6] + + std::generate(blobby.begin(), blobby.end(), + [&]{return static_cast(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(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/test/catch.cpp b/test/catch.cpp new file mode 100644 index 0000000..37b8dbb --- /dev/null +++ b/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 +#include +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/test/collation.cpp b/test/collation.cpp new file mode 100644 index 0000000..8893e42 --- /dev/null +++ b/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 +#include +#include + +#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 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 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/test/connection.cpp b/test/connection.cpp new file mode 100644 index 0000000..b3c63ca --- /dev/null +++ b/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 +#include "test.hpp" + +#include + +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/test/extension/CMakeLists.txt b/test/extension/CMakeLists.txt new file mode 100644 index 0000000..a52a92c --- /dev/null +++ b/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/test/extension/simple_scalar.cpp b/test/extension/simple_scalar.cpp new file mode 100644 index 0000000..4a05afe --- /dev/null +++ b/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 +#include + +BOOST_SQLITE_EXTENSION(simplescalar, conn) +{ + create_scalar_function( + conn, "assert", + [](boost::sqlite::context<>, boost::span sp) + { + if (sp.front().get_int() == 0) + throw std::logic_error("test failed"); + }); + + create_scalar_function( + conn, "my_add", + [](boost::sqlite::context<>, boost::span sp)-> sqlite3_int64 + { + return sp[0].get_int() + sp[1].get_int(); + }); +} diff --git a/test/extension/simple_scalar.sql b/test/extension/simple_scalar.sql new file mode 100644 index 0000000..00064ef --- /dev/null +++ b/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/test/field.cpp b/test/field.cpp new file mode 100644 index 0000000..a3fae32 --- /dev/null +++ b/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 +#include +#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/test/function.cpp b/test/function.cpp new file mode 100644 index 0000000..b9a4c57 --- /dev/null +++ b/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 +#include +#include "test.hpp" + +#include +#include + +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 val) + -> variant2::variant + { + 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 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 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 val) + -> variant2::variant + { + 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 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 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 ) + { + 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 ) + { + return ; + }); + + std::vector 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 val) + { + counter += val[0].get_text().size(); + } + + std::int64_t final() + { + return counter; + } + }; + + sqlite::create_aggregate_function( + conn, + "char_counter", std::make_tuple(0)); + + std::vector 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 step(boost::span val) + { + counter += val[0].get_text().size(); + return {}; + } + + sqlite::result final() + { + return counter; + } + }; + + sqlite::create_aggregate_function( + conn, + "char_counter", std::make_tuple(0)); + + std::vector 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 val) + { + counter += val[0].get_text().size(); + } + + void inverse(boost::span val) + { + counter -= val[0].get_text().size(); + } + + + std::int64_t value() + { + return counter; + } + }; + + sqlite::create_window_function( + conn, + "win_counter"); + + std::vector 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 step(boost::span val) + { + counter += val[0].get_text().size(); + return{}; + } + + sqlite::result inverse(boost::span val) + { + counter -= val[0].get_text().size(); + return {}; + } + + + sqlite::result value() + { + return counter; + } + }; + + sqlite::create_window_function( + conn, + "win_counter"); + + std::vector 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/test/hooks.cpp b/test/hooks.cpp new file mode 100644 index 0000000..2aa371f --- /dev/null +++ b/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 +#include +#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/test/json.cpp b/test/json.cpp new file mode 100644 index 0000000..fe924ab --- /dev/null +++ b/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 +#include +#include +#include +#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 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/test/main_test.cpp b/test/main_test.cpp new file mode 100644 index 0000000..23c2f70 --- /dev/null +++ b/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 + +#include diff --git a/test/meta_data.cpp b/test/meta_data.cpp new file mode 100644 index 0000000..2861a1c --- /dev/null +++ b/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 +#include +#include "test.hpp" +#include + +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/test/mutex.cpp b/test/mutex.cpp new file mode 100644 index 0000000..89023e1 --- /dev/null +++ b/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 + +#include +#include + +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 l1{mtx}; + std::lock_guard l2{rmtx}; + std::lock_guard 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/test/statement.cpp b/test/statement.cpp new file mode 100644 index 0000000..b9e96f6 --- /dev/null +++ b/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 +#include "test.hpp" + +#include +#include + +#include + + +using namespace boost; + + +BOOST_AUTO_TEST_CASE(statement) +{ + sqlite::connection conn; + conn.connect(":memory:"); +#if SQLITE_VERSION_NUMBER >= 3020000 + std::unique_ptr 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() != nullptr); + BOOST_CHECK(v.get_pointer() == ip); + BOOST_CHECK(v.get_pointer() == 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> 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/test/static_resultset.cpp b/test/static_resultset.cpp new file mode 100644 index 0000000..6a9b7c0 --- /dev/null +++ b/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 +#include + +#include + +#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; + + bool found = false; + for (tup t : conn.query("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({"hodges"})) + { + BOOST_CHECK(std::get<1>(t) == "richard"); + found = true; + } + BOOST_CHECK(found); + + BOOST_CHECK_THROW(conn.query("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("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({"hodges"})) + { + BOOST_CHECK(t.first_name == "richard"); + BOOST_CHECK(t.last_name == "hodges"); + found = true; + } + BOOST_CHECK(found); + + BOOST_CHECK_THROW(conn.query("select id, first_name, last_name from author where last_name = 'hodges';"), + system::system_error); + conn.close(); +} + +#endif diff --git a/test/test-db.sql b/test/test-db.sql new file mode 100644 index 0000000..251313c --- /dev/null +++ b/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/test/test.hpp b/test/test.hpp new file mode 100644 index 0000000..3244d9e --- /dev/null +++ b/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 + +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/test/transaction.cpp b/test/transaction.cpp new file mode 100644 index 0000000..f1331b7 --- /dev/null +++ b/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 + +#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/test/vtable.cpp b/test/vtable.cpp new file mode 100644 index 0000000..4f38732 --- /dev/null +++ b/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 +#include "test.hpp" + +#include +#include +#include +#include + +#include +#include + +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 +{ + + simple_cursor(std::vector::const_iterator itr, + std::vector::const_iterator end) : itr(itr), end(end) {} + std::vector::const_iterator itr, end; + + sqlite::result next() {itr++; return {};} + sqlite::result row_id() {return *reinterpret_cast(&itr);} + + sqlite::result 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 +{ + const char * declaration() + { + return R"(create table x(name text);)"; + } + + simple_table(std::vector & names) : names(names) {} + std::vector & names; + + sqlite::result open() + { + return cursor_type{names.begin(), names.end()}; + } + +}; + +struct simple_test_impl final : sqlite::vtab::eponymous_module +{ + std::vector names = {"ruben", "vinnie", "richard", "klemens"}; + + + + + sqlite::result 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> +{ + using iterator = boost::unordered_map::const_iterator; + + modifyable_cursor(iterator itr, iterator end) : itr(itr), end(end) {} + + iterator itr, end; + + sqlite::result next() noexcept {itr++; return {};} + sqlite::result row_id() noexcept {return itr->first;} + + sqlite::result 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, + intrusive::list_base_hook >, + sqlite::vtab::modifiable +{ + const char * declaration() + { + return R"(create table x(id integer primary key autoincrement, name text);)"; + } + + std::string name; + boost::unordered_map 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 open() + { + return modifyable_cursor{names.begin(), names.end()}; + } + + sqlite::result delete_(sqlite::value key) + { + BOOST_CHECK(names.erase(key.get_int()) == 1); + return {}; + } + sqlite::result insert(sqlite::value /*key*/, span 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 update(sqlite::value old_key, sqlite::value new_key, + span 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_test_impl() = default; + + intrusive::list> list; + + sqlite::result 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(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/tools/get-boost.sh b/tools/get-boost.sh new file mode 100755 index 0000000..fb0c8ed --- /dev/null +++ b/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/tools/user-config.jam b/tools/user-config.jam new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3