├── .bazelversion ├── cmake ├── lib │ ├── setup-warnings.cmake │ ├── sl3Config.cmake.in │ ├── debug.cmake │ ├── doxydoc.cmake │ ├── install.cmake │ ├── testing.cmake │ └── find_sqlite.cmake ├── toolchain │ ├── linux-clang-san-adr-ub.cmake │ ├── linux-clang-san-mem.cmake │ ├── use-vcpkg.cmake │ ├── linux-clang.cmake │ ├── add-on-coverage-gcc.cmake │ └── router.cmake ├── add-on │ ├── fetch-dependencies.cmake │ ├── coverage-clang.cmake │ ├── coverage-gcov.cmake │ └── coverage-report-clang.sh ├── preset │ ├── base.json │ ├── ninja.json │ ├── xcode.json │ └── msvc22.json └── project-setup.cmake ├── tests ├── value │ ├── CMakeLists.txt │ └── BUILD.bazel ├── dbvalue │ ├── CMakeLists.txt │ └── BUILD.bazel ├── version │ ├── CMakeLists.txt │ ├── BUILD.bazel │ └── versiontest.cpp ├── commands │ ├── CMakeLists.txt │ ├── BUILD.bazel │ └── commandstest.cpp ├── dataset │ ├── CMakeLists.txt │ ├── BUILD.bazel │ └── datasettest.cpp ├── database │ ├── CMakeLists.txt │ └── BUILD.bazel ├── typenames │ ├── CMakeLists.txt │ ├── BUILD.bazel │ └── typenamestest.cpp ├── testing.hpp ├── rowcallback │ ├── CMakeLists.txt │ ├── BUILD.bazel │ └── rowcallbacktest_coverage.cpp ├── BUILD.bazel ├── sample │ ├── main4.cpp │ ├── main2.cpp │ ├── BUILD.bazel │ ├── main6.cpp │ ├── main.cpp │ ├── main5.cpp │ ├── CMakeLists.txt │ ├── main7.cpp │ ├── main1.cpp │ └── main3.cpp ├── CMakeLists.txt └── test_main.cpp ├── REPO.bazel ├── CMakePresets.json ├── .gitignore ├── vcpkg.json ├── .bazelrc ├── vcpkg-configuration.json ├── .clang-format ├── include ├── sl3.hpp └── sl3 │ ├── rowcallback.hpp │ ├── dbvalues.hpp │ ├── types.hpp │ ├── dataset.hpp │ ├── container.hpp │ ├── error.hpp │ ├── columns.hpp │ ├── command.hpp │ ├── value.hpp │ └── database.hpp ├── Dockerfile ├── MODULE.bazel ├── src ├── sl3 │ ├── rowcallback.cpp │ ├── error.cpp │ ├── config.cpp │ ├── types.cpp │ ├── utils.hpp │ ├── dbvalues.cpp │ ├── connection.hpp │ ├── dataset.cpp │ ├── database.cpp │ ├── columns.cpp │ ├── command.cpp │ └── dbvalue.cpp └── config.in ├── tools └── check_sqlite_versions.sh ├── .github └── workflows │ ├── build-bazel.yml │ ├── build-internal-sqlite.yml │ ├── build-project.yml │ └── docs.yml ├── CMakeUserPresetsExample.json ├── BAZEL_SETUP_CONTEXT.md ├── BUILD.bazel ├── config.bzl ├── README.md ├── CMakeLists.txt └── sqlite └── setup_sqlite3.cmake /.bazelversion: -------------------------------------------------------------------------------- 1 | 8.4.0 -------------------------------------------------------------------------------- /cmake/lib/setup-warnings.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | 3 | -------------------------------------------------------------------------------- /tests/value/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_doctest(value 3 | SOURCES 4 | valuetest.cpp 5 | ) 6 | -------------------------------------------------------------------------------- /tests/dbvalue/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_doctest(dbvalue 3 | SOURCES 4 | dbvaluetest.cpp 5 | ) 6 | -------------------------------------------------------------------------------- /tests/version/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_doctest(version 3 | SOURCES 4 | versiontest.cpp 5 | ) 6 | -------------------------------------------------------------------------------- /tests/commands/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_doctest(commands 3 | SOURCES 4 | commandstest.cpp 5 | ) 6 | -------------------------------------------------------------------------------- /tests/dataset/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_doctest(dataset 3 | SOURCES 4 | datasettest.cpp 5 | ) 6 | 7 | -------------------------------------------------------------------------------- /tests/database/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_doctest(database 3 | SOURCES 4 | dbtest.cpp 5 | ) 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/typenames/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_doctest(typenames 3 | SOURCES 4 | typenamestest.cpp 5 | ) 6 | -------------------------------------------------------------------------------- /REPO.bazel: -------------------------------------------------------------------------------- 1 | """Files to ignore for bazel query and build commands.""" 2 | ignore_directories(["build", "sqlite",]) 3 | -------------------------------------------------------------------------------- /tests/testing.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "doctest/doctest.h" 4 | #include 5 | #include 6 | -------------------------------------------------------------------------------- /cmake/lib/sl3Config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/sl3Targets.cmake") 4 | 5 | check_required_components("sl3") 6 | -------------------------------------------------------------------------------- /tests/rowcallback/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_doctest(rowcallback 3 | SOURCES 4 | rowcallbacktest.cpp 5 | rowcallbacktest_coverage.cpp 6 | ) 7 | -------------------------------------------------------------------------------- /cmake/toolchain/linux-clang-san-adr-ub.cmake: -------------------------------------------------------------------------------- 1 | 2 | 3 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -g -O1 -fno-omit-frame-pointer") 4 | 5 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 8, 3 | "include": [ 4 | "cmake/preset/ninja.json", 5 | "cmake/preset/xcode.json", 6 | "cmake/preset/msvc22.json" 7 | ] 8 | } 9 | 10 | -------------------------------------------------------------------------------- /tests/value/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_test") 2 | 3 | cc_test( 4 | name = "value_test", 5 | timeout = "short", 6 | srcs = ["valuetest.cpp"], 7 | deps = [ 8 | "//:sl3", 9 | "//tests:doctest_main", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /tests/database/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_test") 2 | 3 | cc_test( 4 | name = "database_test", 5 | timeout = "short", 6 | srcs = ["dbtest.cpp"], 7 | deps = [ 8 | "//:sl3", 9 | "//tests:doctest_main", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /tests/dataset/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_test") 2 | 3 | cc_test( 4 | name = "dataset_test", 5 | timeout = "short", 6 | srcs = ["datasettest.cpp"], 7 | deps = [ 8 | "//:sl3", 9 | "//tests:doctest_main", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /tests/dbvalue/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_test") 2 | 3 | cc_test( 4 | name = "dbvalue_test", 5 | timeout = "short", 6 | srcs = ["dbvaluetest.cpp"], 7 | deps = [ 8 | "//:sl3", 9 | "//tests:doctest_main", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /tests/version/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_test") 2 | 3 | cc_test( 4 | name = "version_test", 5 | timeout = "short", 6 | srcs = ["versiontest.cpp"], 7 | deps = [ 8 | "//:sl3", 9 | "//tests:doctest_main", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # eclipse project 3 | .project 4 | .cproject 5 | .settings 6 | *~ 7 | configureCustomFlags 8 | build*/ 9 | *include/sl3/config.hpp 10 | *tests/basics/configtest.cpp 11 | *.pyc 12 | 13 | .vscode 14 | 15 | .DS_STORE 16 | CMakeUserPresets.json 17 | 18 | bazel-* 19 | -------------------------------------------------------------------------------- /tests/commands/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_test") 2 | 3 | cc_test( 4 | name = "commands_test", 5 | timeout = "short", 6 | srcs = ["commandstest.cpp"], 7 | deps = [ 8 | "//:sl3", 9 | "//tests:doctest_main", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /tests/typenames/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_test") 2 | 3 | cc_test( 4 | name = "typenames_test", 5 | timeout = "short", 6 | srcs = ["typenamestest.cpp"], 7 | deps = [ 8 | "//:sl3", 9 | "//tests:doctest_main", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /cmake/toolchain/linux-clang-san-mem.cmake: -------------------------------------------------------------------------------- 1 | 2 | 3 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=memory -fsanitize-memory-track-origins=2 -g -O1 -fno-omit-frame-pointer") 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memory -fsanitize-memory-track-origins=2 -g -O1 -fno-omit-frame-pointer") 5 | 6 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libsl3", 3 | "version": "1.2.41.2", 4 | "dependencies": [ 5 | { 6 | "name": "common-compiler-warnings", 7 | "version>=": "0.9.1" 8 | }, 9 | { 10 | "name": "doctest", 11 | "version>=": "2.4.12" 12 | }, 13 | "sqlite3" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | 2 | # Enable platform-specific config 3 | common --enable_platform_specific_config 4 | 5 | # GCC/Clang warnings 6 | build:unix --copt=-Wall --copt=-Wextra --copt=-Wpedantic --copt=-Werror 7 | # MSVC warnings 8 | build:msvc --copt=/W4 --copt=/WX 9 | 10 | # Map configs to platforms/toolchains 11 | build:macos --config=unix 12 | build:linux --config=unix 13 | build:windows --config=msvc 14 | -------------------------------------------------------------------------------- /cmake/toolchain/use-vcpkg.cmake: -------------------------------------------------------------------------------- 1 | 2 | if(DEFINED ENV{VCPKG_INSTALLATION_ROOT}) 3 | set(VCPKG_ROOT $ENV{VCPKG_INSTALLATION_ROOT}) 4 | elseif(DEFINED ENV{VCPKG_ROOT}) 5 | set(VCPKG_ROOT $ENV{VCPKG_ROOT}) 6 | else() 7 | message(FATAL_ERROR "Neither VCPKG_INSTALLATION_ROOT nor VCPKG_ROOT environment variables are defined.") 8 | endif() 9 | 10 | include(${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake) 11 | 12 | -------------------------------------------------------------------------------- /vcpkg-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "default-registry": { 3 | "kind": "builtin", 4 | "baseline": "5e5d0e1cd7785623065e77eff011afdeec1a3574" 5 | }, 6 | "registries": [ 7 | { 8 | "kind": "git", 9 | "baseline": "e9273e2613b55f3fef3d0cf57a4e98d60aa72b02", 10 | "repository": "https://github.com/a4z/vcpkg-ports", 11 | "packages": [ "common-compiler-warnings" ] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: GNU 2 | AlignConsecutiveAssignments: true 3 | AlignConsecutiveDeclarations: true 4 | AlignEscapedNewlinesLeft: true 5 | BinPackArguments: false 6 | BinPackParameters: false 7 | BreakConstructorInitializersBeforeComma: true 8 | Cpp11BracedListStyle: true 9 | NamespaceIndentation: All 10 | PointerAlignment: Left 11 | Standard: Cpp11 12 | ConstructorInitializerIndentWidth: 0 13 | KeepEmptyLinesAtTheStartOfBlocks: false 14 | 15 | -------------------------------------------------------------------------------- /tests/rowcallback/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_test") 2 | 3 | cc_test( 4 | name = "rowcallback_test", 5 | timeout = "short", 6 | srcs = ["rowcallbacktest.cpp"], 7 | deps = [ 8 | "//:sl3", 9 | "//tests:doctest_main", 10 | ], 11 | ) 12 | 13 | cc_test( 14 | name = "rowcallback_coverage_test", 15 | timeout = "short", 16 | srcs = ["rowcallbacktest_coverage.cpp"], 17 | deps = [ 18 | "//:sl3", 19 | "//tests:doctest_main", 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /tests/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | # Common test main object 6 | cc_library( 7 | name = "doctest_main", 8 | srcs = ["test_main.cpp"], 9 | hdrs = ["testing.hpp"], 10 | linkstatic = 1, 11 | deps = [ 12 | "@doctest//doctest:custom_main", 13 | ], 14 | ) 15 | 16 | # Helper function would go here if Bazel supported it in BUILD files 17 | # For now, we'll define tests individually in subdirectories 18 | -------------------------------------------------------------------------------- /include/sl3.hpp: -------------------------------------------------------------------------------- 1 | /** \file sl3.hpp 2 | * Main include file for the sl3 library 3 | * 4 | * This file includes all the other header files of the library. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "sl3/columns.hpp" 10 | #include "sl3/command.hpp" 11 | #include "sl3/config.hpp" 12 | #include "sl3/container.hpp" 13 | #include "sl3/database.hpp" 14 | #include "sl3/dataset.hpp" 15 | #include "sl3/dbvalue.hpp" 16 | #include "sl3/dbvalues.hpp" 17 | #include "sl3/error.hpp" 18 | #include "sl3/rowcallback.hpp" 19 | #include "sl3/types.hpp" 20 | #include "sl3/value.hpp" 21 | -------------------------------------------------------------------------------- /cmake/toolchain/linux-clang.cmake: -------------------------------------------------------------------------------- 1 | 2 | include_guard(GLOBAL) 3 | 4 | message(STATUS "-- Activate toolchain linux-clang.cmake") 5 | 6 | set(CMAKE_C_COMPILER /usr/bin/clang) 7 | set(CMAKE_CXX_COMPILER /usr/bin/clang++) 8 | 9 | # Specify the path to the Clang linker 10 | set(CMAKE_LINKER /usr/bin/clang++) 11 | #set(CMAKE_LINKER /usr/bin/lld) 12 | set(CMAKE_AR /usr/bin/llvm-ar) 13 | set(CMAKE_RANLIB /usr/bin/llvm-ranlib) 14 | set(CMAKE_NM /usr/bin/llvm-nm) 15 | set(CMAKE_OBJCOPY /usr/bin/llvm-objcopy) 16 | set(CMAKE_OBJDUMP /usr/bin/llvm-objdump) 17 | set(CMAKE_STRIP /usr/bin/llvm-strip) 18 | -------------------------------------------------------------------------------- /cmake/add-on/fetch-dependencies.cmake: -------------------------------------------------------------------------------- 1 | 2 | include(FetchContent) 3 | FetchContent_Declare( 4 | commonCompilerWarnings 5 | GIT_REPOSITORY https://github.com/a4z/commonCompilerWarnings.git 6 | GIT_TAG main 7 | OVERRIDE_FIND_PACKAGE 8 | ) 9 | 10 | FetchContent_Declare( 11 | doctest 12 | SYSTEM 13 | GIT_REPOSITORY "https://github.com/onqtam/doctest" 14 | GIT_TAG "v2.4.12" 15 | OVERRIDE_FIND_PACKAGE 16 | ) 17 | 18 | set(DOCTEST_NO_INSTALL TRUE) 19 | 20 | set(commonCompilerWarnings_NO_INSTALL TRUE) 21 | # now with this problem solved, we probably should always fetch that internally 22 | # so for users, this just works 23 | -------------------------------------------------------------------------------- /cmake/toolchain/add-on-coverage-gcc.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | # that might not be the elegant way to do it, but it works mosts simple 3 | # alternative would be finding a way to inject coverage into every target 4 | # and that would require much more 5 | 6 | message(STATUS "----- GNU Coverage enabled") 7 | # TODO, add -fprofile-abs-path when compiler is gcc 8 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") 9 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") 10 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 11 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | # base dev dependencies, git for fetch content 3 | RUN apk add --no-cache build-base ccache cmake ninja-build ninja-is-really-ninja git 4 | # some coverage and linkers 5 | RUN apk add --no-cache lcov gzip lld binutils-gold 6 | # clang and llvm TODO, this is version dependend, should be specified in the future 7 | RUN apk add --no-cache llvm clang compiler-rt clang17-extra-tools 8 | # dev dependencies, for now (until switch to vcpkg happens) 9 | RUN apk add sqlite-dev 10 | 11 | # avoid ccache permission issues 12 | ENV CCACHE_DIR=/tmp/ccache 13 | ARG UNAME=user 14 | ARG UID=1000 15 | ARG GID=1000 16 | RUN adduser -u ${UID} -g ${GID} ${UNAME} -D -h /home/user 17 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | """libsl3 - A C++ wrapper for SQLite3""" 2 | 3 | # SQLite version configuration - single source of truth 4 | SQLITE3_MINOR = 50 5 | SQLITE3_PATCH = 4 6 | SQLITE3_VERSION = "3.{}.{}".format(SQLITE3_MINOR, SQLITE3_PATCH) 7 | 8 | module( 9 | name = "libsl3", 10 | version = "1.2.{}".format(SQLITE3_MINOR * 1000 + SQLITE3_PATCH), 11 | ) 12 | 13 | # Core Bazel modules 14 | bazel_dep(name = "rules_cc", version = "0.2.8") 15 | bazel_dep(name = "platforms", version = "1.0.0") 16 | 17 | # Library dependencies from the Bazel Central Registry 18 | bazel_dep(name = "sqlite3", version = SQLITE3_VERSION, repo_name = "sqlite") 19 | bazel_dep(name = "doctest", version = "2.4.12") 20 | -------------------------------------------------------------------------------- /cmake/lib/debug.cmake: -------------------------------------------------------------------------------- 1 | 2 | function(print_all_variables) 3 | message(STATUS "CMake Variables:") 4 | #get_cmake_property(_variableNames VARIABLES) 5 | get_cmake_property(_variableNames CACHE_VARIABLES) 6 | list(SORT _variableNames) 7 | foreach (_variableName ${_variableNames}) 8 | message(STATUS "${_variableName}=${${_variableName}}") 9 | endforeach() 10 | endfunction() 11 | 12 | # Call the function to print all variables 13 | # print_all_variables() 14 | 15 | function(print_variable var_name) 16 | if(DEFINED ${var_name}) 17 | message(STATUS "${var_name}=${${var_name}}") 18 | else() 19 | message(STATUS "${var_name} is not defined") 20 | endif() 21 | endfunction() 22 | -------------------------------------------------------------------------------- /tests/sample/main4.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | int 5 | main () 6 | { 7 | using namespace sl3; 8 | DbValue val (Type::Variant); 9 | assert (val.isNull ()); 10 | assert (val.get ("foo") == "foo"); // get with default never throws 11 | val = 2; 12 | std::cout << val << std::endl; // access the integer property 13 | try 14 | { // not possible since this variant holds an integer value 15 | val.getText (); 16 | } 17 | catch (const Error& e) 18 | { 19 | std::cout << e << std::endl; 20 | } 21 | assert (val.get ("foo") == "foo"); // get with default never throws 22 | 23 | val = "example"; 24 | // now it is possible to access the text property 25 | std::cout << val.getText () << std::endl; 26 | } 27 | -------------------------------------------------------------------------------- /src/sl3/rowcallback.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #include 10 | 11 | namespace sl3 12 | { 13 | void 14 | RowCallback::onStart () 15 | { 16 | } 17 | 18 | void 19 | RowCallback::onEnd () 20 | { 21 | } 22 | 23 | } // ns 24 | -------------------------------------------------------------------------------- /tools/check_sqlite_versions.sh: -------------------------------------------------------------------------------- 1 | 2 | cmake_minor=$(grep '^set([[:space:]]*internal_SQLITE_MINOR_V' CMakeLists.txt | sed 's/[^0-9]*\([0-9][0-9]*\).*/\1/') 3 | cmake_patch=$(grep '^set([[:space:]]*internal_SQLITE_PATCH_V' CMakeLists.txt | sed 's/[^0-9]*\([0-9][0-9]*\).*/\1/') 4 | 5 | echo "CMakeLists.txt: minor=$cmake_minor, patch=$cmake_patch" 6 | 7 | 8 | bazel_minor=$(grep '^SQLITE3_MINOR' MODULE.bazel | cut -d '=' -f2 | tr -d '[:space:]') 9 | bazel_patch=$(grep '^SQLITE3_PATCH' MODULE.bazel | cut -d '=' -f2 | tr -d '[:space:]') 10 | 11 | echo "MODULE.bazel: minor=$bazel_minor, patch=$bazel_patch" 12 | 13 | 14 | if [ "$cmake_minor" = "$bazel_minor" ] && [ "$cmake_patch" = "$bazel_patch" ]; then 15 | echo "SQLite versions match" 16 | exit 0 17 | fi 18 | 19 | echo "Error: SQLite version mismatch" >&2 20 | exit 1 21 | -------------------------------------------------------------------------------- /.github/workflows/build-bazel.yml: -------------------------------------------------------------------------------- 1 | name: build bazel 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | name: ${{ matrix.os }} 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | - macos-latest 19 | - windows-latest 20 | steps: 21 | - uses: bazel-contrib/setup-bazel@0.15.0 22 | with: 23 | bazelisk-cache: true 24 | disk-cache: ${{ github.workflow }} 25 | external-cache: true 26 | 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | 30 | - name: Build (fastbuild) 31 | run: bazel build //... 32 | 33 | - name: Test (fastbuild) 34 | run: bazel test //... 35 | -------------------------------------------------------------------------------- /tests/sample/main2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | int 7 | main () 8 | { 9 | using namespace sl3; 10 | // define a db 11 | Database db (":memory:"); 12 | // run commands against the db 13 | db.execute ("CREATE TABLE tbl(f1 INTEGER, f2 TEXT, f3 REAL);"); 14 | // create a command with parameters 15 | auto cmd = db.prepare ("INSERT INTO tbl (f1, f2, f3) VALUES (?,?,?);", 16 | DbValues ({Type::Int, Type::Text, Type::Real})); 17 | 18 | // this will work, 19 | cmd.execute (parameters (1, "one", 1.1)); 20 | 21 | // this will throw since "2" is a wrong type 22 | try 23 | { 24 | cmd.execute (parameters ("2", "two", 2.2)); 25 | } 26 | catch (const Error& e) 27 | { 28 | std::cout << e << std::endl; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/sample/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_binary") 2 | 3 | cc_binary( 4 | name = "sample", 5 | srcs = ["main.cpp"], 6 | deps = ["//:sl3"], 7 | ) 8 | 9 | cc_binary( 10 | name = "sample1", 11 | srcs = ["main1.cpp"], 12 | deps = ["//:sl3"], 13 | ) 14 | 15 | cc_binary( 16 | name = "sample2", 17 | srcs = ["main2.cpp"], 18 | deps = ["//:sl3"], 19 | ) 20 | 21 | cc_binary( 22 | name = "sample3", 23 | srcs = ["main3.cpp"], 24 | deps = ["//:sl3"], 25 | ) 26 | 27 | cc_binary( 28 | name = "sample4", 29 | srcs = ["main4.cpp"], 30 | deps = ["//:sl3"], 31 | ) 32 | 33 | cc_binary( 34 | name = "sample5", 35 | srcs = ["main5.cpp"], 36 | deps = ["//:sl3"], 37 | ) 38 | 39 | cc_binary( 40 | name = "sample6", 41 | srcs = ["main6.cpp"], 42 | deps = ["//:sl3"], 43 | ) 44 | -------------------------------------------------------------------------------- /tests/sample/main6.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | int 6 | main () 7 | { 8 | using namespace sl3; 9 | // define a db 10 | Database db (":memory:"); 11 | // run commands against the db 12 | 13 | db.execute ("CREATE TABLE tbl(f1 INTEGER, f2 TEXT, f3 REAL);" 14 | "INSERT INTO tbl (f1, f2, f3) VALUES (1, 'one', 1.1);" 15 | "INSERT INTO tbl (f1, f2, f3) VALUES (2, 'two', 2.2)"); 16 | 17 | db.execute ("SELECT f1, f2 FROM tbl;", [] (Columns cols) { 18 | assert (cols.count () == 2); 19 | assert (cols.getType (0) == Type::Int); 20 | assert (cols.getType (1) == Type::Text); 21 | std::cout << std::to_string (cols.getInt (0)) << "_" << cols.getText (1) 22 | << std::endl; 23 | return true; 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /tests/sample/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | int 7 | main () 8 | { 9 | using namespace sl3; 10 | // define a db 11 | Database db (":memory:"); 12 | // run commands against the db 13 | 14 | db.execute ("CREATE TABLE tbl(f1 INTEGER, f2 TEXT, f3 REAL);" 15 | "INSERT INTO tbl (f1, f2, f3) VALUES (1, 'one', 1.1);" 16 | "INSERT INTO tbl (f1, f2, f3) VALUES (2, 'two', 2.2)"); 17 | 18 | db.execute ("SELECT f1, f2 FROM tbl;", [] (Columns cols) { 19 | assert (cols.count () == 2); 20 | assert (cols.getType (0) == Type::Int); 21 | assert (cols.getType (1) == Type::Text); 22 | std::cout << std::to_string (cols.getInt (0)) << "_" << cols.getText (1) 23 | << std::endl; 24 | return true; 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/sl3/error.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace sl3 15 | { 16 | std::ostream& 17 | operator<< (std::ostream& os, const Error& e) 18 | { 19 | os << "sl3::" << ErrCodeName (e.getId ()) << ":" << e.what (); 20 | return os; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # add_library(doctest_main OBJECT test_main.cpp ) 3 | 4 | include(lib/testing) 5 | 6 | add_subdirectory(commands) 7 | add_subdirectory(database) 8 | add_subdirectory(dataset) 9 | add_subdirectory(dbvalue) 10 | add_subdirectory(rowcallback) 11 | add_subdirectory(typenames) 12 | add_subdirectory(value) 13 | add_subdirectory(version) 14 | 15 | 16 | 17 | # make test should also run the sample, so either put it here 18 | add_subdirectory(sample) 19 | 20 | if (NOT CODECOVERAGE) 21 | 22 | add_test( NAME sample COMMAND sl3_sample ) 23 | add_test( NAME sample1 COMMAND sl3_sample1 ) 24 | add_test( NAME sample2 COMMAND sl3_sample2 ) 25 | add_test( NAME sample3 COMMAND sl3_sample3 ) 26 | add_test( NAME sample4 COMMAND sl3_sample4 ) 27 | add_test( NAME sample5 COMMAND sl3_sample5 ) 28 | add_test( NAME sample6 COMMAND sl3_sample6 ) 29 | 30 | endif(NOT CODECOVERAGE) 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/sample/main5.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int 5 | main () 6 | { 7 | using namespace sl3; 8 | // define a db 9 | Database db (":memory:"); 10 | // run commands against the db 11 | db.execute ("CREATE TABLE tbl(f1 INTEGER, f2 TEXT, f3 REAL);"); 12 | // create a command with parameters 13 | auto cmd = db.prepare ("INSERT INTO tbl (f1, f2, f3) VALUES (?,?,?);"); 14 | // no types so we use variants 15 | // insert Int, Text, Real 16 | cmd.execute (parameters (1, "one", 1.1)); 17 | // insert Text, Text, Real 18 | cmd.execute (parameters ("some text", "two", 2.1)); 19 | 20 | // this will throw since types in column0 are different 21 | try 22 | { 23 | Dataset ds = db.select ("SELECT * FROM tbl;", 24 | {Type::Int, Type::Text, Type::Real}); 25 | } 26 | catch (const Error& e) 27 | { 28 | std::cout << e << std::endl; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/sample/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ADD_EXECUTABLE( sl3_sample main.cpp ) 5 | TARGET_LINK_LIBRARIES( sl3_sample PRIVATE sl3 ${LIBWARNINGS}) 6 | 7 | ADD_EXECUTABLE( sl3_sample1 main1.cpp ) 8 | TARGET_LINK_LIBRARIES( sl3_sample1 PRIVATE sl3 ${LIBWARNINGS}) 9 | 10 | ADD_EXECUTABLE( sl3_sample2 main2.cpp ) 11 | TARGET_LINK_LIBRARIES( sl3_sample2 PRIVATE sl3 ${LIBWARNINGS}) 12 | 13 | ADD_EXECUTABLE( sl3_sample3 main3.cpp ) 14 | TARGET_LINK_LIBRARIES( sl3_sample3 PRIVATE sl3 ${LIBWARNINGS}) 15 | 16 | ADD_EXECUTABLE( sl3_sample4 main4.cpp ) 17 | TARGET_LINK_LIBRARIES( sl3_sample4 PRIVATE sl3 ${LIBWARNINGS}) 18 | 19 | ADD_EXECUTABLE( sl3_sample5 main5.cpp ) 20 | TARGET_LINK_LIBRARIES( sl3_sample5 PRIVATE sl3 ${LIBWARNINGS}) 21 | 22 | ADD_EXECUTABLE( sl3_sample6 main6.cpp ) 23 | TARGET_LINK_LIBRARIES( sl3_sample6 PRIVATE sl3 ${LIBWARNINGS}) 24 | 25 | ADD_EXECUTABLE( sl3_sample7 main7.cpp ) 26 | TARGET_LINK_LIBRARIES( sl3_sample7 PRIVATE sl3 ${LIBWARNINGS}) 27 | -------------------------------------------------------------------------------- /cmake/lib/doxydoc.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | 3 | find_package( Doxygen QUIET ) 4 | if(NOT DOXYGEN_FOUND) 5 | message(STATUS "Doxygen not found, doc target not available") 6 | return() 7 | endif() 8 | 9 | include(FetchContent) 10 | FetchContent_Declare( 11 | doxygen-awesome-css 12 | URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v2.4.0.zip 13 | ) 14 | FetchContent_MakeAvailable(doxygen-awesome-css) 15 | 16 | FetchContent_GetProperties(doxygen-awesome-css SOURCE_DIR AWESOME_CSS_DIR) 17 | 18 | if(DOT) 19 | set(HAVE_DOT YES) 20 | else(DOT) 21 | set(HAVE_DOT NO) 22 | endif(DOT) 23 | 24 | configure_file(${PROJECT_SOURCE_DIR}/doc/doxyfile.cmake 25 | ${PROJECT_BINARY_DIR}/doc/${PROJECT_NAME}.doxyfile 26 | ) 27 | 28 | #todo, naming, would like to have doxydoc in all projects 29 | #add_custom_target(doc${PROJECT_NAME} ${DOXYGEN} 30 | add_custom_target(doc ${DOXYGEN} 31 | ${PROJECT_BINARY_DIR}/doc/${PROJECT_NAME}.doxyfile 32 | ) 33 | -------------------------------------------------------------------------------- /cmake/preset/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 8, 3 | "configurePresets": [ 4 | { 5 | "name": "base", 6 | "hidden": true, 7 | "generator": "Ninja Multi-Config", 8 | "binaryDir": "${sourceDir}/build/${hostSystemName}/${presetName}", 9 | "toolchainFile": "${sourceDir}/cmake/toolchain/router.cmake", 10 | "cacheVariables": { 11 | "CMAKE_COMPILE_WARNING_AS_ERROR": "ON", 12 | "ACTIVE_PRESET_NAME": "${presetName}", 13 | "CMAKE_MODULE_PATH": "${sourceDir}/cmake", 14 | "CMAKE_PROJECT_INCLUDE": "project-setup" 15 | }, 16 | "environment": { 17 | } 18 | } 19 | ], 20 | "testPresets": [ 21 | { 22 | "name": "base", 23 | "configurePreset": "base", 24 | "configuration": "Debug", 25 | "output": { 26 | "outputOnFailure": true 27 | }, 28 | "execution": { 29 | "noTestsAction": "error", 30 | "stopOnFailure": true 31 | } 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /cmake/lib/install.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | 3 | include(GNUInstallDirs) 4 | 5 | install(TARGETS sl3 EXPORT sl3Targets) 6 | INSTALL(FILES ${sl3_HDR} DESTINATION include/sl3) 7 | INSTALL(FILES include/sl3.hpp DESTINATION include/sl3) 8 | 9 | install(EXPORT sl3Targets 10 | FILE sl3Targets.cmake 11 | NAMESPACE a4z:: 12 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" 13 | ) 14 | 15 | include(CMakePackageConfigHelpers) 16 | write_basic_package_version_file( 17 | ${CMAKE_CURRENT_BINARY_DIR}/sl3ConfigVersion.cmake 18 | COMPATIBILITY AnyNewerVersion 19 | ) 20 | configure_package_config_file( 21 | ${CMAKE_CURRENT_LIST_DIR}/sl3Config.cmake.in 22 | ${CMAKE_CURRENT_BINARY_DIR}/sl3Config.cmake 23 | INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" 24 | ) 25 | 26 | install( 27 | FILES 28 | ${CMAKE_CURRENT_BINARY_DIR}/sl3Config.cmake 29 | ${CMAKE_CURRENT_BINARY_DIR}/sl3ConfigVersion.cmake 30 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" 31 | 32 | ) 33 | 34 | 35 | -------------------------------------------------------------------------------- /cmake/preset/ninja.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 8, 3 | "include": ["base.json"], 4 | "configurePresets": [ 5 | { 6 | "name": "ninja", 7 | "inherits": "base" 8 | } 9 | ], 10 | "buildPresets": [ 11 | { 12 | "name": "ninja", 13 | "configurePreset": "ninja", 14 | "configuration": "Debug" 15 | }, 16 | { 17 | "name": "ninja-release", 18 | "configurePreset": "ninja", 19 | "configuration": "Release" 20 | } 21 | ], 22 | "testPresets": [ 23 | { 24 | "name": "ninja", 25 | "inherits": "base", 26 | "configurePreset": "ninja" 27 | }, 28 | { 29 | "name": "ninja-release", 30 | "inherits": "ninja", 31 | "configuration": "Release" 32 | } 33 | ], 34 | "workflowPresets": [ 35 | { 36 | "name": "ninja", 37 | "steps": [ 38 | { 39 | "type": "configure", 40 | "name": "ninja" 41 | }, 42 | { 43 | "type": "build", 44 | "name": "ninja" 45 | }, 46 | { 47 | "type": "test", 48 | "name": "ninja" 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /cmake/toolchain/router.cmake: -------------------------------------------------------------------------------- 1 | # set(CMAKE_CXX_STANDARD_REQUIRED ON) 2 | # set(CMAKE_CXX_EXTENSIONS OFF) 3 | # set(CMAKE_CXX_STANDARD 23) 4 | 5 | # set(CMAKE_C_STANDARD_REQUIRED ON) 6 | # set(CMAKE_C_EXTENSIONS OFF) 7 | # set(CMAKE_C_STANDARD 17) 8 | 9 | # set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 10 | 11 | find_program(CCACHE_BIN ccache) 12 | if(CCACHE_BIN) 13 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_BIN}) 14 | set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_BIN}) 15 | message("ccache binary foud, ccache enabled") 16 | endif() 17 | 18 | 19 | if(DEFINED TOOLCHAIN_INCLUDES) 20 | foreach(TOOLCHAIN_INCLUDE ${TOOLCHAIN_INCLUDES}) 21 | message(STATUS "Include toolchain : ${TOOLCHAIN_INCLUDE}") 22 | include(${TOOLCHAIN_INCLUDE}) 23 | endforeach() 24 | endif() 25 | 26 | message(STATUS "-- Done with the toolchain router") 27 | 28 | # TODO, do I want to allow env variables also? 29 | # probably not since that might confuse 30 | # if(DEFINED ENV{TOOLCHAIN_ADDONS}) 31 | # foreach(TC $ENV{TOOLCHAIN_ADDONS}) 32 | # message(STATUS "Add toolchain addon (env): ${TC}") 33 | # include(${TC}) 34 | # endforeach() 35 | # endif() 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/sample/main7.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | /* 8 | this is the only example that currently works with memory sanitizer 9 | I keep that here, not sure about how to adjust the other examples 10 | 11 | also, the problem with std::endl seems odd, maybe an alpine arm issue 12 | */ 13 | 14 | int 15 | main () 16 | { 17 | using namespace sl3; 18 | // for memory sanitizer, this is needed at least on alpine arm 19 | std::cout << std::endl; 20 | std::cout.flush (); 21 | 22 | Database db (":memory:"); 23 | 24 | db.execute ("CREATE TABLE tbl(f1 INTEGER, f2 TEXT, f3 REAL);" 25 | "INSERT INTO tbl (f1, f2, f3) VALUES (1, 'one', 1.1);" 26 | "INSERT INTO tbl (f1, f2, f3) VALUES (2, 'two', 2.2)"); 27 | 28 | db.execute ("SELECT f1, f2 FROM tbl;", [] (Columns cols) { 29 | assert (cols.count () == 2); 30 | assert (cols.getType (0) == Type::Int); 31 | assert (cols.getType (1) == Type::Text); 32 | 33 | std::cout << std::to_string (cols.getInt (0)) << "_" << cols.getText (1) 34 | << std::endl; 35 | return true; 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /cmake/preset/xcode.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 8, 3 | "include": ["base.json"], 4 | "configurePresets": [ 5 | { 6 | "name": "xcode", 7 | "generator": "Xcode", 8 | "inherits": "base" 9 | } 10 | ], 11 | "buildPresets": [ 12 | { 13 | "name": "xcode", 14 | "configurePreset": "xcode", 15 | "configuration": "Debug" 16 | }, 17 | { 18 | "name": "xcode-release", 19 | "configurePreset": "xcode", 20 | "configuration": "Release" 21 | } 22 | ], 23 | "testPresets": [ 24 | { 25 | "name": "xcode", 26 | "inherits": "base", 27 | "configurePreset": "xcode" 28 | }, 29 | { 30 | "name": "xcode-release", 31 | "inherits": "xcode", 32 | "configuration": "Release" 33 | } 34 | ], 35 | "workflowPresets": [ 36 | { 37 | "name": "xcode", 38 | "steps": [ 39 | { 40 | "type": "configure", 41 | "name": "xcode" 42 | }, 43 | { 44 | "type": "build", 45 | "name": "xcode" 46 | }, 47 | { 48 | "type": "test", 49 | "name": "xcode" 50 | } 51 | ] 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /cmake/add-on/coverage-clang.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | 3 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") 4 | message(STATUS "-- Clang Coverage enabled, CMAKE_CXX_COMPILER:" ${CMAKE_CXX_COMPILER}) 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate -fcoverage-mapping") 6 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-instr-generate -fcoverage-mapping") 7 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-instr-generate") 8 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fprofile-instr-generate") 9 | else() 10 | message(FATAL_ERROR "Clang Coverage is only supported with Clang") 11 | endif() 12 | 13 | set(COVERAGE_TOOL "llvm-profdata") 14 | 15 | add_custom_target(coverage 16 | COMMAND ${CMAKE_COMMAND} -E env "PATH=$ENV{PATH}" 17 | ${CMAKE_CURRENT_LIST_DIR}/coverage-report-clang.sh --build-dir ${CMAKE_BINARY_DIR} 18 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 19 | COMMENT "Generating coverage report using coverage-report-clang.sh" 20 | ) 21 | 22 | add_custom_target(clean_coverage 23 | COMMAND find . -name '*.profraw' -delete 24 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 25 | ) 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/sample/main1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int 6 | main () 7 | { 8 | using namespace sl3; 9 | // define a db 10 | Database db (":memory:"); 11 | // run commands against the db 12 | db.execute ("CREATE TABLE tbl(f1 INTEGER, f2 TEXT, f3 REAL);"); 13 | // create a command with parameters 14 | auto cmd = db.prepare ("INSERT INTO tbl (f1, f2, f3) VALUES (?,?,?);"); 15 | 16 | // add some data 17 | cmd.execute (parameters (1, "one", 1.1)); 18 | cmd.execute (parameters (2, "two", 2.1)); 19 | 20 | // access the data 21 | Dataset ds = db.select ("SELECT * FROM tbl;"); 22 | 23 | // Dataset is a container 24 | assert (ds.size () == 2); 25 | 26 | // Dataset row is a container 27 | auto row = ds[0]; 28 | assert (row.size () == 3); 29 | assert (row[0].type () == Type::Int); 30 | assert (row[1].type () == Type::Text); 31 | assert (row[2].type () == Type::Real); 32 | 33 | // of course there is also iterator access 34 | for (const auto& r : ds) 35 | { 36 | for (const auto& field : r) 37 | { 38 | std::cout << field << " "; 39 | } 40 | std::cout << std::endl; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /CMakeUserPresetsExample.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 8, 3 | "include": [ 4 | "cmake/preset/ninja.json" 5 | 6 | ] , 7 | "configurePresets": [ 8 | { 9 | "name": "memsan", 10 | "inherits": "ninja", 11 | "cacheVariables": { 12 | "TOOLCHAIN_INCLUDES": "toolchain/linux-clang;toolchain/linux-clang-san-mem", 13 | "PROJECT_ADDONS": "add-on/fetch-dependencies", 14 | "sl3_USE_INTERNAL_SQLITE3": "ON" 15 | } 16 | }, 17 | { 18 | "name": "memsan-int", 19 | "inherits": "ninja", 20 | "cacheVariables": { 21 | "TOOLCHAIN_INCLUDES": "toolchain/linux-clang;toolchain/linux-clang-san-mem", 22 | "PROJECT_ADDONS": "add-on/fetch-dependencies", 23 | "sl3_USE_INTERNAL_SQLITE3": "OFF" 24 | } 25 | } 26 | ], 27 | "buildPresets": [ 28 | { 29 | "name": "memsan", 30 | "configurePreset": "memsan", 31 | "configuration": "Debug" 32 | }, 33 | { 34 | "name": "memsan-int", 35 | "configurePreset": "memsan-int", 36 | "configuration": "Debug" 37 | } 38 | ], 39 | "testPresets": [ 40 | { 41 | "name": "memsan", 42 | "inherits": "ninja", 43 | "configurePreset": "memsan" 44 | } 45 | ] 46 | } 47 | 48 | -------------------------------------------------------------------------------- /cmake/project-setup.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | # Keep this here for now, but add a CI test that builds with 20 or 17 3 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 4 | set(CMAKE_CXX_EXTENSIONS OFF) 5 | set(CMAKE_CXX_STANDARD 23) 6 | 7 | set(CMAKE_C_STANDARD_REQUIRED ON) 8 | set(CMAKE_C_EXTENSIONS OFF) 9 | set(CMAKE_C_STANDARD 17) 10 | 11 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 12 | 13 | if(PROJECT_IS_TOP_LEVEL) 14 | # make git ignore the build directory 15 | file(WRITE ${CMAKE_BINARY_DIR}/.gitignore "*") 16 | include(CTest) 17 | option(sl3_BUILD_TESTING "Build the tests" ${BUILD_TESTING}) 18 | endif() 19 | 20 | 21 | if(DEFINED ACTIVE_PRESET_NAME) 22 | message(STATUS "-- Running preset : ${ACTIVE_PRESET_NAME}") 23 | endif() 24 | 25 | 26 | 27 | if(DEFINED PROJECT_ADDONS) 28 | foreach(ADDON ${PROJECT_ADDONS}) 29 | message(STATUS "Add project addon: ${ADDON}") 30 | include(${ADDON}) 31 | endforeach() 32 | endif() 33 | 34 | 35 | # TODO, do I want to allow env variables also? 36 | # probably not since that might confuse 37 | # if(DEFINED ENV{MY_CMAKE_PROJECT_ADDONS}) 38 | # foreach(ADDON $ENV{MY_CMAKE_PROJECT_ADDONS}) 39 | # message(STATUS "Add project addon (env): ${ADDON}") 40 | # include(${ADDON}) 41 | # endforeach() 42 | # endif() 43 | -------------------------------------------------------------------------------- /cmake/preset/msvc22.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 8, 3 | "include": ["base.json"], 4 | "configurePresets": [ 5 | { 6 | "name": "msvc22", 7 | "inherits": "base", 8 | "generator": "Visual Studio 17 2022", 9 | "cacheVariables": { 10 | "TOOLCHAIN_INCLUDES": "toolchain/use-vcpkg" 11 | } 12 | } 13 | ], 14 | "buildPresets": [ 15 | { 16 | "name": "msvc22", 17 | "configurePreset": "msvc22", 18 | "configuration": "Debug", 19 | "jobs": 0 20 | }, 21 | { 22 | "name": "msvc22-release", 23 | "configurePreset": "msvc22", 24 | "configuration": "Release", 25 | "jobs": 0 26 | } 27 | ], 28 | "testPresets": [ 29 | { 30 | "name": "msvc22", 31 | "inherits": "base", 32 | "configurePreset": "msvc22" 33 | }, 34 | { 35 | "name": "msvc22-release", 36 | "inherits": "msvc22", 37 | "configuration": "Release" 38 | } 39 | ], 40 | "workflowPresets": [ 41 | { 42 | "name": "msvc22", 43 | "steps": [ 44 | { 45 | "type": "configure", 46 | "name": "msvc22" 47 | }, 48 | { 49 | "type": "build", 50 | "name": "msvc22" 51 | }, 52 | { 53 | "type": "test", 54 | "name": "msvc22" 55 | } 56 | ] 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /tests/rowcallback/rowcallbacktest_coverage.cpp: -------------------------------------------------------------------------------- 1 | #include "../testing.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | SCENARIO ("for coverage") 9 | { 10 | GIVEN ("a record with known data and known types") 11 | { 12 | sl3::Database db{":memory:"}; 13 | auto sql = "SELECT 'b' as byte, NULL as noval; "; 14 | REQUIRE_NOTHROW (db.execute (sql)); 15 | WHEN ("asking for the used storegae space") 16 | { 17 | THEN ("some int will use some space, and null non space") 18 | { 19 | db.execute (sql, [] (sl3::Columns cols) { 20 | CHECK (cols.getSize (0) == 1); 21 | CHECK (cols.getSize (1) == 0); 22 | return true; 23 | }); 24 | } 25 | } 26 | WHEN ("geting an int from a null value") 27 | { 28 | THEN ("sqlite will convert it to 0") 29 | { 30 | db.execute (sql, [] (sl3::Columns cols) { 31 | CHECK (cols.getInt (1) == 0); 32 | return true; 33 | }); 34 | } 35 | } 36 | 37 | WHEN ("want accecc to shoot myslef into the foot") 38 | { 39 | THEN ("this is possible") 40 | { 41 | db.execute (sql, [] (sl3::Columns cols) { 42 | CHECK (cols.get_stmt () != nullptr); 43 | return true; 44 | }); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/build-internal-sqlite.yml: -------------------------------------------------------------------------------- 1 | name: build with internal SQLite 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | build: 13 | - { 14 | os: ubuntu-latest, 15 | preset: ninja, 16 | 17 | } 18 | - { 19 | os: macos-latest, 20 | preset: xcode, 21 | 22 | } 23 | - { 24 | os: windows-2022, 25 | preset: msvc22, 26 | 27 | } 28 | runs-on: ${{ matrix.build.os }} 29 | steps: 30 | - uses: lukka/get-cmake@latest 31 | with: 32 | cmakeVersion: "~3.30.0" # <--= optional, use most recent 3.25.x version 33 | ninjaVersion: "^1.11.1" # <--= optional, use most recent 1.x version 34 | - name: Checkout repository 35 | uses: actions/checkout@v4 36 | - name: Setup MSVC 37 | if: startsWith(matrix.build.os, 'windows') 38 | uses: ilammy/msvc-dev-cmd@v1 39 | - name: Configure project 40 | run: cmake --preset ${{ matrix.build.preset }} -DPROJECT_ADDONS='add-on/fetch-dependencies' -Dsl3_USE_INTERNAL_SQLITE3=ON 41 | - name: Build project 42 | run: cmake --build --preset ${{ matrix.build.preset }}-release --parallel --verbose 43 | - name: Run unit test 44 | run: ctest --preset ${{ matrix.build.preset }}-release --parallel 45 | 46 | 47 | -------------------------------------------------------------------------------- /BAZEL_SETUP_CONTEXT.md: -------------------------------------------------------------------------------- 1 | # Bazel Setup Context for libsl3 2 | 3 | ## Project Status 4 | 5 | Successfully added Bazel 8.4+ build system alongside existing CMake. All core functionality working. 6 | 7 | ## Current Status 8 | 9 | - ✅ Library builds: `bazel build --config=release //:sl3` 10 | - ✅ all tests pass 11 | - ✅ Sample programs build successfully 12 | 13 | ## Key Commands 14 | 15 | ```bash 16 | # Build library 17 | bazel build ... 18 | bazel build -c dbg ... 19 | bazel build -c opt ... 20 | 21 | bazel test ... 22 | # apply configs as wanted 23 | 24 | # Debug compile commands 25 | bazel aquery --config=debug //tests/version:version_test --output=text 26 | ``` 27 | 28 | ## Outstanding Issues 29 | 30 | 1. **Version test fails** - SQLite runtime vs compile-time version mismatch (3.43.2 vs 3.47.2) 31 | 2. **Need toolchain setup** - Add dedicated Xcode and GCC (brew) toolchains 32 | 3. **VS Code IntelliSense** - Add hedron_compile_commands for C++ language support 33 | 34 | ## Next Steps 35 | 36 | Note that the order will defined, not as written here. 37 | 38 | - Add hedron_compile_commands to MODULE.bazel for VS Code IntelliSense 39 | - Add Xcode toolchain configuration 40 | - Add GCC (brew) toolchain configuration 41 | - Test builds with different toolchains 42 | 43 | ## Architecture 44 | 45 | - Uses modern Bazel Bzlmod (MODULE.bazel) instead of deprecated WORKSPACE.bazel 46 | - Parallel CMake/Bazel build support (no conflicts) 47 | -------------------------------------------------------------------------------- /tests/test_main.cpp: -------------------------------------------------------------------------------- 1 | // #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | // #include "doctest.h" 3 | 4 | #define DOCTEST_CONFIG_IMPLEMENT 5 | #include "doctest/doctest.h" 6 | 7 | int 8 | main (int argc, char** argv) 9 | { 10 | // environment setup give me the code that should stand here 11 | 12 | doctest::Context context; // initialize 13 | 14 | // defaults 15 | // context.addFilter("test-case-exclude", "*math*"); // exclude test cases 16 | // with "math" in their name 17 | context.setOption ("no-breaks", 18 | true); // don't break in the debugger when assertions fail 19 | 20 | context.applyCommandLine (argc, argv); 21 | 22 | // overrides 23 | context.setOption ("abort-after", 24 | 5); // stop test execution after 5 failed assertions 25 | // context.setOption("sort", "name"); // sort the test cases by their name 26 | 27 | int res = context.run (); // run 28 | 29 | if (context.shouldExit ()) // important - query flags (and --exit) rely on 30 | // the user doing this 31 | return res; // propagate the result of the tests 32 | 33 | int client_stuff_return_code = 0; 34 | // your program - if the testing framework is integrated in your production 35 | // code 36 | 37 | return res + client_stuff_return_code; // the result from doctest is 38 | // propagated here as well 39 | } 40 | -------------------------------------------------------------------------------- /tests/sample/main3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | int 7 | main () 8 | { 9 | using namespace sl3; 10 | // define a db 11 | Database db (":memory:"); 12 | // run commands against the db 13 | db.execute ("CREATE TABLE tbl(f1 INTEGER, f2 TEXT, f3 REAL);"); 14 | // create a command with parameters 15 | auto cmd = db.prepare ("INSERT INTO tbl (f1, f2, f3) VALUES (?,?,?);"); 16 | // no types so we use variants 17 | // so this will work 18 | cmd.execute (parameters (1, "one", 1.1)); 19 | // and this will also work 20 | cmd.execute (parameters ("some text", "two", 2.1)); 21 | 22 | // access the data 23 | Dataset ds = db.select ("SELECT * FROM tbl;"); 24 | 25 | assert (ds.size () == 2); // 2 records 26 | assert (ds[0].size () == 3); // 3 fields 27 | 28 | // first row first field is a integer, as inserted 29 | assert (ds[0][0].dbtype () == Type::Variant); 30 | assert (ds[0][0].type () == Type::Int); 31 | // second row first field is text, as inserted 32 | assert (ds[1][0].dbtype () == Type::Variant); 33 | assert (ds[1][0].type () == Type::Text); 34 | 35 | // of course we can work wit the values 36 | for (const auto& row : ds) 37 | { 38 | for (const auto& field : row) 39 | { 40 | std::cout << typeName (field.dbtype ()) << "/" 41 | << typeName (field.type ()) << ": " << field << ", "; 42 | } 43 | std::cout << std::endl; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/sl3/config.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace sl3 16 | { 17 | //---------------------------------------------------------------------------- 18 | // implement functions from config.h 19 | const char* 20 | sqliteCompiledVersion () 21 | { 22 | return SQLITE_VERSION; 23 | } 24 | int 25 | sqliteCompiledVersionNumber () 26 | { 27 | return SQLITE_VERSION_NUMBER; 28 | } 29 | 30 | const char* 31 | sqliteRuntimeVersion () 32 | { 33 | return sqlite3_libversion (); 34 | } 35 | 36 | int 37 | sqliteRuntimeVersionNumber () 38 | { 39 | return sqlite3_libversion_number (); 40 | } 41 | 42 | int 43 | sqliteThreadSafeCompileOption () 44 | { 45 | return sqlite3_threadsafe (); 46 | } 47 | //---------------------------------------------------------------------------- 48 | 49 | } // ns 50 | -------------------------------------------------------------------------------- /src/sl3/types.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace sl3 14 | { 15 | std::string 16 | typeName (Type type) 17 | { 18 | switch (type) 19 | { 20 | case Type::Null: 21 | return "Null"; 22 | break; 23 | case Type::Int: 24 | return "Int"; 25 | break; 26 | case Type::Real: 27 | return "Real"; 28 | break; 29 | case Type::Text: 30 | return "Text"; 31 | break; 32 | case Type::Blob: 33 | return "Blob"; 34 | break; 35 | case Type::Variant: 36 | return "Variant"; 37 | break; 38 | } 39 | 40 | return "unknown"; // LCOV_EXCL_LINE 41 | } 42 | 43 | std::ostream& 44 | operator<< (std::ostream& os, const Type& t) 45 | { 46 | os << "Type::" << typeName (t); 47 | return os; 48 | } 49 | 50 | void 51 | Types::swap (Types& other) noexcept 52 | { 53 | using std::swap; 54 | swap (_cont, other._cont); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_library") 2 | load("//:config.bzl", "sl3_config") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | # Generate config header using cross-platform Starlark 7 | sl3_config( 8 | name = "generate_config", 9 | output = "include/sl3/config.hpp", 10 | template = "src/config.in", 11 | version = module_version(), 12 | ) 13 | 14 | cc_library( 15 | name = "sl3", 16 | srcs = [ 17 | "src/sl3/columns.cpp", 18 | "src/sl3/command.cpp", 19 | "src/sl3/config.cpp", 20 | "src/sl3/database.cpp", 21 | "src/sl3/dataset.cpp", 22 | "src/sl3/dbvalue.cpp", 23 | "src/sl3/dbvalues.cpp", 24 | "src/sl3/error.cpp", 25 | "src/sl3/rowcallback.cpp", 26 | "src/sl3/types.cpp", 27 | "src/sl3/value.cpp", 28 | # Private headers 29 | "src/sl3/connection.hpp", 30 | "src/sl3/utils.hpp", 31 | ], 32 | hdrs = [ 33 | "include/sl3.hpp", 34 | "include/sl3/columns.hpp", 35 | "include/sl3/command.hpp", 36 | "include/sl3/container.hpp", 37 | "include/sl3/database.hpp", 38 | "include/sl3/dataset.hpp", 39 | "include/sl3/dbvalue.hpp", 40 | "include/sl3/dbvalues.hpp", 41 | "include/sl3/error.hpp", 42 | "include/sl3/rowcallback.hpp", 43 | "include/sl3/types.hpp", 44 | "include/sl3/value.hpp", 45 | ":generate_config", 46 | ], 47 | includes = [ 48 | "include", 49 | ], 50 | deps = [ 51 | "@sqlite//:sqlite3", 52 | ], 53 | ) 54 | -------------------------------------------------------------------------------- /config.bzl: -------------------------------------------------------------------------------- 1 | """Configuration generation for libsl3""" 2 | 3 | def _generate_config_impl(ctx): 4 | """Implementation for config header generation""" 5 | 6 | # Parse version string (format: 1.2.47002) 7 | version_parts = ctx.attr.version.split(".") 8 | if len(version_parts) != 3: 9 | fail("Version must be in format MAJOR.MINOR.PATCH (e.g., 1.2.47002)") 10 | 11 | major = version_parts[0] 12 | minor = version_parts[1] 13 | patch = int(version_parts[2]) 14 | 15 | # Extract SQLite version from patch (47002 -> minor=47, patch=2) 16 | sqlite_minor = patch // 1000 17 | sqlite_patch = patch % 1000 18 | 19 | # Define substitutions 20 | substitutions = { 21 | "${sl3_MAJOR_VERSION}": major, 22 | "${sl3_MINOR_VERSION}": minor, 23 | "${sl3_PATCH_VERSION}": str(patch), 24 | "${sl3_VERSION}": ctx.attr.version, 25 | "${internal_SQLITE_MINOR_V}": str(sqlite_minor), 26 | "${internal_SQLITE_PATCH_V}": str(sqlite_patch), 27 | } 28 | 29 | # Use expand_template action instead of manual file reading 30 | ctx.actions.expand_template( 31 | template = ctx.file.template, 32 | output = ctx.outputs.config_header, 33 | substitutions = substitutions, 34 | ) 35 | 36 | generate_config = rule( 37 | implementation = _generate_config_impl, 38 | attrs = { 39 | "template": attr.label(allow_single_file = True, mandatory = True), 40 | "version": attr.string(mandatory = True), 41 | "config_header": attr.output(mandatory = True), 42 | }, 43 | ) 44 | 45 | def sl3_config(name, template, version, output): 46 | """Generate sl3 config header from template""" 47 | generate_config( 48 | name = name, 49 | template = template, 50 | version = version, 51 | config_header = output, 52 | ) 53 | -------------------------------------------------------------------------------- /cmake/lib/testing.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | 3 | 4 | find_package(doctest CONFIG REQUIRED) 5 | 6 | set(CON_DOCTEST doctest::doctest) 7 | 8 | add_library(doctest_main OBJECT ${PROJECT_SOURCE_DIR}/tests/test_main.cpp) 9 | 10 | target_link_libraries(doctest_main PUBLIC ${CON_DOCTEST}) 11 | 12 | function (add_doctest NAME) 13 | 14 | set(option_args WILL_FAIL) 15 | set(value_args TIMEOUT) 16 | set(list_args SOURCES) 17 | 18 | cmake_parse_arguments(D_TEST 19 | "${option_args}" "${value_args}" "${list_args}" 20 | ${ARGN} 21 | ) 22 | 23 | add_executable(sl3test-${NAME} ${D_TEST_SOURCES} $) 24 | # use naming what we had for now 25 | target_link_libraries(sl3test-${NAME} 26 | PRIVATE 27 | sl3 28 | ${CON_DOCTEST} 29 | $ 30 | ) 31 | #target_link_libraries(sl3test-${NAME} PRIVATE $) 32 | 33 | if(NOT D_TEST_TIMEOUT) 34 | set(D_TEST_TIMEOUT 3) 35 | endif() 36 | if(NOT D_TEST_WILL_FAIL) 37 | set(D_TEST_WILL_FAIL OFF) 38 | endif() 39 | 40 | # use naming what we had for now 41 | add_test(NAME sl3test_${NAME} COMMAND sl3test-${NAME}) 42 | # with clang, we use this to get coverage 43 | if(COVERAGE_TOOL STREQUAL "llvm-profdata") 44 | # important, the profile file shall have the same name as the test binary, not the test name (cmake test name) 45 | set_tests_properties(sl3test_${NAME} PROPERTIES ENVIRONMENT "LLVM_PROFILE_FILE=sl3test-${NAME}.profraw") 46 | endif() 47 | 48 | 49 | set_tests_properties(sl3test_${NAME} 50 | PROPERTIES 51 | TIMEOUT ${D_TEST_TIMEOUT} 52 | WILL_FAIL ${D_TEST_WILL_FAIL} 53 | ) 54 | 55 | endfunction(add_doctest) 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /cmake/lib/find_sqlite.cmake: -------------------------------------------------------------------------------- 1 | 2 | #[[ 3 | 4 | in an optimal world, this would just work: 5 | 6 | find_package(SQLite3 REQUIRED) 7 | target_link_libraries(sl3 PUBLIC ${SQLite3_LIBRARIES}) 8 | target_include_directories(sl3 SYSTEM BEFORE PRIVATE ${SQLite3_INCLUDE_DIRS}) 9 | 10 | but it does not 11 | 12 | this happened on Mac CI, 13 | 14 | /Users/runner/work/libsl3/libsl3/tests/version/versiontest.cpp:6: 15 | Scenario: check sqlite versions for library and app 16 | Given: cmake genertated config 17 | When: comparing the compile/runtime version numbers 18 | Then: they are the same 19 | 20 | /Users/runner/work/libsl3/libsl3/tests/version/versiontest.cpp:27: ERROR: CHECK_EQ( sl3::sqliteCompiledVersionNumber (), sl3::sqliteRuntimeVersionNumber () ) is NOT correct! 21 | values: CHECK_EQ( 3026000, 3043002 ) 22 | 23 | ~~~~~ 24 | ~~~~~ 25 | 26 | package-manager could be helpful, but are sometimes not 27 | 28 | vcpkg, for example, does the following: 29 | ------ 30 | sqlite3 provides pkgconfig bindings. 31 | sqlite3 provides CMake targets: 32 | 33 | find_package(unofficial-sqlite3 CONFIG REQUIRED) 34 | target_link_libraries(main PRIVATE unofficial::sqlite3::sqlite3) 35 | ------ 36 | 37 | How to deliver a library that should work / be able to recompile in multiple contexts 38 | 39 | What if conan is used, instead of vcpkg 40 | Or someone whats to rely whats on the system (root fs on x compile for yocto) 41 | 42 | # ]] 43 | 44 | 45 | include(lib/debug) 46 | #print_all_variables() 47 | 48 | if (sl3_USE_INTERNAL_SQLITE3) 49 | include( sqlite/setup_sqlite3.cmake ) 50 | else(sl3_USE_INTERNAL_SQLITE3) 51 | if (_VCPKG_INSTALLED_DIR) 52 | find_package(unofficial-sqlite3 REQUIRED) 53 | set(SQLITE_LINK_NAME unofficial::sqlite3::sqlite3) 54 | else() 55 | find_package(SQLite3 REQUIRED) 56 | set(SQLITE_LINK_NAME SQLite::SQLite3) 57 | endif() 58 | endif(sl3_USE_INTERNAL_SQLITE3) 59 | 60 | # print_variable(SQLite3_INCLUDE_DIR) 61 | # print_variable(SQLite3_LIBRARY) 62 | 63 | -------------------------------------------------------------------------------- /tests/typenames/typenamestest.cpp: -------------------------------------------------------------------------------- 1 | #include "../testing.hpp" 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | SCENARIO ("check typenames") 9 | { 10 | using namespace sl3; 11 | GIVEN ("the sqlite version libsl3 was comiled with") 12 | { 13 | std::string libUsedVersion = sl3::sqliteCompiledVersion (); 14 | 15 | WHEN ("using the typename function") 16 | { 17 | std::string appUsedVersion = sl3::sqliteRuntimeVersion (); 18 | 19 | THEN ("the short enum name is returned") 20 | { 21 | CHECK_EQ (typeName (Type::Int), "Int"); 22 | CHECK_EQ (typeName (Type::Real), "Real"); 23 | CHECK_EQ (typeName (Type::Text), "Text"); 24 | CHECK_EQ (typeName (Type::Blob), "Blob"); 25 | CHECK_EQ (typeName (Type::Null), "Null"); 26 | CHECK_EQ (typeName (Type::Variant), "Variant"); 27 | } 28 | } 29 | 30 | WHEN ("getting typnmes in a stream") 31 | { 32 | std::string appUsedVersion = sl3::sqliteRuntimeVersion (); 33 | 34 | THEN ("the full enum name in in the stream") 35 | { 36 | { 37 | std::stringstream ss; 38 | ss << Type::Int; 39 | CHECK_EQ (ss.str (), "Type::Int"); 40 | } 41 | { 42 | std::stringstream ss; 43 | ss << Type::Real; 44 | CHECK_EQ (ss.str (), "Type::Real"); 45 | } 46 | { 47 | std::stringstream ss; 48 | ss << Type::Text; 49 | CHECK_EQ (ss.str (), "Type::Text"); 50 | } 51 | { 52 | std::stringstream ss; 53 | ss << Type::Blob; 54 | CHECK_EQ (ss.str (), "Type::Blob"); 55 | } 56 | { 57 | std::stringstream ss; 58 | ss << Type::Variant; 59 | CHECK_EQ (ss.str (), "Type::Variant"); 60 | } 61 | { 62 | std::stringstream ss; 63 | ss << Type::Null; 64 | CHECK_EQ (ss.str (), "Type::Null"); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/version/versiontest.cpp: -------------------------------------------------------------------------------- 1 | #include "../testing.hpp" 2 | #include 3 | 4 | #include 5 | 6 | // believe it or not, this is actually usefull, 7 | // there are often many different version of sqlite on a system 8 | // and which one cmake picks up depends ... so you want to know about that 9 | 10 | /* 11 | 12 | this happened on Mac CI, 13 | 14 | /Users/runner/work/libsl3/libsl3/tests/version/versiontest.cpp:6: 15 | Scenario: check sqlite versions for library and app 16 | Given: cmake genertated config 17 | When: comparing the compile/runtime version numbers 18 | Then: they are the same 19 | 20 | /Users/runner/work/libsl3/libsl3/tests/version/versiontest.cpp:27: ERROR: 21 | CHECK_EQ( sl3::sqliteCompiledVersionNumber (), sl3::sqliteRuntimeVersionNumber 22 | () ) is NOT correct! values: CHECK_EQ( 3026000, 3043002 ) 23 | 24 | */ 25 | 26 | SCENARIO ("check sqlite versions for library and app") 27 | { 28 | // this can be used as an example to add into your project 29 | // assure the libsl3 was compiled using the same header as you app 30 | 31 | GIVEN ("cmake genertated config") 32 | { 33 | WHEN ("comparing compile/runtime verion") 34 | { 35 | THEN ("these version are the same") 36 | { 37 | std::string compiledVersion = sl3::sqliteCompiledVersion (); 38 | CHECK (compiledVersion == sl3::sqliteRuntimeVersion ()); 39 | } 40 | } 41 | 42 | WHEN ("comparing the compile/runtime version numbers") 43 | { 44 | THEN ("they are the same") 45 | { 46 | CHECK_EQ (sl3::sqliteCompiledVersionNumber (), 47 | sl3::sqliteRuntimeVersionNumber ()); 48 | } 49 | } 50 | 51 | WHEN ("doing something just for coverage") 52 | { 53 | THEN ("it should always pass") 54 | { // if this is ever important, just use as needed 55 | auto tsco = sl3::sqliteThreadSafeCompileOption (); 56 | auto ts = tsco == 1 || tsco == 2 || tsco == 3; 57 | CHECK (ts); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/build-project.yml: -------------------------------------------------------------------------------- 1 | name: build project 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | jobs: 9 | check-sqlite: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | - name: Check SQLite versions 15 | run: bash tools/check_sqlite_versions.sh 16 | 17 | build: 18 | needs: check-sqlite 19 | strategy: 20 | matrix: 21 | build: 22 | - { 23 | os: ubuntu-latest, 24 | preset: ninja, 25 | cmake_args: -DCMAKE_TOOLCHAIN_FILE=toolchain/use-vcpkg 26 | } 27 | - { 28 | os: macos-latest, 29 | preset: xcode, 30 | cmake_args: -DPROJECT_ADDONS='add-on/fetch-dependencies' 31 | } 32 | - { 33 | os: windows-2022, 34 | preset: msvc22, 35 | cmake_args: -DCMAKE_TOOLCHAIN_FILE=toolchain/use-vcpkg 36 | } 37 | runs-on: ${{ matrix.build.os }} 38 | steps: 39 | - name: Cleanup for SQLite on macOS 40 | run: | 41 | sudo rm -rf /Library/Frameworks/Mono.framework 42 | # There is a sqlite header in a different version than the lib found in the system 43 | if: startsWith(matrix.build.os, 'macos') 44 | - uses: lukka/get-cmake@latest 45 | with: 46 | cmakeVersion: "~3.30.0" # <--= optional, use most recent 3.25.x version 47 | ninjaVersion: "^1.11.1" # <--= optional, use most recent 1.x version 48 | - name: Checkout repository 49 | uses: actions/checkout@v4 50 | - name: Setup MSVC 51 | if: startsWith(matrix.build.os, 'windows') 52 | uses: ilammy/msvc-dev-cmd@v1 53 | - name: Configure project 54 | run: cmake --preset ${{ matrix.build.preset }} ${{ matrix.build.cmake_args }} 55 | - name: Build project 56 | run: cmake --build --preset ${{ matrix.build.preset }}-release --parallel --verbose 57 | - name: Run unit test 58 | run: ctest --preset ${{ matrix.build.preset }}-release --parallel 59 | 60 | -------------------------------------------------------------------------------- /include/sl3/rowcallback.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3_RowCallback_HPP 10 | #define SL3_RowCallback_HPP 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | namespace sl3 18 | { 19 | class Columns; 20 | 21 | /** 22 | * \brief Callback for SQL Select statements 23 | * 24 | * This interface can be used to process through the result of 25 | * a SQL statement. 26 | * 27 | * 28 | */ 29 | class LIBSL3_API RowCallback 30 | { 31 | friend class Command; 32 | 33 | protected: 34 | /** 35 | * \brief Constructor 36 | */ 37 | RowCallback () noexcept = default; 38 | 39 | /** 40 | * \brief Constructor 41 | */ 42 | virtual ~RowCallback () noexcept = default; 43 | 44 | /** 45 | * \brief Process one row of the result from a SELECT statement 46 | * 47 | * @param columns Columns object for accessing values. 48 | * 49 | * @return false if processing the query result shall stop 50 | * \n true otherwise 51 | */ 52 | virtual bool onRow (Columns columns) = 0; 53 | 54 | /** 55 | * \brief Called before a query result will be processed 56 | * 57 | * The default implementation does nothing. 58 | */ 59 | virtual void onStart (); 60 | 61 | /** 62 | * \brief Called after a query result has been processed 63 | * 64 | * The default implementation does nothing. 65 | */ 66 | virtual void onEnd (); 67 | }; 68 | } 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /cmake/add-on/coverage-gcov.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | 3 | # TODO , check that we are on GCC 4 | if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 5 | message(FATAL_ERROR "This coverage implementation requires the GCC compiler") 6 | endif() 7 | 8 | 9 | message(STATUS "----- GNU Coverage enabled") 10 | # TODO, add -fprofile-abs-path when compiler is gcc 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") 12 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") 13 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 14 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") 15 | 16 | 17 | 18 | find_program(LCOV lcov) 19 | find_program(GCOV gcov) 20 | if(NOT GCOV) 21 | message(FATAL_ERROR "gcov tool not found for GCC version ${GCC_MAJOR_VERSION}") 22 | endif() 23 | 24 | set(COVERAGE_BRANCHES "--rc branch_coverage=1") 25 | # these warnings are ridiculous, they depend on the lcov genhtml version 26 | set(COVERAGE_WARNINGS "--ignore-errors gcov --ignore-errors mismatch --ignore-errors unused") 27 | set(GENHTML_WARNINGS "") 28 | 29 | set(COVERAGE_TOOL "lcov") 30 | 31 | separate_arguments(COVERAGE_BRANCHES) 32 | separate_arguments(COVERAGE_WARNINGS) 33 | separate_arguments(GENHTML_WARNINGS) 34 | add_custom_target(coverage 35 | COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/coverage 36 | COMMAND lcov --directory . --capture --output-file ${CMAKE_BINARY_DIR}/coverage/coverage.info ${COVERAGE_WARNINGS} ${COVERAGE_BRANCHES} 37 | COMMAND lcov --remove ${CMAKE_BINARY_DIR}/coverage/coverage.info '/usr/*' '*/tests/*' '*/vcpkg_installed/*' '${CMAKE_BINARY_DIR}/_deps/*' '${CMAKE_SOURCE_DIR}/external/*' --output-file ${CMAKE_BINARY_DIR}/coverage/coverage.info.cleaned ${COVERAGE_WARNINGS} ${COVERAGE_BRANCHES} 38 | COMMAND genhtml --branch-coverage ${CMAKE_BINARY_DIR}/coverage/coverage.info.cleaned --output-directory ${CMAKE_BINARY_DIR}/coverage ${GENHTML_WARNINGS} ${COVERAGE_BRANCHES} 39 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 40 | ) 41 | 42 | 43 | add_custom_target(clean_coverage 44 | COMMAND find . -name '*.gcda' -delete 45 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 46 | ) 47 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Deploy doxygen docs 3 | 4 | on: 5 | push: 6 | tags: 7 | - 'v*' 8 | branches: ["main"] 9 | pull_request: 10 | 11 | workflow_dispatch: 12 | 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | concurrency: 19 | group: "pages" 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | build: 24 | runs-on: ubuntu-24.04 25 | steps: 26 | - uses: ssciwr/doxygen-install@v1 27 | with: 28 | version: "1.12.0" 29 | - uses: lukka/get-cmake@latest 30 | with: 31 | cmakeVersion: "~3.30.0" # <--= optional, use most recent 3.25.x version 32 | ninjaVersion: "^1.11.1" # <--= optional, use most recent 1.x version 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | - name: Install lcov 36 | run: sudo apt-get install lcov 37 | # For doc gen present should not be used, since the multi config is not needed, but keep that for now 38 | - name: Run workflow to get coverage docs 39 | run: cmake --preset ninja -DTOOLCHAIN_INCLUDES=toolchain/use-vcpkg -DPROJECT_ADDONS='add-on/coverage-gcov' -DCMAKE_COMPILE_WARNING_AS_ERROR=OFF 40 | - name: Build project 41 | run: cmake --build --preset ninja --parallel 42 | - name: Run tests 43 | run: ctest --preset ninja --parallel 44 | - name: Build documentation 45 | run: cmake --build --preset ninja --parallel --target doc 46 | - name: Build coverage docs 47 | run: cmake --build --preset ninja --target coverage 48 | - name: Add coverage to docs 49 | run: mv build/Linux/ninja/coverage/ build/Linux/ninja/doc/html/coverage 50 | - name: Upload site artifact 51 | uses: actions/upload-pages-artifact@v3 52 | with: 53 | path: 'build/Linux/ninja/doc/html/' 54 | 55 | 56 | deploy: 57 | needs: build 58 | # if: github.ref == 'refs/heads/main' 59 | # todo, switch to update pages only for new releases 60 | if: startsWith(github.ref, 'refs/tags/v') 61 | environment: 62 | name: github-pages 63 | url: ${{ steps.deployment.outputs.page_url }} 64 | runs-on: ubuntu-latest 65 | steps: 66 | - name: Checkout 67 | uses: actions/checkout@v4 68 | - name: Setup Pages 69 | uses: actions/configure-pages@v5 70 | - name: Deploy to GitHub Pages 71 | id: deployment 72 | uses: actions/deploy-pages@v4 73 | -------------------------------------------------------------------------------- /src/sl3/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace sl3 8 | { 9 | 10 | inline size_t 11 | as_size_t (int val) 12 | { 13 | assert (val >= 0); 14 | return static_cast (val); 15 | } 16 | 17 | inline size_t 18 | as_size_t (ptrdiff_t val) 19 | { 20 | assert (val >= 0); 21 | return static_cast (val); 22 | } 23 | 24 | inline int 25 | as_int (size_t val) 26 | { 27 | assert (val < std::numeric_limits::max ()); 28 | return static_cast (val); 29 | } 30 | 31 | // gcc -Werror=conversion helpers 32 | // I keep that as a comment, since it's so funny, but not use it 33 | // template struct common_typeype 34 | // { 35 | // typedef typename std::conditional < std::numeric_limits::is_iec559 36 | // &&std::numeric_limits::is_iec559, 37 | // typename std::conditional< 38 | // std::numeric_limits< 39 | // T1>::digits::digits, T2, T1>::type, 40 | // typename std::conditional::is_iec559, 41 | // T1, 42 | // T2>::type>::type type; 43 | // }; 44 | 45 | template 46 | bool 47 | is_less (const T1& a, const T2& b) 48 | { 49 | using common_type = std::common_type_t; 50 | return std::less{}(static_cast (a), 51 | static_cast (b)); 52 | } 53 | 54 | template 55 | bool 56 | is_greater (const T1& a, const T2& b) 57 | { 58 | using common_type = std::common_type_t; 59 | return std::greater{}(static_cast (a), 60 | static_cast (b)); 61 | } 62 | 63 | template 64 | bool 65 | is_less_equal (const T1& a, const T2& b) 66 | { 67 | using common_type = std::common_type_t; 68 | return std::less_equal{}(static_cast (a), 69 | static_cast (b)); 70 | } 71 | 72 | template 73 | bool 74 | is_equal (const T1& a, const T2& b) 75 | { 76 | using common_type = std::common_type_t; 77 | return std::equal_to{}(static_cast (a), 78 | static_cast (b)); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/sl3/dbvalues.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #include 10 | #include 11 | 12 | namespace sl3 13 | { 14 | #ifdef _MSC_VER 15 | using std::initializer_list; 16 | 17 | DbValues::DbValues (conatiner_type c) noexcept ( 18 | std::is_nothrow_move_constructible::value) 19 | : Container (std::move (c)) 20 | { 21 | } 22 | 23 | DbValues::DbValues (initializer_list l) 24 | : Container (std::move (l)) 25 | { 26 | } 27 | #endif 28 | 29 | DbValues::DbValues () noexcept 30 | : Container () 31 | { 32 | } 33 | DbValues::DbValues (const DbValues& row) 34 | : Container (row) 35 | { 36 | } 37 | 38 | DbValues::DbValues (DbValues&& row) noexcept ( 39 | std::is_nothrow_move_constructible::value) 40 | : Container (std::move (row)) 41 | { 42 | } 43 | 44 | DbValues& 45 | DbValues::operator= (const DbValues& row) 46 | { 47 | // in case of exception , both needs to stay unchanged 48 | // first all checks, than assign 49 | if (size () != row.size ()) 50 | throw ErrTypeMisMatch (); 51 | 52 | for (size_t i = 0; i < size (); ++i) 53 | { 54 | if (!_cont[i].canAssign (row[i])) 55 | throw ErrTypeMisMatch (); 56 | } 57 | 58 | for (size_t i = 0; i < size (); ++i) 59 | { 60 | _cont[i].assign (row[i]); 61 | } 62 | 63 | return *this; 64 | } 65 | 66 | DbValues& 67 | DbValues::operator= (DbValues&& row) 68 | { 69 | // in case of exception , both needs to stay unchanged 70 | // first all checks, than assign 71 | // if there is not size, it is OK, was possible moved 72 | if (size () && size () != row.size ()) 73 | throw ErrTypeMisMatch (); 74 | 75 | for (size_t i = 0; i < size (); ++i) 76 | { 77 | if (!_cont[i].canAssign (row[i])) 78 | throw ErrTypeMisMatch (); 79 | } 80 | 81 | _cont = std::move (row._cont); 82 | return *this; 83 | } 84 | 85 | void 86 | DbValues::swap (DbValues& other) noexcept 87 | { 88 | using std::swap; 89 | swap (_cont, other._cont); 90 | } 91 | 92 | void 93 | swap (DbValues& a, DbValues& b) noexcept 94 | { 95 | a.swap (b); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /cmake/add-on/coverage-report-clang.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Directory containing the test binaries 4 | set -e 5 | 6 | # Parse arguments 7 | TEST_BIN_DIR="" 8 | while [ "$#" -gt 0 ]; do 9 | case "$1" in 10 | --build-dir) 11 | TEST_BIN_DIR="$2" 12 | shift 2 13 | ;; 14 | --build-dir=*) 15 | TEST_BIN_DIR="${1#*=}" 16 | shift 1 17 | ;; 18 | *) 19 | echo "Unknown option: $1" 20 | exit 1 21 | ;; 22 | esac 23 | done 24 | 25 | if [ -z "${TEST_BIN_DIR}" ]; then 26 | echo "Please provide the build directory by using the --build-dir option" 27 | exit 1 28 | fi 29 | 30 | # Set XCRUN based on the output of uname 31 | if [ "$(uname)" = "Darwin" ]; then 32 | XCRUN="xcrun" 33 | else 34 | XCRUN="" 35 | fi 36 | 37 | # Directory to store the coverage report 38 | # TODO, add into build dir 39 | COVERAGE_REPORT_DIR="${TEST_BIN_DIR}/coverage_report" 40 | 41 | # Remove any existing .profraw files 42 | # find ${TEST_BIN_DIR} -name "*.profraw" -delete 43 | 44 | # Find all .profraw files 45 | ALL_PROFILES=$(find ${TEST_BIN_DIR} -name '*.profraw') 46 | ALL_BINARIES='' 47 | for profile in ${ALL_PROFILES}; do 48 | binary=$(basename -s .profraw "${profile}") 49 | binary_path=$(find "${TEST_BIN_DIR}" -type f -name "${binary}" -print -quit) 50 | if [ -z "${binary_path}" ]; then 51 | echo "Binary ${binary} for profile ${profile} not found in ${TEST_BIN_DIR}" 52 | # exit 1 53 | else 54 | ALL_BINARIES="${ALL_BINARIES} ${binary_path}" 55 | fi 56 | done 57 | 58 | COVERAGE_COMBINED_FILE="${TEST_BIN_DIR}/coverage-all.profraw" 59 | 60 | # Merge all .profraw files into a single .profdata file 61 | rm -f ${COVERAGE_COMBINED_FILE} 62 | echo "Merging profiles into ${COVERAGE_COMBINED_FILE}" 63 | ${XCRUN} llvm-profdata merge -sparse ${ALL_PROFILES} -o ${COVERAGE_COMBINED_FILE} 64 | 65 | IGNORE_REGEX="${TEST_BIN_DIR}/_deps/.*|tests/.*" 66 | echo "Generating plain coverage report" 67 | # Generate the plain text coverage report for each binary 68 | for binary in ${ALL_BINARIES}; do 69 | ${XCRUN} llvm-cov show ${binary} \ 70 | -instr-profile=${COVERAGE_COMBINED_FILE} \ 71 | -ignore-filename-regex=${IGNORE_REGEX} \ 72 | -output-dir=${COVERAGE_REPORT_DIR}/plain 73 | done 74 | 75 | # Generate an HTML coverage report for each binary 76 | echo "Generating HTML coverage report" 77 | for binary in ${ALL_BINARIES}; do 78 | ${XCRUN} llvm-cov show ${binary} \ 79 | -instr-profile=${COVERAGE_COMBINED_FILE} \ 80 | -ignore-filename-regex=${IGNORE_REGEX} \ 81 | -format=html \ 82 | -output-dir=${COVERAGE_REPORT_DIR}/html 83 | done 84 | 85 | # Open the HTML report 86 | echo "Opening the HTML coverage report: ${COVERAGE_REPORT_DIR}/html/index.html" 87 | #open ${COVERAGE_REPORT_DIR}/html/index.html 88 | 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libsl3, a C++ interface for SQLite 3.x 2 | 3 | libsl3 is designed to enable comfortable and efficient communication with a 4 | SQLite database based on its natural language, which is SQL. 5 | 6 | libsl3 originated back at a time when C++11 was new. 7 | It has remained stable to support existing users for quite a while, over a decade. 8 | With the arrival of C++23, development has restarted, but due to a lack of time, not much has happened so far. The minimum required C++ standard is now C++17. 9 | The goal is to keep the interface stable, but using a newer C++ standard might justify some breaking changes. 10 | 11 | For people seeking the old version, release v1.1.31001 preserves the original C++11 state with CMake 2.8 support. 12 | 13 | **The full documentation for libsl3 can be found here:**
14 | https://a4z.github.io/libsl3/ 15 | 16 | ## Build 17 | 18 | Quickly summarized, there are dependencies, but they are all either development dependencies or optional 19 | 20 | - sqlite3 21 | - doctest 22 | - commonCompilerWarnings 23 | 24 | ### Consume via CMake 25 | 26 | To build libsl3 without any dependencies, run 27 | 28 | cmake -S . -B build -DBUILD_TESTING=OFF -Dsl3_USE_INTERNAL_SQLITE3=ON -Dsl3_USE_COMMON_COMPILER_WARNINGS=OFF 29 | 30 | This will build libsl3 with the internal sqlite distribution. 31 | The used sqlite version is documented in the patch level part of the actual libsl3 version. 32 | 33 | For using sqlite from the system, run 34 | 35 | cmake -S . -B build -DBUILD_TESTING=OFF -Dsl3_USE_COMMON_COMPILER_WARNINGS=OFF 36 | 37 | For more information about how to consume and build the library, visit [the documentation](https://a4z.github.io/libsl3/#Installation). 38 | 39 | ## A short usage example 40 | 41 | ```cpp 42 | #include 43 | #include 44 | #include 45 | 46 | int main() 47 | { 48 | using namespace sl3; 49 | // define a db 50 | Database db(":memory:"); 51 | // run commands against the db 52 | db.execute("CREATE TABLE tbl(f1 INTEGER, f2 TEXT, f3 REAL);"); 53 | // create a command with parameters 54 | auto cmd = db.prepare("INSERT INTO tbl (f1, f2, f3) VALUES (?,?,?);"); 55 | //add some data 56 | cmd.execute(parameters(1, "one", 1.1)); 57 | cmd.execute(parameters(2, "two", 2.2)); 58 | // access the data 59 | Dataset ds = db.select("SELECT * FROM tbl;"); 60 | // Dataset is a container 61 | assert(ds.size()==2); 62 | // A row in a dataset is also a container 63 | auto row = ds[0] ; 64 | assert(row.size()==3); 65 | // Type info for each field s available 66 | assert ( row[0].type() == Type::Int ) ; 67 | assert ( row[1].type() == Type::Text ) ; 68 | assert ( row[2].type() == Type::Real ) ; 69 | // there is also iterator access 70 | for(const auto& row :ds) { 71 | for (const auto& field : row) { 72 | std::cout << field << " " ; 73 | } 74 | std::cout << std::endl; 75 | } 76 | } 77 | 78 | ``` 79 | 80 | This will output 81 | 82 | ```bash 83 | 1 one 1.1 84 | 2 two 2.1 85 | ``` 86 | 87 | Additional samples can be found in the tests and tests/samples subfolder. 88 | 89 | ## License 90 | 91 | https://www.mozilla.org/en-US/MPL/2.0/ 92 | -------------------------------------------------------------------------------- /src/sl3/connection.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3_CONNECTION_HPP_ 10 | #define SL3_CONNECTION_HPP_ 11 | 12 | #include 13 | 14 | struct sqlite3; 15 | 16 | namespace sl3 17 | { 18 | /// \cond HIDDEN_SYMBOLS 19 | namespace internal 20 | { 21 | /** 22 | * \internal 23 | * \brief Holds the sqlite3 connection pointer. 24 | * 25 | * This is the link between Database and Command , and only used internal 26 | * 27 | */ 28 | class Connection 29 | { 30 | friend class sl3::Database; 31 | 32 | Connection (sqlite3* p); 33 | 34 | public: 35 | ~Connection (); 36 | 37 | /// return db pointer 38 | sqlite3* db (); 39 | 40 | /// return if connection is open 41 | bool isValid (); 42 | 43 | /// throw ErrNoConnection if not valid 44 | void ensureValid (); 45 | 46 | private: 47 | Connection (Connection&&) = default; 48 | 49 | Connection (const Connection&) = delete; 50 | Connection& operator= (const Connection&) = delete; 51 | Connection& operator= (Connection&&) = delete; 52 | 53 | void close (); // called by the db 54 | 55 | sqlite3* sl3db; 56 | }; 57 | } 58 | ///\endcond 59 | 60 | namespace internal 61 | { 62 | inline Connection::Connection (sqlite3* p) 63 | : sl3db (p) 64 | { 65 | } 66 | 67 | inline Connection::~Connection () { close (); } 68 | 69 | inline sqlite3* 70 | Connection::db () 71 | { 72 | return sl3db; 73 | } 74 | 75 | inline bool 76 | Connection::isValid () 77 | { 78 | return sl3db != nullptr; 79 | } 80 | 81 | inline void 82 | Connection::ensureValid () 83 | { 84 | if (sl3db == nullptr) 85 | { 86 | throw ErrNoConnection (); 87 | } 88 | } 89 | 90 | inline void 91 | Connection::close () 92 | { 93 | if (sl3db == nullptr) 94 | return; 95 | 96 | // total clean up to be sure nothing left. 97 | auto stm = sqlite3_next_stmt (sl3db, 0); 98 | while (stm != nullptr) 99 | { 100 | sqlite3_finalize (stm); 101 | stm = sqlite3_next_stmt (sl3db, 0); 102 | } 103 | 104 | // if busy, use v2 for garbed collecting, 105 | if (sqlite3_close (sl3db) != SQLITE_OK) 106 | { // but that should 'never' happen :-) 107 | sqlite3_close_v2 (sl3db); // LCOV_EXCL_LINE 108 | } 109 | 110 | sl3db = nullptr; 111 | } 112 | 113 | } // ns internal 114 | } 115 | 116 | #endif /* ...DATABASE_HPP_ */ 117 | -------------------------------------------------------------------------------- /include/sl3/dbvalues.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3_DbVALUES_HPP_ 10 | #define SL3_DbVALUES_HPP_ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace sl3 19 | { 20 | /** 21 | * \brief A row of DbValues 22 | * 23 | * Fixed size vector of DbValue instances. 24 | * DbValues models a row in a database, therefore assignment works only if 25 | * fields are compatible. 26 | * 27 | * 28 | */ 29 | class LIBSL3_API DbValues final : public Container> 30 | { 31 | public: 32 | //@{ 33 | /// usual typedefs 34 | using conatiner_type = Container::conatiner_type; 35 | using iterator = conatiner_type::iterator; 36 | using const_iterator = conatiner_type::const_iterator; 37 | using value_type = conatiner_type::value_type; 38 | using reference = conatiner_type::reference; 39 | using const_reference = conatiner_type::const_reference; 40 | using size_type = conatiner_type::size_type; 41 | //@} 42 | 43 | /// Constructor 44 | DbValues () noexcept; 45 | 46 | #ifndef _MSC_VER 47 | using Container::Container; 48 | 49 | #else 50 | 51 | DbValues (conatiner_type) noexcept ( 52 | std::is_nothrow_move_constructible::value); 53 | 54 | DbValues (std::initializer_list); 55 | 56 | #endif 57 | 58 | /** \brief Copy constructor 59 | */ 60 | DbValues (const DbValues&); 61 | 62 | /** \brief Move constructor 63 | */ 64 | DbValues (DbValues&&) noexcept ( 65 | std::is_nothrow_move_constructible::value); 66 | 67 | /** \brief Assigment 68 | * \throw sl3::ErrTypeMisMatch if size() is different 69 | * \see assignment of DbValue 70 | * \return reference ot this 71 | */ 72 | DbValues& operator= (const DbValues&); 73 | 74 | /** \brief Assigment 75 | * \throw sl3::ErrTypeMisMatch if size() is different 76 | * \see assignment of DbValue 77 | * \return reference ot this 78 | */ 79 | DbValues& operator= (DbValues&&); 80 | 81 | /** 82 | * \brief swap function 83 | * 84 | * Swaps content of 2 DbValues. 85 | * 86 | * \param other DbValues to swap wit 87 | */ 88 | void swap (DbValues& other) noexcept; 89 | }; 90 | 91 | /** 92 | * \brief DbValue specialized swap function 93 | * 94 | * \param a first value to swap with second value 95 | * \param b second value to swap with first value 96 | * 97 | * This function call a.swap(b). \see DbValues::swap 98 | */ 99 | void swap (DbValues& a, DbValues& b) noexcept; 100 | 101 | } 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /src/config.in: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2016 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | 10 | /************************************************************************ 11 | !!!! THIS FILE IS AUTOGENERATED BY CMAKE !!!! 12 | *************************************************************************/ 13 | 14 | #ifndef SL3_CONFIG 15 | #define SL3_CONFIG 16 | 17 | 18 | // TODO this has to look like here https://gcc.gnu.org/wiki/Visibility 19 | 20 | #if defined(_WIN32) && defined(_MSC_VER) 21 | #if defined(LIBSL3_DLL) 22 | #define LIBSL3_API __declspec(dllexport) 23 | #elif defined(LINK_SL3_DLL) 24 | #define LIBSL3_API __declspec(dllimport) 25 | #else 26 | #define LIBSL3_API 27 | #endif 28 | #else 29 | #define LIBSL3_API 30 | #endif 31 | 32 | #include 33 | 34 | //#define LIBSL3_API 35 | 36 | /** 37 | \namespace sl3 38 | \brief Namespace of libSL3. 39 | 40 | The namespace where the library defines it's elements. 41 | 42 | */ 43 | namespace sl3 44 | { 45 | 46 | static constexpr int MAJOR_VERSION = ${sl3_MAJOR_VERSION} ; 47 | static constexpr int MINOR_VERSION = ${sl3_MINOR_VERSION} ; 48 | static constexpr int PATCH_VERSION = ${sl3_PATCH_VERSION} ; 49 | 50 | 51 | /** 52 | * \brief sqlite version string at compile time 53 | * 54 | * if this library was linked against an installed version of sqlite 55 | * this function can be used to determinate if the system library has 56 | * been updated. 57 | * \sa sqliteRuntimeVersion() 58 | * 59 | * \return version string at compile time 60 | */ 61 | const char* sqliteCompiledVersion(); 62 | 63 | /** 64 | * \brief sqlite version number at compile time 65 | * 66 | * if this library was linked against an installed version of sqlite 67 | * this function can be used to determinate if the system library has 68 | * been updated. 69 | * \sa sqliteRuntimeVersionNumber() 70 | * 71 | * \return version number at compile time 72 | */ 73 | int sqliteCompiledVersionNumber(); 74 | 75 | 76 | /** 77 | * \brief sqlite version string at runtime 78 | * 79 | * if this library was linked against an installed version of sqlite 80 | * this function can be used to determinate if the system library has 81 | * been updated. 82 | * \sa sqliteCompiledVersion() 83 | * 84 | * \return version string currently used 85 | */ 86 | const char* sqliteRuntimeVersion(); 87 | 88 | /** 89 | * \brief sqlite version number at compile time 90 | * 91 | * if this library was linked against an installed version of sqlite 92 | * this function can be used to determinate if the system library has 93 | * been updated. 94 | * \sa sqliteCompiledVersionNumber() 95 | * 96 | * \return sqlite version number currently used 97 | */ 98 | int sqliteRuntimeVersionNumber(); 99 | 100 | 101 | /** 102 | * \brief returns value of SQLITE_THREADSAFE compilation option 103 | * 104 | * see http://www.sqlite.org/compile.html about additional informations 105 | * 106 | * \return 0 or 1 or 2 107 | */ 108 | int sqliteThreadSafeCompileOption(); 109 | 110 | 111 | } 112 | 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /include/sl3/types.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3_TYPES_HPP_ 10 | #define SL3_TYPES_HPP_ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | namespace sl3 21 | { 22 | /** 23 | * Enumeration of different value types 24 | * 25 | * These are the types known by sqlite. 26 | * They are used when reading from or writing to a database. 27 | * 28 | */ 29 | enum class Type 30 | { 31 | Null = 0, //!< Null, no value 32 | Int = 1, //!< Int value 33 | Real = 2, //!< Real value 34 | Text = 3, //!< Text value 35 | Blob = 4, //!< Blob vale 36 | Variant = 5 //!< takes any type 37 | }; 38 | 39 | /** 40 | * \brief Get the type name as string 41 | * 42 | * For example, in log messages a type "Real" is more verbose that a type 2. 43 | * 44 | * \return the type name as string 45 | */ 46 | std::string typeName (Type); 47 | 48 | /** 49 | * \brief overloaded stream operator for sl3::Type 50 | * \param os ostream 51 | * \param t the Type 52 | * \return the ostream 53 | */ 54 | std::ostream& LIBSL3_API operator<< (std::ostream& os, const Type& t); 55 | 56 | /** 57 | * \brief A Container holding sl3::Type values. 58 | * 59 | * A fixed size list of sl3::Type values. 60 | */ 61 | class LIBSL3_API Types : public Container> 62 | { 63 | public: 64 | //@{ 65 | using conatiner_type = Container::conatiner_type; 66 | using iterator = conatiner_type::iterator; 67 | using const_iterator = conatiner_type::const_iterator; 68 | using value_type = conatiner_type::value_type; 69 | using reference = conatiner_type::reference; 70 | using const_reference = conatiner_type::const_reference; 71 | using size_type = conatiner_type::size_type; 72 | //@} 73 | 74 | #ifndef _MSC_VER 75 | using Container::Container; 76 | 77 | #else 78 | /** 79 | * \brief c'tor 80 | * 81 | * Create an empty sl3::Type Container 82 | */ 83 | Types () noexcept {} 84 | using Base = Container>; 85 | 86 | /** 87 | * \brief c'tor 88 | * 89 | * Create a container with given vector of sl3::Type elements. 90 | * 91 | * \param c vector of sl3::Type 92 | */ 93 | Types (conatiner_type c) noexcept ( 94 | std::is_nothrow_move_constructible::value) 95 | : Base (std::move (c)) 96 | { 97 | } 98 | 99 | /** 100 | * \brief c'tor 101 | * 102 | * Create a container with given initializer list of sl3::Type elements. 103 | * 104 | * \param l initialer list of sl3::Type 105 | */ 106 | Types (std::initializer_list l) 107 | : Base (std::move (l)) 108 | { 109 | } 110 | 111 | #endif 112 | 113 | /** 114 | * \brief Swap container 115 | * 116 | * Uses standard swap to change the contents. 117 | * 118 | * \param other Types to swap with 119 | */ 120 | void swap (Types& other) noexcept; 121 | }; 122 | 123 | /** 124 | * A type for binary data 125 | */ 126 | using Blob = std::vector; 127 | } 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.29) 2 | 3 | 4 | set(sl3_MAJOR_VERSION 1) 5 | set(sl3_MINOR_VERSION 2) 6 | 7 | # sqlite major is always 3, no need to use that here 8 | # set(internal_SQLITE_MAJOR_V 3) 9 | set(internal_SQLITE_MINOR_V 50) 10 | set(internal_SQLITE_PATCH_V 4) 11 | 12 | # sqlite uses (X*1000000 + Y*1000 + Z), 13 | # but minor patch used since major is always 3 14 | math(EXPR sl3_PATCH_VERSION "${internal_SQLITE_MINOR_V}*1000 + ${internal_SQLITE_PATCH_V}") 15 | 16 | set(sl3_VERSION ${sl3_MAJOR_VERSION}.${sl3_MINOR_VERSION}.${sl3_PATCH_VERSION}) 17 | 18 | project(libsl3 19 | VERSION ${sl3_VERSION} 20 | LANGUAGES C CXX 21 | ) 22 | 23 | 24 | 25 | # done differently now 26 | LIST(PREPEND CMAKE_MODULE_PATH "${libsl3_SOURCE_DIR}/cmake") 27 | 28 | #optional enable personal test/trial dir 29 | include(CMakeLocalOpts.cmake OPTIONAL) 30 | 31 | 32 | option(sl3_USE_INTERNAL_SQLITE3 "use build-in sqlite3 ON, use system sqlite3 header/lib, OFF" OFF) 33 | option(sl3_USE_COMMON_COMPILER_WARNINGS "Build using shared libraries" ON) 34 | 35 | if (sl3_USE_COMMON_COMPILER_WARNINGS) 36 | find_package(commonCompilerWarnings CONFIG REQUIRED) 37 | set(LIBWARNINGS a4z::commonCompilerWarnings) 38 | else(sl3_USE_COMMON_COMPILER_WARNINGS) 39 | add_library(all_warnings_off INTERFACE) 40 | set(LIBWARNINGS all_warnings_off) 41 | message(STATUS "sl3_USE_COMMON_COMPILER_WARNINGS is OFF") 42 | endif(sl3_USE_COMMON_COMPILER_WARNINGS) 43 | 44 | 45 | set(sl3_CONFIG_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/include/sl3/config.hpp") 46 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/config.in" "${sl3_CONFIG_HEADER}") 47 | 48 | ################################################################################ 49 | 50 | SET ( sl3_HDR 51 | include/sl3/columns.hpp 52 | include/sl3/command.hpp 53 | include/sl3/config.hpp 54 | include/sl3/container.hpp 55 | include/sl3/database.hpp 56 | include/sl3/dataset.hpp 57 | include/sl3/dbvalue.hpp 58 | include/sl3/dbvalues.hpp 59 | include/sl3/error.hpp 60 | include/sl3/rowcallback.hpp 61 | include/sl3/types.hpp 62 | include/sl3/value.hpp 63 | ) 64 | #------------------------------------------------------------------------------- 65 | SET ( sl3_SRCHDR 66 | src/sl3/connection.hpp 67 | 68 | ) 69 | #------------------------------------------------------------------------------- 70 | SET ( sl3_SRC 71 | src/sl3/columns.cpp 72 | src/sl3/config.cpp 73 | src/sl3/command.cpp 74 | src/sl3/database.cpp 75 | src/sl3/dataset.cpp 76 | src/sl3/dbvalue.cpp 77 | src/sl3/dbvalues.cpp 78 | src/sl3/error.cpp 79 | src/sl3/rowcallback.cpp 80 | src/sl3/types.cpp 81 | src/sl3/value.cpp 82 | 83 | ) 84 | ################################################################################ 85 | 86 | add_library( sl3 ${sl3_SRC} ${sl3_SRCHDR} ${sl3_HDR} ) 87 | target_include_directories(sl3 PUBLIC 88 | $ 89 | $ # /include/mylib 90 | ) 91 | 92 | target_link_libraries(sl3 PRIVATE $) 93 | 94 | include( lib/find_sqlite ) 95 | target_link_libraries(sl3 PUBLIC ${SQLITE_LINK_NAME}) 96 | 97 | option(BUILD_SHARED_LIBS "Build using shared libraries" OFF) 98 | 99 | set(sl3_install_targets sl3) 100 | 101 | if(BUILD_SHARED_LIBS) 102 | set_target_properties(sl3 PROPERTIES 103 | VERSION ${sl3_VERSION} 104 | SOVERSION ${sl3_MAJOR_VERSION}.${sl3_MINOR_VERSION} 105 | POSITION_INDEPENDENT_CODE ON 106 | ) 107 | endif(BUILD_SHARED_LIBS) 108 | 109 | if(sl3_BUILD_TESTING) 110 | add_subdirectory(tests) 111 | endif(sl3_BUILD_TESTING) 112 | 113 | include(lib/install) 114 | include(lib/doxydoc) 115 | 116 | # include(CMakePrintSystemInformation) 117 | # include(CMakePrintHelpers) 118 | # cmake_print_variables(CMAKE_SYSTEM_AND_CXX_COMPILER_INFO_FILE) 119 | 120 | -------------------------------------------------------------------------------- /src/sl3/dataset.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "utils.hpp" 19 | 20 | namespace sl3 21 | { 22 | Dataset::Dataset () noexcept 23 | : _fieldtypes () 24 | , _names () 25 | { 26 | } 27 | Dataset::Dataset (Types types) 28 | : _fieldtypes (std::move (types)) 29 | , _names () 30 | { 31 | } 32 | 33 | Dataset::Dataset (Dataset&& other) noexcept ( 34 | std::is_nothrow_move_constructible>::value 35 | && std::is_nothrow_move_constructible::value 36 | && std::is_nothrow_move_constructible>::value) 37 | : Container> (std::move (other)) 38 | , _fieldtypes (std::move (other._fieldtypes)) 39 | , _names (std::move (other._names)) 40 | { 41 | } 42 | 43 | void 44 | Dataset::reset () 45 | { 46 | _names.clear (); 47 | _cont.clear (); 48 | } 49 | 50 | void 51 | Dataset::reset (const Types& types) 52 | { 53 | _fieldtypes = types; 54 | reset (); 55 | } 56 | 57 | void 58 | Dataset::merge (const Dataset& other) 59 | { 60 | if (!other._names.empty ()) 61 | { 62 | if (!_names.empty () && _names != other._names) 63 | throw ErrTypeMisMatch (); 64 | } 65 | 66 | if (_fieldtypes.size () != other._fieldtypes.size ()) 67 | throw ErrTypeMisMatch (); 68 | 69 | for (std::size_t i = 0; i < _fieldtypes.size (); ++i) 70 | { 71 | if (_fieldtypes[i] != Type::Variant) 72 | { 73 | if (_fieldtypes[i] != other._fieldtypes[i]) 74 | throw ErrTypeMisMatch (); 75 | } 76 | } 77 | 78 | _cont.insert (_cont.end (), other._cont.begin (), other._cont.end ()); 79 | } 80 | 81 | void 82 | Dataset::merge (const DbValues& row) 83 | { 84 | if (_fieldtypes.size () > 0 && _fieldtypes.size () != row.size ()) 85 | { 86 | throw ErrTypeMisMatch (); 87 | } 88 | 89 | for (std::size_t i = 0; i < _fieldtypes.size (); ++i) 90 | { 91 | if (_fieldtypes[i] != Type::Variant) 92 | { 93 | if (_fieldtypes[i] != row[i].dbtype ()) 94 | { 95 | throw ErrTypeMisMatch (); 96 | } 97 | } 98 | } 99 | 100 | _cont.push_back (DbValues (row)); 101 | } 102 | 103 | size_t 104 | Dataset::getIndex (const std::string& name) const 105 | { 106 | using namespace std; 107 | auto pos = find (_names.begin (), _names.end (), name); 108 | if (pos == _names.end ()) 109 | throw ErrOutOfRange ("Field name " + name + " not found"); 110 | 111 | return as_size_t (distance (_names.begin (), pos)); 112 | } 113 | 114 | void 115 | Dataset::sort (const std::vector& idxs, DbValueSort cmp) 116 | { 117 | ASSERT_EXCEPT (cmp, ErrNullValueAccess); 118 | 119 | auto lessValues = [&] (const DbValues& a, const DbValues& b) -> bool { 120 | for (auto cur : idxs) 121 | { 122 | if (cmp (a.at (cur), b.at (cur))) 123 | return true; 124 | else if (cmp (b.at (cur), a.at (cur))) 125 | return false; 126 | } 127 | return false; 128 | }; 129 | 130 | std::sort (begin (), end (), lessValues); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /sqlite/setup_sqlite3.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | 3 | #http://www.sqlite.org/compile.html 4 | 5 | # SQLITE_DEFAULT_FILE_FORMAT=<1 or 4> 6 | # SQLITE_DEFAULT_FOREIGN_KEYS=<0 or 1> 7 | # SQLITE_THREADSAFE=<0 or 1 or 2> 8 | # SQLITE_TEMP_STORE=<0 through 3> 0 always files / 1 files - allow PRAGMA temp_store, 2 memory allow PRAGMA temp_store , 3 always memory 9 | # SQLITE_ENABLE_FTS3 10 | # SQLITE_ENABLE_FTS3_PARENTHESIS 11 | # SQLITE_ENABLE_ICU for future... 12 | # SQLITE_SOUNDEX 13 | # SQLITE_ENABLE_RTREE 14 | # SQLITE_OMIT_LOAD_EXTENSION 15 | # SQLITE_ENABLE_STAT4 will be always set 16 | # SQLITE_ENABLE_JSON1 default on, but might change 17 | 18 | 19 | # TODO covert the legacy BOOL to ON/OFF options 20 | 21 | 22 | include_directories( BEFORE SYSTEM sqlite) 23 | set( SQLITE3_FILES sqlite/sqlite3.h sqlite/sqlite3ext.h sqlite/sqlite3.c ) 24 | 25 | # No warnings for sqlite3.c 26 | if(CMAKE_LANG_COMPILER_ID STREQUAL "MSVC") 27 | set(OPTION_PREFIX "/") 28 | else() 29 | set(OPTION_PREFIX "-") 30 | endif() 31 | set_source_files_properties(sqlite/sqlite3.c PROPERTIES COMPILE_FLAGS "${OPTION_PREFIX}w") 32 | 33 | set( SQLITE_THREADSAFE "1" CACHE STRING "defines SQLITE_THREADSAFE=<0 or 1 or 2>") 34 | set(SQLITE_OMIT_LOAD_EXTENSION ON CACHE BOOL "defines SQLITE_OMIT_LOAD_EXTENSION if ON") 35 | 36 | # TODO add FTS 4 and 5 37 | set( SQLITE_ENABLE_FTS3 ${SQLITE_ENABLE_FTS3} CACHE BOOL "defines SQLITE_ENABLE_FTS3 if on") 38 | set( SQLITE_ENABLE_FTS3 ${SQLITE_ENABLE_FTS4} CACHE BOOL "defines SQLITE_ENABLE_FTS3 if on") 39 | set( SQLITE_ENABLE_FTS3 ${SQLITE_ENABLE_FTS5} CACHE BOOL "defines SQLITE_ENABLE_FTS3 if on") 40 | 41 | set( SQLITE_ENABLE_FTS3_PARENTHESIS OFF CACHE BOOL 42 | "defines SQLITE_ENABLE_FTS3_PARENTHESIS if on") 43 | 44 | set( SQLITE_SOUNDEX ${SQLITE_SOUNDEX} CACHE BOOL "defines SQLITE_SOUNDEX if on") 45 | set( SQLITE_ENABLE_ICU OFF CACHE BOOL "defines SQLITE_ENABLE_ICU if on") 46 | # <0 through 3> 0 always files / 1 files - allow PRAGMA temp_store, 2 memory allow PRAGMA temp_store , 3 always memory 47 | set(SQLITE_TEMP_STORE 1 CACHE STRING "defines SQLITE_TEMP_STORE=<0 or 1 or 2>") 48 | 49 | # TODO , other as required.... 50 | set( sl3_sqlite3LIBS pthread dl ) # what on windows ??? 51 | # add defaults first 52 | 53 | set(mysqlt3_DEFINES ${SL3_SQLITE_USERDEFINES}) 54 | 55 | # add this always to be sure that it is enabled 56 | # stat2 is no op and stat3 can be overwritten by stat 4 57 | # use stat4 as default 58 | list( APPEND mysqlt3_DEFINES SQLITE_ENABLE_STAT4 ) 59 | 60 | 61 | if (SQLITE_THREADSAFE EQUAL 0) 62 | list( APPEND mysqlt3_DEFINES SQLITE_THREADSAFE=0 ) 63 | list( REMOVE_ITEM sl3_sqlite3LIBS pthread ) 64 | elseif( SQLITE_THREADSAFE GREATER 0 AND SQLITE_THREADSAFE LESS 3 ) 65 | list( APPEND mysqlt3_DEFINES SQLITE_THREADSAFE=${SQLITE_THREADSAFE} ) 66 | else( SQLITE_THREADSAFE EQUAL 0 ) 67 | #MESSAGE( FATAL_ERROR "invalid value for "${SQLITE_THREADSAFE} ) 68 | MESSAGE( WARNING "invalid value ${SQLITE_THREADSAFE} for SQLITE_THREADSAFE, use default 1" ) 69 | list( APPEND mysqlt3_DEFINES SQLITE_THREADSAFE=${SQLITE_THREADSAFE} ) 70 | endif( SQLITE_THREADSAFE EQUAL 0 ) 71 | 72 | if( SQLITE_TEMP_STORE GREATER -1 AND SQLITE_THREADSAFE LESS 3 ) 73 | list( APPEND mysqlt3_DEFINES SQLITE_TEMP_STORE=${SQLITE_TEMP_STORE} ) 74 | else( SQLITE_TEMP_STORE GREATER -1 AND SQLITE_THREADSAFE LESS 3 ) 75 | MESSAGE( WARING "invalid value ${SQLITE_TEMP_STORE} for SQLITE_TEMP_STORE, define will not be defined" ) 76 | endif( SQLITE_TEMP_STORE GREATER -1 AND SQLITE_THREADSAFE LESS 3 ) 77 | 78 | if ( SQLITE_OMIT_LOAD_EXTENSION ) 79 | 80 | list( REMOVE_ITEM SQLITE3_FILES sqlt3/sqlite3ext.h ) 81 | list( APPEND mysqlt3_DEFINES SQLITE_OMIT_LOAD_EXTENSION ) 82 | list( REMOVE_ITEM sl3_sqlite3LIBS dl ) 83 | 84 | endif ( SQLITE_OMIT_LOAD_EXTENSION ) 85 | 86 | # make stat4 default 87 | set (SQLITE_ENABLE_FTS4 true) 88 | 89 | if ( SQLITE_ENABLE_FTS3 ) 90 | list( APPEND mysqlt3_DEFINES SQLITE_ENABLE_FTS3 ) 91 | endif ( SQLITE_ENABLE_FTS3 ) 92 | 93 | if ( SQLITE_ENABLE_FTS4 ) 94 | list( APPEND mysqlt3_DEFINES SQLITE_ENABLE_FTS4 ) 95 | endif ( SQLITE_ENABLE_FTS4 ) 96 | 97 | if ( SQLITE_ENABLE_FTS5 ) 98 | list( APPEND mysqlt3_DEFINES SQLITE_ENABLE_FTS5 ) 99 | endif ( SQLITE_ENABLE_FTS5 ) 100 | 101 | 102 | 103 | if ( SQLITE_ENABLE_FTS3_PARENTHESIS ) 104 | list( APPEND mysqlt3_DEFINES SQLITE_ENABLE_FTS3_PARENTHESIS ) 105 | endif ( SQLITE_ENABLE_FTS3_PARENTHESIS ) 106 | 107 | if ( SQLITE_SOUNDEX ) 108 | list( APPEND mysqlt3_DEFINES SQLITE_SOUNDEX ) 109 | endif ( SQLITE_SOUNDEX ) 110 | 111 | if ( SQLITE_ENABLE_ICU ) 112 | list( APPEND mysqlt3_DEFINES SQLITE_ENABLE_ICU ) 113 | endif ( SQLITE_ENABLE_ICU ) 114 | 115 | if(WIN32) 116 | # TODO, since WSL, MSYS and Cygwin might not be that relevant anymore 117 | if(NOT MSYS) #and not cygwin ? 118 | set(sl3_sqlite3LIBS "") 119 | endif(NOT MSYS) 120 | endif(WIN32) 121 | 122 | target_sources(sl3 PRIVATE ${SQLITE3_FILES}) 123 | target_compile_definitions( sl3 PRIVATE ${mysqlt3_DEFINES} ) 124 | find_package( Threads ) 125 | target_link_libraries( sl3 PUBLIC ${CMAKE_THREAD_LIBS_INIT} ) 126 | 127 | # TODO, make it the same as find_package(SQLite3 REQUIRED) does 128 | 129 | #message(STATUS "**DEFINES ARE: "${mysqlt3_DEFINES} ) 130 | #message(STATUS "**LIBRARIES ARE: "${sl3_sqlite3LIBS} ) 131 | 132 | # else (sl3_USE_INTERNAL_SQLITE3) 133 | # INCLUDE(FindPkgConfig) 134 | 135 | # # TODO define a minimum version 3.3.8 136 | 137 | # # TODO what if I am on windows ... 138 | # # SQLITE3_INCLUDE_DIRS 139 | # # SQLITE3_LIBRARIES 140 | # # SQLITE3_LIBRARY_DIRS 141 | # pkg_check_modules(SQLITE3 REQUIRED sqlite3) 142 | # set(sl3_sqlite3LIBS ${SQLITE3_LIBRARIES} CACHE STRING "sqlite3 libraries") 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/sl3/database.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include "connection.hpp" 14 | 15 | namespace 16 | { 17 | sqlite3* 18 | opendb (const std::string& name, int openFlags) 19 | { 20 | if (openFlags == 0) 21 | { 22 | openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; 23 | } 24 | 25 | sqlite3* db = nullptr; 26 | auto sl3rc = sqlite3_open_v2 (name.c_str (), &db, openFlags, nullptr); 27 | 28 | if (sl3rc != SQLITE_OK) 29 | { 30 | using scope_guard 31 | = std::unique_ptr; 32 | scope_guard guard{db, &sqlite3_close}; 33 | throw sl3::SQLite3Error{sl3rc, sqlite3_errmsg (db)}; 34 | } 35 | return db; 36 | } 37 | 38 | } 39 | 40 | namespace sl3 41 | { 42 | 43 | std::string 44 | getErrStr (int errcode) 45 | { 46 | return std::string (sqlite3_errstr (errcode)); 47 | } 48 | 49 | Database::Database (const std::string& name, int flags) 50 | : _connection{new internal::Connection{opendb (name, flags)}} 51 | { 52 | sqlite3_extended_result_codes (_connection->db (), true); 53 | } 54 | 55 | Database::Database (Database&& other) noexcept 56 | : _connection (std::move (other._connection)) 57 | { 58 | // always have a connection, but an invalid one 59 | other._connection.reset (new internal::Connection{nullptr}); 60 | } 61 | 62 | Database::~Database () noexcept 63 | { 64 | if (_connection != nullptr) 65 | { 66 | _connection->close (); 67 | } 68 | } 69 | 70 | Command 71 | Database::prepare (const std::string& sql) 72 | { 73 | return {_connection, sql}; 74 | } 75 | 76 | Command 77 | Database::prepare (const std::string& sql, const DbValues& parameters) 78 | { 79 | return {_connection, sql, parameters}; 80 | } 81 | 82 | void 83 | Database::execute (const std::string& sql) 84 | { 85 | execute (sql.c_str ()); 86 | } 87 | 88 | void 89 | Database::execute (const char* sql) 90 | { 91 | _connection->ensureValid (); 92 | char* dbMsg = nullptr; 93 | 94 | int rc = sqlite3_exec (_connection->db (), sql, nullptr, nullptr, &dbMsg); 95 | 96 | if (rc != SQLITE_OK) 97 | { 98 | using scope_guard = std::unique_ptr; 99 | scope_guard guard (dbMsg, &sqlite3_free); 100 | throw SQLite3Error{rc, dbMsg}; 101 | } 102 | } 103 | 104 | void 105 | Database::execute (const std::string& sql, RowCallback& cb) 106 | { 107 | prepare (sql).execute (cb); 108 | } 109 | 110 | void 111 | Database::execute (const std::string& sql, Callback cb) 112 | { 113 | prepare (sql).execute (std::move (cb)); 114 | } 115 | 116 | Dataset 117 | Database::select (const std::string& sql) 118 | { 119 | return Command (_connection, sql).select (); 120 | } 121 | 122 | Dataset 123 | Database::select (const std::string& sql, const Types& types) 124 | { 125 | return Command (_connection, sql).select (types); 126 | } 127 | 128 | DbValue 129 | Database::selectValue (const std::string& sql) 130 | { 131 | DbValue retVal (Type::Variant); 132 | 133 | Callback cb = [&retVal] (Columns cols) -> bool { 134 | retVal = cols.getValue (0); 135 | return false; // exit after first row 136 | }; 137 | 138 | Command cmd (_connection, sql); 139 | cmd.execute (cb); 140 | 141 | return retVal; 142 | } 143 | 144 | DbValue 145 | Database::selectValue (const std::string& sql, Type type) 146 | { 147 | DbValue retVal{type}; 148 | 149 | Callback cb = [&retVal, type] (Columns cols) -> bool { 150 | retVal = cols.getValue (0, type); 151 | return false; // exit after first row 152 | }; 153 | 154 | Command cmd (_connection, sql); 155 | cmd.execute (cb); 156 | 157 | return retVal; 158 | } 159 | 160 | int 161 | Database::getMostRecentErrCode () 162 | { 163 | _connection->ensureValid (); 164 | return sqlite3_extended_errcode (_connection->db ()); 165 | } 166 | 167 | std::string 168 | Database::getMostRecentErrMsg () 169 | { 170 | _connection->ensureValid (); 171 | return std::string (sqlite3_errmsg (_connection->db ())); 172 | } 173 | 174 | std::size_t 175 | Database::getTotalChanges () 176 | { 177 | _connection->ensureValid (); 178 | return static_cast ( 179 | sqlite3_total_changes (_connection->db ())); 180 | } 181 | 182 | std::size_t 183 | Database::getRecentlyChanged () 184 | { 185 | _connection->ensureValid (); 186 | return static_cast (sqlite3_changes (_connection->db ())); 187 | } 188 | 189 | int64_t 190 | Database::getLastInsertRowid () 191 | { 192 | _connection->ensureValid (); 193 | return sqlite3_last_insert_rowid (_connection->db ()); 194 | } 195 | 196 | sqlite3* 197 | Database::db () 198 | { 199 | return _connection->db (); 200 | } 201 | 202 | auto 203 | Database::beginTransaction () -> Transaction 204 | { 205 | return Transaction{*this}; 206 | } 207 | 208 | Database::Transaction::Transaction (Database& db) 209 | : _db (&db) 210 | { 211 | _db->execute ("BEGIN TRANSACTION"); 212 | } 213 | 214 | Database::Transaction::Transaction (Transaction&& other) noexcept 215 | : _db (other._db) 216 | { 217 | other._db = nullptr; // ensure other does not commit in d'tor 218 | } 219 | 220 | Database::Transaction::~Transaction () 221 | { 222 | if (_db) 223 | _db->execute ("ROLLBACK TRANSACTION"); 224 | } 225 | 226 | void 227 | Database::Transaction::commit () 228 | { 229 | if (_db) 230 | { 231 | _db->execute ("COMMIT TRANSACTION"); 232 | _db = nullptr; 233 | } 234 | } 235 | 236 | } // ns 237 | -------------------------------------------------------------------------------- /src/sl3/columns.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "utils.hpp" 16 | 17 | namespace sl3 18 | { 19 | Columns::Columns (sqlite3_stmt* stmt) 20 | : _stmt (stmt) 21 | { 22 | } 23 | 24 | int 25 | Columns::count () const 26 | { 27 | return sqlite3_column_count (_stmt); 28 | } 29 | 30 | std::string 31 | Columns::getName (int idx) const 32 | { 33 | if (idx < 0 || !(idx < count ())) 34 | throw ErrOutOfRange ("column index out of range"); 35 | 36 | const char* name = sqlite3_column_name (_stmt, idx); 37 | return name ? std::string (name) : std::string (); 38 | } 39 | 40 | DbValue 41 | Columns::getValue (int idx) const 42 | { 43 | return getValue (idx, Type::Variant); 44 | } 45 | 46 | DbValue 47 | Columns::getValue (int idx, Type type) const 48 | { 49 | if (idx < 0 || !(idx < count ())) 50 | throw ErrOutOfRange ("column index out of range"); 51 | 52 | switch (sqlite3_column_type (_stmt, idx)) 53 | { 54 | case SQLITE_INTEGER: 55 | return DbValue (getInt64 (idx), type); 56 | break; 57 | 58 | case SQLITE_FLOAT: 59 | return DbValue (getReal (idx), type); 60 | break; 61 | 62 | case SQLITE_TEXT: 63 | return DbValue (getText (idx), type); 64 | break; 65 | 66 | case SQLITE_BLOB: 67 | return DbValue (getBlob (idx), type); 68 | break; 69 | 70 | // return this one at the end 71 | // some compiler detect unreachable code and warn 72 | case SQLITE_NULL: 73 | // return DbValue (type); 74 | break; 75 | 76 | default: // LCOV_EXCL_START 77 | throw ErrUnexpected ("should never reach this line"); 78 | break; // LCOV_EXCL_STOP 79 | } 80 | // some complains non reachable code. 81 | // therefore, handle the Null value here 82 | return DbValue (type); 83 | } 84 | 85 | std::vector 86 | Columns::getNames () const 87 | { 88 | std::vector names; 89 | names.reserve (as_size_t (count ())); 90 | for (int i = 0; i < count (); ++i) 91 | { 92 | names.emplace_back (getName (i)); 93 | } 94 | 95 | return names; 96 | } 97 | 98 | DbValues 99 | Columns::getRow () const 100 | { 101 | DbValues::container_type v; 102 | for (int i = 0; i < count (); ++i) 103 | { 104 | v.push_back (getValue (i)); 105 | } 106 | return DbValues (std::move (v)); 107 | } 108 | 109 | DbValues 110 | Columns::getRow (const Types& types) const 111 | { 112 | if (types.size () != static_cast (count ())) 113 | { 114 | throw ErrTypeMisMatch ("types.size () != getColumnCount ()"); 115 | } 116 | 117 | DbValues::container_type v; 118 | for (int i = 0; i < count (); ++i) 119 | { 120 | v.push_back (getValue (i, types[as_size_t (i)])); 121 | } 122 | return DbValues (std::move (v)); 123 | } 124 | 125 | Type 126 | Columns::getType (int idx) const 127 | { 128 | if (idx < 0 || !(idx < count ())) 129 | throw ErrOutOfRange ("column index out of range"); 130 | 131 | auto type = Type::Variant; 132 | 133 | switch (sqlite3_column_type (_stmt, idx)) 134 | { 135 | case SQLITE_INTEGER: 136 | type = Type::Int; 137 | break; 138 | 139 | case SQLITE_FLOAT: 140 | type = Type::Real; 141 | break; 142 | 143 | case SQLITE_TEXT: 144 | type = Type::Text; 145 | break; 146 | 147 | case SQLITE_BLOB: 148 | type = Type::Blob; 149 | break; 150 | 151 | case SQLITE_NULL: 152 | type = Type::Null; 153 | break; 154 | 155 | default: 156 | throw ErrUnexpected ("never reach"); // LCOV_EXCL_LINE 157 | break; // LCOV_EXCL_LINE 158 | } 159 | 160 | return type; 161 | } 162 | 163 | size_t 164 | Columns::getSize (int idx) const 165 | { 166 | if (idx < 0 || !(idx < count ())) 167 | throw ErrOutOfRange ("column index out of range"); 168 | 169 | return as_size_t (sqlite3_column_bytes (_stmt, idx)); 170 | } 171 | 172 | std::string 173 | Columns::getText (int idx) const 174 | { 175 | if (idx < 0 || !(idx < count ())) 176 | throw ErrOutOfRange ("column index out of range"); 177 | 178 | const char* first 179 | = reinterpret_cast (sqlite3_column_text (_stmt, idx)); 180 | std::size_t s = as_size_t (sqlite3_column_bytes (_stmt, idx)); 181 | return s > 0 ? std::string (first, s) : std::string (); 182 | } 183 | 184 | int 185 | Columns::getInt (int idx) const 186 | { 187 | if (idx < 0 || !(idx < count ())) 188 | throw ErrOutOfRange ("column index out of range"); 189 | 190 | return sqlite3_column_int (_stmt, idx); 191 | } 192 | 193 | int64_t 194 | Columns::getInt64 (int idx) const 195 | { 196 | if (idx < 0 || !(idx < count ())) 197 | throw ErrOutOfRange ("column index out of range"); 198 | 199 | return sqlite3_column_int64 (_stmt, idx); 200 | } 201 | 202 | double 203 | Columns::getReal (int idx) const 204 | { 205 | if (idx < 0 || !(idx < count ())) 206 | throw ErrOutOfRange ("column index out of range"); 207 | 208 | return sqlite3_column_double (_stmt, idx); 209 | } 210 | 211 | Blob 212 | Columns::getBlob (int idx) const 213 | { 214 | if (idx < 0 || !(idx < count ())) 215 | throw ErrOutOfRange ("column index out of range"); 216 | 217 | using value_type = Blob::value_type; 218 | const value_type* first 219 | = static_cast (sqlite3_column_blob (_stmt, idx)); 220 | std::size_t s = as_size_t (sqlite3_column_bytes (_stmt, idx)); 221 | return s > 0 ? Blob (first, first + s) : Blob (); 222 | } 223 | 224 | } // ns 225 | -------------------------------------------------------------------------------- /include/sl3/dataset.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3_DATASET_HPP_ 10 | #define SL3_DATASET_HPP_ 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace sl3 21 | { 22 | /** 23 | * \brief A utility for processing the result queries. 24 | * 25 | * This class is a RowCallback that loads the result of a Query into a list 26 | * of DbValues. 27 | * The loaded list can be browsed and the loaded values can be accessed. 28 | * 29 | * A Dataset can either be created: 30 | * - giving a DbValuesTypeList that describes the fields \n 31 | * Giving a DbValuesTypeList the types of DbValue will use the given 32 | * storage 33 | * type when reading 34 | * the data from sqlite. \n 35 | * When the Dataset becomes populated with data the field count will be 36 | * validated. 37 | * In case the number of fields is different sl3::ErrTypeMisMatch will 38 | * be thrown. 39 | * 40 | * - Without any specification \n 41 | * In this case all fields will be DsVariantField , 42 | * using the storage type sqlite reports for the actual value. 43 | * 44 | * 45 | * 46 | */ 47 | class LIBSL3_API Dataset final : public Container> 48 | { 49 | friend class Command; 50 | 51 | public: 52 | /** 53 | * \brief Constructor 54 | * 55 | * All fields will be DsVariantField , using the storage type sqlite 56 | * reports for the actual value. 57 | * Field count will be detected and applied. 58 | */ 59 | Dataset () noexcept; 60 | 61 | /** 62 | * \brief Constructor wiht DbValuesTypeList as description 63 | * 64 | * Types of DbValue will use the given description when creating the 65 | * DbValue list. 66 | * If the given list is not empty, field count will be validated when 67 | * the actual instance becomes populated with data. 68 | * \param types Types the fields must satisfy 69 | */ 70 | Dataset (Types types); 71 | 72 | /** 73 | * \brief Copy Constructor 74 | */ 75 | Dataset (const Dataset&) = default; 76 | 77 | /** 78 | * \brief Move Constructor 79 | */ 80 | Dataset (Dataset&&) noexcept ( 81 | std::is_nothrow_move_constructible>::value 82 | && std::is_nothrow_move_constructible::value 83 | && std::is_nothrow_move_constructible< 84 | std::vector>::value); 85 | // = default; no mscv does not like it 86 | 87 | /** 88 | * \brief Value assignment 89 | * \return reference to this 90 | */ 91 | Dataset& operator= (const Dataset&) = default; 92 | 93 | /** 94 | * \brief Rvalues assignment 95 | * \return reference to this 96 | */ 97 | Dataset& operator= (Dataset&&) = default; 98 | 99 | /** 100 | * \brief Clear all states. 101 | * Removes loaded data so that the actual instance can be reused/refilled. 102 | * 103 | */ 104 | void reset (); 105 | 106 | /** 107 | * \brief Clear all states. 108 | * 109 | * Removes loaded data and sets a new specification for the field 110 | * description 111 | * so that the actual instance can be reused for populate with a different 112 | * select statement / sql command. 113 | * Passing an empty DbValuesTypeList mean that all fields will be 114 | * DsVariandField and field count will be detected. 115 | * 116 | * \param types new Types requirement 117 | */ 118 | void reset (const Types& types); 119 | 120 | /** 121 | * \brief Merge an other Dataset. 122 | * 123 | * Appends the data of the given Dataset to the end of the actual data. 124 | * The field names and types of the given Dataset must match the actuals 125 | * one. 126 | * 127 | * \throw sl3::ErrTypeMisMatch if field names types are not equal or 128 | * size differs. 129 | * 130 | * \param other Dataset which shall be added to this one. 131 | */ 132 | void merge (const Dataset& other); 133 | 134 | /** 135 | * \brief Merge DbValues. 136 | * 137 | * Appends the DbValues to the end of the actual data. 138 | * 139 | * \throw sl3::ErrTypeMisMatch if size differs from existing row size or 140 | * if types are not compatible 141 | * 142 | * \param row A row which shall be added. 143 | */ 144 | void merge (const DbValues& row); 145 | 146 | /** 147 | * \brief Get the index of a field by namespace 148 | * 149 | * \throw sl3::OutOfRanage if name is not found 150 | * \param name field name 151 | * \return field index 152 | */ 153 | std::size_t getIndex (const std::string& name) const; 154 | 155 | /** 156 | * \brief Typedef for a relation function signature 157 | * 158 | * Used to specify the the less function that shall be used for sorting 159 | * a Dataset. 160 | * 161 | * \see Dataset::sort 162 | */ 163 | typedef bool (*DbValueSort) (const DbValue&, const DbValue&); 164 | 165 | // using DbValueSort = std::function 166 | // ; 167 | 168 | /** 169 | * \brief Sort the Dataset 170 | * 171 | * Sort according to the given field indexes. 172 | * The Dataset will be sorted according to sqlite rules. 173 | * 174 | * \throw sl2::OutOfRange if a given index is invalid 175 | * \param idxs list of field indexes 176 | * \param cmp pointer to a less than compare function, default dbval_lt 177 | */ 178 | void sort (const std::vector& idxs, DbValueSort cmp = &dbval_lt); 179 | 180 | private: 181 | Types _fieldtypes; 182 | std::vector _names; 183 | }; 184 | } 185 | 186 | #endif 187 | -------------------------------------------------------------------------------- /include/sl3/container.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3_CONTAINER_HPP_ 10 | #define SL3_CONTAINER_HPP_ 11 | 12 | #include 13 | 14 | namespace sl3 15 | { 16 | /** 17 | * \brief Wrapper to provide begin, end and random access of a container 18 | * 19 | * \tparam ContainerType a container like for example std::vector 20 | * 21 | * A ContainerType has begin, end, random access and size. 22 | * 23 | * This class makes these methods accessible and hides the others, like a 24 | * push_back, erase, ... 25 | * A derived class can decide which other methods shall become visible. 26 | * 27 | */ 28 | template class Container 29 | { 30 | public: 31 | //@{ 32 | using conatiner_type = ContainerType; 33 | using iterator = typename conatiner_type::iterator; 34 | using const_iterator = typename conatiner_type::const_iterator; 35 | using value_type = typename conatiner_type::value_type; 36 | using reference = typename conatiner_type::reference; 37 | using const_reference = typename conatiner_type::const_reference; 38 | using size_type = typename conatiner_type::size_type; 39 | using container_type = ContainerType; 40 | //@} 41 | 42 | /** 43 | * \brief Constructor. 44 | * 45 | * Create an empty container. 46 | * 47 | */ 48 | Container () noexcept {} 49 | /** 50 | * \brief Constructor 51 | * 52 | * \param container values 53 | */ 54 | Container (ContainerType container) noexcept ( 55 | std::is_nothrow_move_constructible::value) 56 | : _cont (std::move (container)) 57 | { 58 | } 59 | 60 | /** 61 | * \brief Constructor 62 | * 63 | * \param container values 64 | */ 65 | Container (std::initializer_list container) 66 | : _cont (std::move (container)) 67 | { 68 | } 69 | 70 | /** 71 | * \brief Copy Constructor 72 | */ 73 | Container (const Container&) = default; 74 | 75 | /** 76 | * \brief Assignment 77 | * \return reference to this 78 | */ 79 | Container& operator= (const Container&) = default; 80 | 81 | /** 82 | * \brief Move constructor 83 | */ 84 | Container (Container&&) noexcept ( 85 | std::is_nothrow_move_constructible::value) 86 | = default; 87 | 88 | /** 89 | * \brief Move assignment 90 | * \return reference to this 91 | */ 92 | Container& operator= (Container&&) = default; 93 | 94 | /** 95 | * \brief Destructor 96 | */ 97 | virtual ~Container () noexcept ( 98 | std::is_nothrow_destructible::value) 99 | = default; 100 | 101 | /** 102 | * \brief Iterator access 103 | * \return requested iterator 104 | */ 105 | iterator 106 | begin () 107 | { 108 | return std::begin (_cont); 109 | } 110 | 111 | /** 112 | * \brief Iterator access 113 | * \return requested iterator 114 | */ 115 | const_iterator 116 | begin () const 117 | { 118 | return _cont.cbegin (); 119 | } 120 | 121 | /** 122 | * \brief Iterator access 123 | * \return requested iterator 124 | */ 125 | iterator 126 | end () 127 | { 128 | return std::end (_cont); 129 | } 130 | 131 | /** 132 | * \brief Iterator access 133 | * \return requested iterator 134 | */ 135 | const_iterator 136 | end () const 137 | { 138 | return _cont.cend (); 139 | } 140 | 141 | /** 142 | * \brief Iterator access 143 | * \return requested iterator 144 | */ 145 | const_iterator 146 | cbegin () const 147 | { 148 | return _cont.cbegin (); 149 | } 150 | 151 | /** 152 | * \brief Iterator access 153 | * \return requested iterator 154 | */ 155 | const_iterator 156 | cend () const 157 | { 158 | return _cont.cend (); 159 | }; 160 | 161 | /// 162 | 163 | /** 164 | * \brief Container size 165 | * \return number of elements 166 | */ 167 | size_type 168 | size () const 169 | { 170 | return _cont.size (); 171 | } 172 | 173 | /**\brief checked random access 174 | * \param i index 175 | * \throw sl3::ErrOutOfRange if index is invalid 176 | * \return reference to element at requested index 177 | */ 178 | reference 179 | at (size_t i) 180 | { 181 | try 182 | { 183 | return _cont.at (i); 184 | } 185 | catch (...) 186 | { 187 | throw ErrOutOfRange ("no data at: " + std::to_string (i)); 188 | } 189 | } 190 | 191 | /** \brief checked random access 192 | * 193 | * \param i index 194 | * \throw sl3::ErrOutOfRange if index is invalid 195 | * \return const reference to element at requested index 196 | */ 197 | const_reference 198 | at (size_t i) const 199 | { 200 | try 201 | { 202 | return _cont.at (i); 203 | } 204 | catch (...) 205 | { 206 | throw ErrOutOfRange ("no data at: " + std::to_string (i)); 207 | } 208 | } 209 | 210 | /**\brief unchecked random access 211 | * \param i index 212 | * behaves undefined if given index is invalid 213 | * \return reference to element at requested index 214 | */ 215 | reference 216 | operator[] (size_t i) 217 | { 218 | return _cont[i]; 219 | } 220 | /**\brief unchecked random access 221 | * \param i index 222 | * behaves undefined if given index is invalid 223 | * \return reference to element at requested index 224 | */ 225 | const_reference 226 | operator[] (size_t i) const 227 | { 228 | return _cont[i]; 229 | } 230 | 231 | protected: 232 | /// Container T 233 | ContainerType _cont; 234 | }; 235 | } 236 | 237 | #endif /* SL3_CONTAINER_HPP_ */ 238 | -------------------------------------------------------------------------------- /include/sl3/error.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3__ERROR_HPP_ 10 | #define SL3__ERROR_HPP_ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | namespace sl3 20 | { 21 | /** 22 | * \brief Error codes 23 | * 24 | * These enum values used to create ErrType objects. 25 | * 26 | * Each ErrCode becomes an ErrType object. 27 | * 28 | */ 29 | enum class ErrCode 30 | { 31 | SQL3Error = 1, ///< sqlite3 error happened 32 | NoConnection = 2, ///< no database 33 | OutOfRange = 5, ///< index op out of range 34 | TypeMisMatch = 6, ///< type cast problem 35 | NullValueAccess = 7, ///< accessing a value that is Null 36 | UNEXPECTED = 99 ///< for everything that happens unexpected 37 | }; 38 | 39 | /** 40 | * \brief get textual representantion (the name) of an ErrCode 41 | * \param ec wanted ErrCode name 42 | * \return the ErrCode name 43 | */ 44 | constexpr const char* 45 | ErrCodeName (ErrCode ec) 46 | { 47 | return ec == ErrCode::SQL3Error ? "SQLite3Error" 48 | : ec == ErrCode::NoConnection ? "NoConnection" 49 | : ec == ErrCode::OutOfRange ? "OutOfRange" 50 | : ec == ErrCode::TypeMisMatch ? "TypeMisMatch" 51 | : ec == ErrCode::NullValueAccess ? "NullValueAccess" 52 | : ec == ErrCode::UNEXPECTED ? "UNEXPECTED" 53 | : "NA"; 54 | } 55 | 56 | /** 57 | * \brief Exception base type 58 | * 59 | * A command base for all ErrType objects. 60 | * All exceptions thrown by the library wil have this base type. 61 | */ 62 | class LIBSL3_API Error : public std::runtime_error 63 | { 64 | protected: 65 | public: 66 | /// Inherit constructors from std::runtime_error 67 | using std::runtime_error::runtime_error; 68 | 69 | /** 70 | * \brief Get ErrCode 71 | * \return the Errcode of the excetion 72 | */ 73 | virtual ErrCode getId () const = 0; 74 | }; 75 | 76 | /** 77 | * \brief overloaded stream operator 78 | * \param os ostream 79 | * \param e the Error 80 | * \return the ostream 81 | */ 82 | LIBSL3_API std::ostream& operator<< (std::ostream& os, const Error& e); 83 | 84 | /** 85 | * \brief Object class representing an ErrCode 86 | * 87 | * Allows typedef objects using an Errcode. 88 | * Each sl3::ErrCode becomes an ErrType object. 89 | */ 90 | template class LIBSL3_API ErrType final : public Error 91 | { 92 | public: 93 | ErrType () 94 | : Error ("") 95 | { 96 | } 97 | 98 | using Error::Error; 99 | 100 | ErrCode 101 | getId () const override 102 | { 103 | return ec; 104 | } 105 | }; 106 | 107 | /// error that will be thrown if no open database was available but needed 108 | using ErrNoConnection = ErrType; 109 | 110 | /// thrown if an index op is out of range 111 | using ErrOutOfRange = ErrType; 112 | 113 | /** 114 | * \brief thrown in case of an incorrect type usage 115 | */ 116 | using ErrTypeMisMatch = ErrType; 117 | 118 | /// thrown in case of accessing a Null value field/parameter 119 | using ErrNullValueAccess = ErrType; 120 | 121 | /// thrown if something unexpected happened, mostly used by test tools and in 122 | /// debug mode 123 | using ErrUnexpected = ErrType; 124 | 125 | /** 126 | * \brief packaging an error from sqlite3. 127 | * This exception will be thrown if sqlite3 reports an error. 128 | * 129 | * Holds the sqlite3 error code and the error message if it is available. 130 | * \see Database::getMostRecentErrMsg 131 | * \see Database::getMostRecentErrCode 132 | * 133 | * \n Extended error codes can be obtained through Database class. 134 | * \see Database::getMostRecentExtendedErrCode 135 | * \see Database::enableExtendedResultCodes 136 | * 137 | */ 138 | template <> class LIBSL3_API ErrType final : public Error 139 | { 140 | public: 141 | /** 142 | * \brief c'tor 143 | * \param sl3ec sqite error code 144 | * \param sl3msg sqite error code 145 | * \param msg additional message 146 | */ 147 | ErrType (int sl3ec, const char* sl3msg, const std::string& msg) 148 | : Error ("(" + std::to_string (sl3ec) + ":" + sl3msg + "):" + msg) 149 | , _sqlite_ec (sl3ec) 150 | , _sqlite_msg (sl3msg) 151 | { 152 | } 153 | 154 | /** 155 | * \brief c'tor 156 | * \param sl3ec sqite error code 157 | * \param sl3msg sqite error code 158 | */ 159 | ErrType (int sl3ec, const char* sl3msg) 160 | : ErrType (sl3ec, sl3msg, "") 161 | { 162 | } 163 | ErrCode 164 | getId () const override 165 | { 166 | return ErrCode::SQL3Error; 167 | } 168 | 169 | /** 170 | * \brief Get the sqlite3 error code 171 | * \return the sqlite3 error code 172 | */ 173 | int 174 | SQLiteErrorCode () const 175 | { 176 | return _sqlite_ec; 177 | } 178 | 179 | /** 180 | * \brief Get the sqlite3 error message. 181 | * 182 | * If the exception was created with a sqlite a error message 183 | * it can be accessed here. 184 | * 185 | * \return the sqlite3 error message 186 | */ 187 | const std::string& 188 | SQLiteErrorMessage () const 189 | { 190 | return _sqlite_msg; 191 | }; 192 | 193 | private: 194 | int _sqlite_ec; 195 | std::string _sqlite_msg; 196 | }; 197 | 198 | /** 199 | * \brief Alias for ErrType 200 | * 201 | * A short alias for an ErrType 202 | */ 203 | using SQLite3Error = ErrType; 204 | 205 | #define ASSERT_EXCEPT(exp, except) \ 206 | if (!(exp)) \ 207 | throw except (std::string (__FUNCTION__) + ": " + #exp) 208 | 209 | /* 210 | TODO check if this can be done, togehter with __FUNCTION__ 211 | into a nice code locastion for nicer messages 212 | File.cpp::Function::39 The problem 213 | 214 | 215 | using cstr = const char * const; 216 | 217 | static constexpr cstr past_last_slash(cstr str, cstr last_slash) 218 | { 219 | return 220 | *str == '\0' ? last_slash : 221 | *str == '/' ? past_last_slash(str + 1, str + 1) : 222 | past_last_slash(str + 1, last_slash); 223 | } 224 | 225 | static constexpr cstr past_last_slash(cstr str) 226 | { 227 | return past_last_slash(str, str); 228 | } 229 | 230 | #define __SHORT_FILE__ ({constexpr cstr sf__ {past_last_slash(__FILE__)}; 231 | sf__;}) 232 | 233 | */ 234 | 235 | } // ns 236 | 237 | #endif /* ...ERROR_HPP_ */ 238 | -------------------------------------------------------------------------------- /include/sl3/columns.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3_COLUMNS_HPP 10 | #define SL3_COLUMNS_HPP 11 | 12 | #include 13 | #include 14 | 15 | struct sqlite3_stmt; 16 | 17 | namespace sl3 18 | { 19 | 20 | // TODO add to docu 21 | // index access always checked, since docu says 22 | // 'if the column index is out of range, the result is undefined' 23 | // but if you fell lke preoptimization is requiresd, 24 | // feel free to access the underlying sqlite3_stmt via 25 | // and adopt the index access! 26 | 27 | /** 28 | * \brief Class to access data of query results. 29 | * 30 | * A Columns instance is constructed by a Command and passed to the 31 | * callback which handles the results of a query. 32 | * 33 | * \see RowCallback 34 | * \see Command::Callback 35 | * \see Database::Callback 36 | */ 37 | class LIBSL3_API Columns 38 | { 39 | friend class Command; 40 | 41 | Columns (sqlite3_stmt* stmt); 42 | 43 | // not be needed, even if they would not harm .. 44 | Columns& operator= (const Columns&) = delete; 45 | 46 | // not be needed, even if they would not harm .. 47 | Columns& operator= (Columns&&) = delete; 48 | 49 | Columns (const Columns&) = default; 50 | 51 | public: 52 | /** 53 | * \brief Move constructor 54 | * A column is movable 55 | */ 56 | Columns (Columns&&) = default; 57 | 58 | /** 59 | * \brief Destructor 60 | * 61 | */ 62 | ~Columns () = default; 63 | 64 | /** 65 | * \brief Number of columns in the statement. 66 | * 67 | * \return number of columns 68 | */ 69 | int count () const; 70 | 71 | /** 72 | * \brief Column name of given index. 73 | * 74 | * 75 | * \throw sl3::ErrOutOfRange if idx is invalid, 76 | * \param idx wanted column index 77 | * \return column name or an empty string if index is invalid 78 | */ 79 | std::string getName (int idx) const; 80 | 81 | /** 82 | * \brief Get columns names 83 | * 84 | * Will return a list of size count() . 85 | * Unnamed columns will be an ampty string 86 | * 87 | * \return the names 88 | */ 89 | std::vector getNames () const; 90 | 91 | /** 92 | * \brief Get the value at a given index 93 | * 94 | * The returned DbValue::getType() will be a Type::Variant and 95 | * DbValue::getStorageType() will be set to the sqlite3 storage type. 96 | * 97 | * \param idx wanted index 98 | * \throw sl3::ErrOutOfRange if idx is invalid, 99 | * \return DbValue of requested column 100 | */ 101 | DbValue getValue (int idx) const; 102 | 103 | /** 104 | * \brief Get the value at a given index 105 | * 106 | * The returned DbValue::getType() and DbValue::getStorageType() 107 | * will be set to the given type. 108 | * 109 | * \param idx wanted index 110 | * \param type wanted type 111 | * \throw sl3::ErrTypeMisMatch if type is not the sqlite type 112 | * \throw sl3::ErrOutOfRange if idx is invalid, 113 | * \return DbValue of requested column 114 | */ 115 | DbValue getValue (int idx, Type type) const; 116 | 117 | /** 118 | * \brief Get all columns at once 119 | * 120 | * All DbValue object will be of Type::Variant 121 | * 122 | * \return DbValues of the column values 123 | */ 124 | DbValues getRow () const; 125 | 126 | /** 127 | * \brief Get all columns at once using the given types 128 | * 129 | * If a value does not math the given types an exception will be thown 130 | * 131 | * \param types wanted Types 132 | * \throw sl3::ErrTypeMisMatch in case of an incorrect type 133 | * \return DbValues of the column values 134 | */ 135 | DbValues getRow (const Types& types) const; 136 | 137 | /** 138 | * \brief Get the sqlite type for a column 139 | * 140 | * If used, should be called before accessing the value of the column 141 | * at the given index, otherwise the typed access might set the type. 142 | * 143 | * This method can be used to check if a column isNull. 144 | * 145 | * \param idx wanted index 146 | * \throw sl3::ErrOutOfRange if idx is invalid 147 | * \return Type sqlite interprets the value 148 | */ 149 | Type getType (int idx) const; 150 | 151 | /** 152 | * \brief Get the size of a column 153 | * 154 | * The size sqlite3 uses to store the value of the given field. 155 | * 156 | * \param idx wanted index 157 | * \throw sl3::ErrOutOfRange if idx is invalid 158 | * \return size sqlite uses for the column 159 | */ 160 | size_t getSize (int idx) const; 161 | 162 | /** 163 | * \brief Get the value of a column. 164 | * 165 | * If a column is Null of of a different type, the sqlite3 conversion 166 | * rules are applied. 167 | * 168 | * \param idx column index 169 | * \throw sl3::ErrOutOfRange if idx is invalid 170 | * \return column value 171 | */ 172 | std::string getText (int idx) const; 173 | 174 | /** 175 | * \brief Get the value of a column. 176 | * 177 | * If a column is Null of of a different type, the sqlite3 conversion 178 | * rules are applied. 179 | * 180 | * \param idx column index 181 | * \throw sl3::ErrOutOfRange if idx is invalid 182 | * \return column value 183 | */ 184 | int getInt (int idx) const; 185 | 186 | /** 187 | * \brief Get the value of a column. 188 | * 189 | * If a column is Null of of a different type, the sqlite3 conversion 190 | * rules are applied. 191 | * \param idx column index 192 | * \throw sl3::ErrOutOfRange if idx is invalid 193 | * \return column value 194 | */ 195 | int64_t getInt64 (int idx) const; 196 | 197 | /** 198 | * \brief Get the value of a column. 199 | * 200 | * If a column is Null of of a different type, the sqlite3 conversion 201 | * rules are applied. 202 | * 203 | * \param idx column index 204 | * \throw sl3::ErrOutOfRange if idx is invalid 205 | * \return column value 206 | */ 207 | double getReal (int idx) const; 208 | 209 | /** 210 | * \brief Get the value of a column. 211 | * 212 | * If a column is Null of of a different type, the sqlite3 conversion 213 | * rules are applied. 214 | * 215 | * \param idx column index 216 | * \throw sl3::ErrOutOfRange if idx is invalid 217 | * \return column value 218 | */ 219 | Blob getBlob (int idx) const; 220 | 221 | /** 222 | * \brief Get the underlying sqlite3_stmt 223 | * 224 | * User defined QueryCallbacks might have their own way to do things, 225 | * so this getter provides access to the underlying sqlite3_stmt. 226 | * 227 | * \return underlying sqlite3_stmt 228 | */ 229 | sqlite3_stmt* 230 | get_stmt () const 231 | { 232 | return _stmt; 233 | } 234 | 235 | private: 236 | sqlite3_stmt* _stmt; 237 | }; 238 | } 239 | 240 | #endif 241 | -------------------------------------------------------------------------------- /include/sl3/command.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3_SQLCOMMAND_HPP 10 | #define SL3_SQLCOMMAND_HPP 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | struct sqlite3; 21 | struct sqlite3_stmt; 22 | 23 | namespace sl3 24 | { 25 | 26 | namespace internal 27 | { 28 | class Connection; 29 | } 30 | 31 | /** 32 | * \brief A compiled SQL command 33 | * 34 | * This class holds a compiled SQL statement so that it can be reused. 35 | * 36 | * A command can have parameters. 37 | * 38 | */ 39 | class LIBSL3_API Command 40 | { 41 | friend class Database; 42 | using Connection = std::shared_ptr; 43 | 44 | Command (Connection connection, const std::string& sql); 45 | 46 | Command (Connection connection, 47 | const std::string& sql, 48 | DbValues parameters); 49 | 50 | Command () = delete; 51 | Command (const Command&) = delete; 52 | Command operator= (const Command&) = delete; 53 | Command operator= (Command&&) = delete; 54 | 55 | public: 56 | /** 57 | * \brief Move constructor. 58 | * 59 | * A command is movable 60 | * 61 | */ 62 | Command (Command&&); 63 | 64 | /** 65 | * \brief Destructor. 66 | * 67 | * The destructor calls release. 68 | */ 69 | ~Command (); // cause of unique pointer, check if protected is possible 70 | 71 | /** 72 | * \brief Run the Command and get the result 73 | * 74 | * Runs the command, applying possible already given parameters 75 | * and returns the result in a Dataset. 76 | * 77 | * 78 | * \return A Dataset containing the query result 79 | */ 80 | Dataset select (); 81 | 82 | /** 83 | * \brief Run the Command and get the result 84 | * 85 | * Runs the command, applying given parameters 86 | * and returns the result in a Dataset. 87 | * It types are given, they are used for the returned Dataset. 88 | * It no types are given all fields in the returned Dataset will be 89 | * of Type::Variant 90 | * 91 | * \throw sl3::ErrTypeMisMatch if types are given which are invalid or 92 | * given parameters are of the wrong size. 93 | * \param parameters a list of parameters 94 | * \param types Types the Dataset shall use 95 | * \return A Dataset containing the query result 96 | */ 97 | Dataset select (const DbValues& parameters, const Types& types = {}); 98 | 99 | /** 100 | * \brief Run the Command and get the result 101 | * 102 | * Runs the command, applying possible given parameters 103 | * and returns the result in a Dataset in which given types are 104 | * used for the fields. 105 | * 106 | * \throw sl3::ErrTypeMisMatch if types are given which are invalid or 107 | * given parameters are of the wrong size. 108 | * \param parameters a list of parameters 109 | * \param types Types the Dataset shall use 110 | * \return A Dataset containing the query result 111 | */ 112 | Dataset select (const Types& types, const DbValues& parameters = {}); 113 | 114 | /** 115 | * \brief function object for handling a command result. 116 | * 117 | * Functor called for each processed row of Command or a 118 | * sql statement. 119 | * 120 | * \return false if processing the query result shall stop 121 | * \n true otherwise 122 | */ 123 | using Callback = std::function; 124 | 125 | /** 126 | * \brief Execute the command 127 | * 128 | * Runs the current command. 129 | * If parameters are set, 130 | * 131 | */ 132 | void execute (); 133 | 134 | /** 135 | * \brief Execute the command 136 | * 137 | * Applies given parameters and run the current command. 138 | * 139 | * \throw sl3::ErrTypeMisMatch given parameters are of the wrong size. 140 | * \param parameters a list of parameters 141 | */ 142 | void execute (const DbValues& parameters); 143 | 144 | /** 145 | * \brief Execute the command applying given callback 146 | * 147 | * Applies given parameters and run the current command. 148 | * 149 | * \throw sl3::ErrTypeMisMatch given parameters are of the wrong size. 150 | * \param cb a callback 151 | * \param parameters a list of parameters 152 | */ 153 | void execute (RowCallback& cb, const DbValues& parameters = {}); 154 | 155 | /** 156 | * \brief Execute the command applying given callback 157 | * 158 | * Applies given parameters and run the current command. 159 | * 160 | * \throw sl3::ErrTypeMisMatch given parameters are of the wrong size. 161 | * \param cb a callback 162 | * \param parameters a list of parameters 163 | */ 164 | void execute (Callback cb, const DbValues& parameters = {}); 165 | 166 | /** 167 | * \brief Parameters of command. 168 | * 169 | * \return referenct to the parameters 170 | */ 171 | DbValues& getParameters (); 172 | 173 | /** 174 | * \brief Parameters of command. 175 | * 176 | * \return const referenct to the parameters 177 | */ 178 | const DbValues& getParameters () const; 179 | 180 | /** 181 | * \brief get Parameter at given index. 182 | * \param idx index 183 | * \throw sl3::ErrOutOfRange if index is invalid 184 | * \return referenct to the parameters at given index 185 | */ 186 | DbValue& getParameter (int idx); 187 | 188 | /** 189 | * \brief get Parameter at given index. 190 | * \param idx index 191 | * \throw sl3::ErrOutOfRange if index is invalid 192 | * \return const referenct to the parameters at given index 193 | */ 194 | const DbValue& getParameter (int idx) const; 195 | 196 | /** 197 | * \brief Set parameter values 198 | * 199 | * The Types ot the given values must be compatible with the 200 | * current ones. 201 | * 202 | * \param values new paramter values 203 | * \throw sl3::ErrTypeMisMatch if size of values differs from the actual 204 | * parameter size or DbValue assignment throws. 205 | * \see DbValue 206 | * 207 | */ 208 | void setParameters (const DbValues& values); 209 | 210 | /** 211 | * \brief Set new parameter values 212 | * 213 | * In contrast to setParameters, where the DbValue types must match, 214 | * this can be used to set other Types. 215 | * 216 | * \param values new parameter values 217 | * \throw sl3::ErrTypeMisMatch if size of values differs from the actual 218 | * one 219 | */ 220 | void resetParameters (DbValues values); 221 | 222 | /** 223 | * \brief get a list of the parameter names 224 | * 225 | * If the command has no parameters, the list will be empty. 226 | * Parameters with no name will be an empty entry in the list. 227 | * 228 | * \return list of names 229 | */ 230 | std::vector getParameterNames () const; 231 | 232 | private: 233 | Connection _connection; 234 | sqlite3_stmt* _stmt; 235 | DbValues _parameters; 236 | }; 237 | 238 | /** 239 | * \brief Syntax sugar to create command parameters 240 | * 241 | * Creates DbValues with types based on the given arguments. 242 | * \code 243 | * cmd.execute(parameters(1,"foo",3.3)) 244 | * \endcode 245 | * Will create DbValues of Type::Int, Type::Text and Type::Real 246 | * 247 | * \tparam VALS variadic argument types 248 | * \param vals variadic argument values 249 | * 250 | * \return DbValues constructed by given arguments 251 | */ 252 | template 253 | DbValues 254 | parameters (VALS&&... vals) 255 | { 256 | return {DbValue{vals}...}; 257 | } 258 | } 259 | 260 | #endif 261 | -------------------------------------------------------------------------------- /tests/dataset/datasettest.cpp: -------------------------------------------------------------------------------- 1 | #include "../testing.hpp" 2 | #include 3 | 4 | #include 5 | 6 | SCENARIO ("dataset creation and defautl operatores") 7 | { 8 | using namespace sl3; 9 | GIVEN ("a database, a table and knowen data") 10 | { 11 | Database db{":memory:"}; 12 | db.execute ("CREATE TABLE t (int INTEGER,txt TEXT, dbl real );" 13 | "INSERT INTO t VALUES (1, 'eins', 1.111) ;" 14 | "INSERT INTO t VALUES (2, 'zwei', 2.22) ;" 15 | "INSERT INTO t VALUES (3, NULL, NULL) ;"); 16 | 17 | const Types types{Type::Int, Type::Text, Type::Real}; 18 | 19 | WHEN ("having an empty dataset") 20 | { 21 | Dataset ds; 22 | THEN ("assinging new dataset is possible without problems") 23 | { 24 | CHECK_NOTHROW (ds = db.select ("SELECT * FROM t;")); 25 | CHECK_EQ (ds.getIndex ("int"), 0); 26 | CHECK_EQ (ds.getIndex ("txt"), 1); 27 | CHECK_EQ (ds.getIndex ("dbl"), 2); 28 | CHECK_THROWS_AS (ds.getIndex ("abc"), ErrOutOfRange); 29 | 30 | CHECK_EQ (ds.size (), 3); 31 | CHECK_EQ (ds.at (0).size (), 3); 32 | } 33 | } 34 | 35 | WHEN ("having a typed dataset") 36 | { 37 | auto ds = db.select ("SELECT * FROM t;", types); 38 | 39 | THEN ("the fieds are not variants") 40 | { 41 | for (const auto& row : ds) 42 | { 43 | REQUIRE (row.size () == types.size ()); 44 | REQUIRE (row.at (0).dbtype () == types[0]); 45 | REQUIRE (row.at (1).dbtype () == types[1]); 46 | REQUIRE (row.at (2).dbtype () == types[2]); 47 | } 48 | } 49 | } 50 | 51 | WHEN ("creating a dataset via a rvalue") 52 | { 53 | auto dsm = db.select ("SELECT * FROM t;"); 54 | CHECK_NOTHROW (Dataset ds{std::move (dsm)}); 55 | 56 | THEN ("the ds include all data and names") 57 | { 58 | dsm = db.select ("SELECT * FROM t;"); 59 | Dataset ds{std::move (dsm)}; 60 | 61 | CHECK_EQ (ds.getIndex ("int"), 0); 62 | CHECK_EQ (ds.getIndex ("txt"), 1); 63 | CHECK_EQ (ds.getIndex ("dbl"), 2); 64 | } 65 | } 66 | } 67 | } 68 | 69 | SCENARIO ("merging datasets") 70 | { 71 | using namespace sl3; 72 | GIVEN ("a database and a typed empty dataset") 73 | { 74 | Database db{":memory:"}; 75 | Dataset ds ({Type::Int, Type::Text, Type::Real}); 76 | WHEN ("having a type compatible dataset") 77 | { 78 | const Types types = {Type::Int, Type::Text, Type::Real}; 79 | auto ds1 = db.select ("SELECT 1,'hello', 2.2;", types); 80 | 81 | THEN ("merging works") { CHECK_NOTHROW (ds.merge (ds1)); } 82 | AND_THEN ("merging compatible DbValues works") 83 | { 84 | CHECK_NOTHROW (ds.merge (ds1.at (0))); 85 | } 86 | } 87 | 88 | WHEN ("merge a dataset with different field count") 89 | { 90 | const auto dsdifferent 91 | = db.select ("SELECT 1,'hello';", {Type::Int, Type::Text}); 92 | THEN ("merging this dataset throws") 93 | { 94 | CHECK_THROWS_AS (ds.merge (dsdifferent), ErrTypeMisMatch); 95 | } 96 | AND_THEN ("merging just a row of this dataset also throws") 97 | { 98 | CHECK_THROWS_AS (ds.merge (dsdifferent.at (0)), ErrTypeMisMatch); 99 | } 100 | } 101 | 102 | WHEN ("having a dataset with incompatible types") 103 | { 104 | const auto dsdifferent = db.select ("SELECT 'he', 'll', 'o';"); 105 | 106 | THEN ("merging this ds throws") 107 | { 108 | CHECK_THROWS_AS (ds.merge (dsdifferent), ErrTypeMisMatch); 109 | } 110 | THEN ("merging a row of this ds throws") 111 | { 112 | CHECK_THROWS_AS (ds.merge (dsdifferent.at (0)), ErrTypeMisMatch); 113 | } 114 | 115 | AND_WHEN ("reset the fields but not the types") 116 | { 117 | THEN ("merging this ds still thorws") 118 | { 119 | ds.reset (); 120 | CHECK_THROWS_AS (ds.merge (dsdifferent), ErrTypeMisMatch); 121 | } 122 | } 123 | 124 | AND_WHEN ("reset the fields to compatible types") 125 | { 126 | THEN ("merging works") 127 | { 128 | ds.reset ({Type::Variant, Type::Variant, Type::Variant}); 129 | CHECK_NOTHROW (ds.merge (dsdifferent)); 130 | CHECK (ds.size () == 1); 131 | } 132 | } 133 | } 134 | 135 | WHEN ("having an dataset with names fields") 136 | { 137 | Dataset dsf = db.select ("SELECT 1 as int, 'eins' as txt, 2.2 as dbl;"); 138 | CHECK_EQ (dsf.getIndex ("int"), 0); 139 | CHECK_EQ (dsf.getIndex ("txt"), 1); 140 | CHECK_EQ (dsf.getIndex ("dbl"), 2); 141 | 142 | THEN ("merging a dataset with the same fieldnames work") 143 | { 144 | const auto orig_cout = dsf.size (); 145 | auto ds1 = db.select ("SELECT 1 as int, 'eins' as txt, 2.2 as dbl;"); 146 | CHECK_NOTHROW (dsf.merge (ds1)); 147 | CHECK (dsf.size () == orig_cout + 1); 148 | } 149 | 150 | AND_WHEN ("merging a dataset with different fieldnames throws") 151 | { 152 | auto ds1 = db.select ("SELECT 1 as foo, 'eins' as bar, 2.2 as bas;"); 153 | THEN ("merging throws") 154 | { 155 | CHECK_THROWS_AS (ds.merge (ds1), ErrTypeMisMatch); 156 | } 157 | } 158 | } 159 | } 160 | } 161 | 162 | SCENARIO ("sorting a dataset") 163 | { 164 | using namespace sl3; 165 | GIVEN ("a database, a table and known data") 166 | { 167 | Database db{":memory:"}; 168 | db.execute ("CREATE TABLE t (int INTEGER,txt TEXT, dbl real );" 169 | "INSERT INTO t VALUES (1, 1.1, 'b') ;" 170 | "INSERT INTO t VALUES (2, 0.11, 'c') ;" 171 | "INSERT INTO t VALUES (1, 0.2, 'a') ;"); 172 | 173 | WHEN ("having dataset with known order") 174 | { 175 | Dataset ds = db.select ("SELECT * FROM t;"); 176 | CHECK_EQ (ds[0][2].getText (), "b"); 177 | CHECK_EQ (ds[1][2].getText (), "c"); 178 | CHECK_EQ (ds[2][2].getText (), "a"); 179 | 180 | THEN ("sorting with one index sorts as expected") 181 | { 182 | ds.sort ({0}); 183 | CHECK_EQ (ds[0][2].getText (), "b"); 184 | CHECK_EQ (ds[1][2].getText (), "a"); 185 | CHECK_EQ (ds[2][2].getText (), "c"); 186 | } 187 | THEN ("sorting with 2 indexes sorts as expected") 188 | { 189 | ds.sort ({0, 1}); 190 | CHECK_EQ (ds[0][2].getText (), "a"); 191 | CHECK_EQ (ds[1][2].getText (), "b"); 192 | CHECK_EQ (ds[2][2].getText (), "c"); 193 | } 194 | } 195 | } 196 | } 197 | 198 | SCENARIO ("doing some things via dbvalues on rows of datasets") 199 | { 200 | using namespace sl3; 201 | GIVEN ("a database and a typed dataset with knowen content") 202 | { 203 | Database db{":memory:"}; 204 | Types types = {Type::Int, Type::Text, Type::Real}; 205 | Dataset ds = db.select ("SELECT 1,'hello', 1.1 as dbl;", types); 206 | WHEN ("having a dataset with different field count") 207 | { 208 | auto ds1 = db.select ("SELECT 1,'hello';", {Type::Int, Type::Text}); 209 | THEN ("swapping rows will work !") 210 | { // this might be wanted or not 211 | CHECK_NOTHROW (ds.at (0).swap (ds1.at (0))); 212 | CHECK_NOTHROW (std::swap (ds.at (0), ds1.at (0))); 213 | CHECK_NOTHROW (sl3::swap (ds.at (0), ds1.at (0))); 214 | } 215 | } 216 | 217 | WHEN ("having a dataset with different field count") 218 | { 219 | auto ds1 = db.select ("SELECT 1,'hello';", {Type::Int, Type::Text}); 220 | THEN ("assign a different rows will not work !") 221 | { 222 | CHECK_THROWS_AS (ds.at (0) = ds1.at (0), ErrTypeMisMatch); 223 | CHECK_THROWS_AS (ds.at (0) = std::move (ds1.at (0)), ErrTypeMisMatch); 224 | } 225 | } 226 | 227 | WHEN ("having a dataset with same field count but incampatible types") 228 | { 229 | auto ds1 = db.select ("SELECT 1,'hello', 'hello';", 230 | {Type::Int, Type::Text, Type::Text}); 231 | 232 | THEN ("assign a row will not work !") 233 | { 234 | CHECK_THROWS_AS (ds.at (0) = ds1.at (0), ErrTypeMisMatch); 235 | CHECK_THROWS_AS (ds.at (0) = std::move (ds1.at (0)), ErrTypeMisMatch); 236 | } 237 | } 238 | 239 | WHEN ("having an ohter , compatible dataset") 240 | { 241 | auto ds1 = db.select ("SELECT 2,'world', 3.3;", 242 | {Type::Int, Type::Text, Type::Real}); 243 | auto ds2 = db.select ("SELECT 3,'world', 3.3;", 244 | {Type::Int, Type::Text, Type::Real}); 245 | THEN ("assign a row will work !") 246 | { 247 | CHECK_NOTHROW (ds.at (0) = ds1.at (0)); 248 | CHECK (ds[0][0].getInt () == 2); 249 | CHECK_NOTHROW (ds.at (0) = std::move (ds2.at (0))); 250 | CHECK (ds[0][0].getInt () == 3); 251 | } 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /tests/commands/commandstest.cpp: -------------------------------------------------------------------------------- 1 | #include "../testing.hpp" 2 | #include 3 | 4 | #include 5 | 6 | SCENARIO ("using precompiled commands") 7 | { 8 | using namespace sl3; 9 | GIVEN ("cmake genertated config") 10 | { 11 | Database db{":memory:"}; 12 | db.execute ("CREATE TABLE tbl (fld1 , fld2 , fld3 );"); 13 | 14 | WHEN ("creating a simple compiled command") 15 | { 16 | auto cmd = db.prepare ("insert into tbl (fld1 , fld2 , fld3)" 17 | " VALUES (? , ? , ? ); "); 18 | 19 | THEN ("parameters are detected and set to Null") 20 | { 21 | CHECK (cmd.getParameters ().size () == 3); 22 | } 23 | 24 | THEN ("it can be executed") 25 | { 26 | CHECK (db.getTotalChanges () == 0); 27 | cmd.execute (parameters (1, 2, 3)); 28 | CHECK (db.getTotalChanges () == 1); 29 | cmd.execute (); // same are applied again 30 | auto val 31 | = db.selectValue ("SELECT COUNT(*) FROM tbl WHERE fld1 = 1 ;"); 32 | CHECK (val.getInt () == 2); 33 | } 34 | } 35 | 36 | WHEN ("creating a command via invalid sql") 37 | { 38 | THEN ("this throws en error") 39 | { 40 | REQUIRE_THROWS_AS (db.prepare ("SELECT * FROM FOOOOO;"), SQLite3Error); 41 | } 42 | } 43 | 44 | WHEN ("creating a command with parameters") 45 | { 46 | auto cmd = db.prepare ("insert into tbl (fld1 , fld2 , fld3)" 47 | " VALUES (? , ? , ? ); ", 48 | parameters (1, 2, 3)); 49 | THEN ("the parameters are set") 50 | { 51 | CHECK (cmd.getParameters ().size () == 3); 52 | cmd.execute (); 53 | CHECK (db.getTotalChanges () == 1); 54 | cmd.execute (); 55 | auto val 56 | = db.selectValue ("SELECT COUNT(*) FROM tbl WHERE fld1 = 1 ;"); 57 | CHECK (val.getInt () == 2); 58 | } 59 | 60 | THEN ("the parameters can be re-set") 61 | { 62 | cmd.resetParameters (parameters (6, 7, 8)); 63 | cmd.execute (); 64 | auto val 65 | = db.selectValue ("SELECT COUNT(*) FROM tbl WHERE fld1 = 6 ;"); 66 | CHECK (val.getInt () == 1); 67 | } 68 | } 69 | 70 | WHEN ("applying wrong numbers of parameters") 71 | { 72 | auto sql = "insert into tbl (fld1 , fld2 , fld3)" 73 | " VALUES (? , ? , ? ); "; 74 | auto param2 = parameters (1, 2); 75 | auto param4 = parameters (1, 2, 3, 4); 76 | 77 | THEN ("this will throw a type miss match") 78 | { 79 | CHECK_THROWS_AS ((void)db.prepare (sql, param2), ErrTypeMisMatch); 80 | CHECK_THROWS_AS ((void)db.prepare (sql, param4), ErrTypeMisMatch); 81 | auto cmd = db.prepare (sql); 82 | CHECK_THROWS_AS (cmd.resetParameters (parameters (1, 2)), 83 | ErrTypeMisMatch); 84 | CHECK_THROWS_AS (cmd.resetParameters (param4), ErrTypeMisMatch); 85 | 86 | CHECK_THROWS_AS (cmd.setParameters (parameters (1, 2)), 87 | ErrTypeMisMatch); 88 | CHECK_THROWS_AS (cmd.setParameters (param4), ErrTypeMisMatch); 89 | } 90 | } 91 | 92 | WHEN ("creating a command with names parameters") 93 | { 94 | auto sql1 = "insert into tbl (fld1 , fld2 , fld3)" 95 | " VALUES (:eins , @zwei , $drei); "; 96 | 97 | auto sql2 = "insert into tbl (fld1 , fld2 , fld3)" 98 | " VALUES (?1 , ?2 , ?1); "; 99 | 100 | THEN ("the parameter names can be queried") 101 | { 102 | auto cmd1 = db.prepare (sql1); 103 | auto names1 = cmd1.getParameterNames (); 104 | CHECK (names1.size () == 3); 105 | CHECK_EQ (names1.at (0), ":eins"); 106 | CHECK_EQ (names1.at (1), "@zwei"); 107 | CHECK_EQ (names1.at (2), "$drei"); 108 | REQUIRE_THROWS_AS (auto _ = names1.at (100), std::out_of_range); 109 | 110 | auto cmd2 = db.prepare (sql2); 111 | auto names2 = cmd2.getParameterNames (); 112 | CHECK (names2.size () == 2); 113 | CHECK_EQ (names2.at (0), "?1"); 114 | CHECK_EQ (names2.at (1), "?2"); 115 | } 116 | } 117 | 118 | WHEN ("creating a command typed parameters") 119 | { 120 | auto sql1 = "insert into tbl (fld1 , fld2 , fld3)" 121 | " VALUES (:eins , @zwei , $drei); "; 122 | 123 | THEN ("the command is movable") 124 | { 125 | auto cmd1 = db.prepare (sql1); 126 | cmd1.getParameter (0).set (100); 127 | cmd1.getParameter (1).set ("hello"); 128 | const Command cmd2{std::move (cmd1)}; 129 | auto names = cmd2.getParameterNames (); 130 | CHECK (names.size () == 3); 131 | CHECK_EQ (names.at (0), ":eins"); 132 | CHECK_EQ (names.at (1), "@zwei"); 133 | CHECK_EQ (names.at (2), "$drei"); 134 | CHECK_EQ (cmd2.getParameters ().size (), 3); 135 | CHECK_EQ (cmd2.getParameter (0).getInt (), 100); 136 | CHECK_EQ (cmd2.getParameter (1).getText (), "hello"); 137 | } 138 | } 139 | } 140 | } 141 | 142 | SCENARIO ("executing with callback and parameters") 143 | { 144 | using namespace sl3; 145 | GIVEN ("a database with some test data and a RowCallBack ") 146 | { 147 | Database db{":memory:"}; 148 | db.execute ("CREATE TABLE t (f);" 149 | "INSERT INTO t VALUES (1);" 150 | "INSERT INTO t VALUES (2);" 151 | "INSERT INTO t VALUES (3);"); 152 | 153 | struct : RowCallback 154 | { 155 | size_t counter{0}; 156 | bool 157 | onRow (sl3::Columns) 158 | { 159 | ++counter; 160 | return true; 161 | } 162 | } cb; 163 | WHEN ("executing a command with parameters that give me 1 record") 164 | { 165 | auto cmd = db.prepare ("SELECT * FROM t WHERE f = ?"); 166 | cmd.execute (cb, parameters (2)); 167 | THEN ("then I got the one expected record") { CHECK (cb.counter == 1); } 168 | } 169 | } 170 | } 171 | 172 | SCENARIO ("handling constraint violations") 173 | { 174 | using namespace sl3; 175 | GIVEN ("a database with a table that requires unique values") 176 | { 177 | Database db{":memory:"}; 178 | db.execute ("CREATE TABLE t (f UNIQUE);"); 179 | 180 | WHEN ("having a value") 181 | { 182 | auto cmd = db.prepare ("INSERT INTO t VALUES(?);"); 183 | cmd.execute (parameters (1)); 184 | THEN ("then inserting a value again is a exception") 185 | { 186 | CHECK_THROWS_AS (cmd.execute (), SQLite3Error); 187 | } 188 | } 189 | } 190 | } 191 | 192 | SCENARIO ("binding values of all types and using select to check the result") 193 | { 194 | using namespace sl3; 195 | GIVEN ("a database with a table that can take all types and") 196 | { 197 | Database db{":memory:"}; 198 | db.execute ("CREATE TABLE t (fi, fr, fs, fb, fn);"); 199 | auto sqin = "INSERT INTO t VALUES (?,?,?,?,?);"; 200 | 201 | auto b = [] (int v) -> std::byte { return std::byte (v); }; 202 | 203 | DbValues params = {DbValue{1}, 204 | DbValue{2.2}, 205 | DbValue{"drei"}, 206 | DbValue{Blob{b (1), b (2), b (3), b (4)}}, 207 | DbValue{Type::Variant}}; 208 | 209 | auto cmd = db.prepare (sqin); 210 | 211 | WHEN ("inserting values of all types") 212 | { 213 | CHECK_NOTHROW (cmd.execute (params)); 214 | THEN ("selecting all data returns the inserted data") 215 | { 216 | auto ds = db.select ("SELECT * FROM t;"); 217 | REQUIRE_EQ (ds.size (), 1); 218 | REQUIRE_EQ (ds[0].size (), params.size ()); 219 | REQUIRE_EQ (ds[0][0].getInt (), params[0].getInt ()); 220 | REQUIRE_EQ (ds[0][1].getReal (), params[1].getReal ()); 221 | REQUIRE_EQ (ds[0][2].getText (), params[2].getText ()); 222 | REQUIRE_EQ (ds[0][3].getBlob (), params[3].getBlob ()); 223 | REQUIRE (ds[0][4].isNull ()); 224 | } 225 | THEN ("selecting all data with concrete types will retun typed info") 226 | { 227 | auto types = Types{ 228 | Type::Int, Type::Real, Type::Text, Type::Blob, Type::Variant}; 229 | auto ds = db.select ("SELECT * FROM t;", types); 230 | REQUIRE_EQ (ds.size (), 1); 231 | REQUIRE_EQ (ds[0].size (), types.size ()); 232 | for (size_t i = 0; i < ds[0].size (); ++i) 233 | REQUIRE_EQ (ds[0][i].dbtype (), types[i]); 234 | } 235 | THEN ("selecting all data with wrong types will throw") 236 | { 237 | auto types 238 | = Types{Type::Int, Type::Int, Type::Int, Type::Int, Type::Int}; 239 | REQUIRE_THROWS_AS ((void)db.select ("SELECT * FROM t;", types), 240 | ErrTypeMisMatch); 241 | } 242 | THEN ("selecting all data with wrong numver of types will throw") 243 | { 244 | auto types = Types{Type::Int, Type::Real, Type::Text, Type::Blob}; 245 | REQUIRE_THROWS_AS ((void)db.select ("SELECT * FROM t;", types), 246 | ErrTypeMisMatch); 247 | } 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/sl3/command.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include "../sl3/connection.hpp" 16 | #include 17 | #include 18 | #include 19 | 20 | #include "utils.hpp" 21 | 22 | namespace sl3 23 | { 24 | namespace 25 | { 26 | sqlite3_stmt* 27 | createStmt (sqlite3* db, const std::string& sql) 28 | { 29 | if (db == nullptr) 30 | throw ErrNoConnection{}; 31 | 32 | sqlite3_stmt* stmt = nullptr; 33 | const char* unussedSQL = nullptr; 34 | 35 | int rc = sqlite3_prepare_v2 (db, sql.c_str (), -1, &stmt, &unussedSQL); 36 | 37 | if (rc != SQLITE_OK) 38 | { 39 | SQLite3Error sl3error (rc, sqlite3_errmsg (db)); 40 | throw sl3error; 41 | } 42 | 43 | return stmt; 44 | } 45 | 46 | DbValues 47 | createParameters (sqlite3_stmt* stmt) 48 | { 49 | const size_t paracount = as_size_t (sqlite3_bind_parameter_count (stmt)); 50 | 51 | using container = DbValues::container_type; 52 | 53 | return paracount > 0 ? DbValues (container (paracount, Type::Variant)) 54 | : DbValues (); 55 | } 56 | 57 | void 58 | bind (sqlite3_stmt* stmt, DbValues& parameters) 59 | { 60 | int curParaNr = 0; 61 | for (auto& val : parameters) 62 | { 63 | curParaNr += 1; // sqlite starts at 1 64 | 65 | int rc; 66 | 67 | switch (val.type ()) 68 | { 69 | case Type::Int: 70 | rc = sqlite3_bind_int64 (stmt, curParaNr, val.getInt ()); 71 | break; 72 | 73 | case Type::Real: 74 | rc = sqlite3_bind_double (stmt, curParaNr, val.getReal ()); 75 | break; 76 | 77 | case Type::Text: 78 | // note, i do not want \0 in the db so take size 79 | // SQLITE_TRANSIENT would copy the string , is unwanted here 80 | rc = sqlite3_bind_text ( 81 | stmt, 82 | curParaNr, 83 | val.getText ().c_str (), 84 | static_cast (val.getText ().size ()), 85 | SQLITE_STATIC); 86 | 87 | break; 88 | 89 | case Type::Blob: 90 | rc = sqlite3_bind_blob ( 91 | stmt, 92 | curParaNr, 93 | &(val.getBlob ()[0]), 94 | static_cast (val.getBlob ().size ()), 95 | SQLITE_STATIC); 96 | 97 | break; 98 | 99 | case Type::Null: 100 | rc = sqlite3_bind_null (stmt, curParaNr); 101 | break; 102 | 103 | default: 104 | throw ErrUnexpected (); // LCOV_EXCL_LINE 105 | } 106 | 107 | if (rc != SQLITE_OK) 108 | throw sl3::SQLite3Error (rc, ""); // LCOV_EXCL_LINE TODO ho to test 109 | } 110 | } 111 | 112 | } // ns 113 | 114 | Command::Command (Connection connection, const std::string& sql) 115 | : _connection (std::move (connection)) 116 | , _stmt (createStmt (_connection->db (), sql)) 117 | , _parameters (createParameters (_stmt)) 118 | { 119 | } 120 | 121 | Command::Command (Connection connection, 122 | const std::string& sql, 123 | DbValues parameters) 124 | : _connection (std::move (connection)) 125 | , _stmt (createStmt (_connection->db (), sql)) 126 | , _parameters (std::move (parameters)) 127 | { 128 | const size_t paracount = as_size_t (sqlite3_bind_parameter_count (_stmt)); 129 | 130 | if (paracount != _parameters.size ()) 131 | { 132 | throw ErrTypeMisMatch ("Incorrect parameter count"); 133 | } 134 | } 135 | 136 | Command::Command (Command&& other) 137 | : _connection (std::move (other._connection)) 138 | , _stmt (other._stmt) 139 | , _parameters (std::move (other._parameters)) 140 | { // clear stm so that d'tor ot other does no action 141 | other._stmt = nullptr; 142 | } 143 | 144 | Command::~Command () 145 | { 146 | if (_stmt) // its not a moved from zombi 147 | { 148 | if (_connection->isValid ()) // otherwise database will have done this 149 | { 150 | sqlite3_finalize (_stmt); 151 | } 152 | } 153 | } 154 | 155 | Dataset 156 | Command::select () 157 | { 158 | return select (DbValues (), Types ()); 159 | } 160 | 161 | Dataset 162 | Command::select (const Types& types, const DbValues& parameters) 163 | { 164 | return select (parameters, types); 165 | } 166 | 167 | Dataset 168 | Command::select (const DbValues& parameters, const Types& types) 169 | { 170 | Dataset ds{types}; 171 | Callback fillds = [&ds] (Columns columns) -> bool { 172 | if (ds._names.size () == 0) 173 | { 174 | const int typeCount = static_cast (ds._fieldtypes.size ()); 175 | 176 | if (typeCount == 0) 177 | { 178 | using container_type = Types::container_type; 179 | container_type c (as_size_t (columns.count ()), Type::Variant); 180 | Types fieldtypes{c}; 181 | ds._fieldtypes.swap (fieldtypes); 182 | } 183 | else if (typeCount != columns.count ()) 184 | { 185 | throw ErrTypeMisMatch ( 186 | "DbValuesTypeList.size != queryrow.getColumnCount()"); 187 | } 188 | ds._names = columns.getNames (); 189 | } 190 | 191 | // this will throw if a type does not match. 192 | ds._cont.emplace_back (columns.getRow (ds._fieldtypes)); 193 | 194 | return true; 195 | }; 196 | 197 | execute (fillds, parameters); 198 | return ds; 199 | } 200 | 201 | void 202 | Command::execute () 203 | { 204 | execute (DbValues ()); 205 | } 206 | 207 | void 208 | Command::execute (const DbValues& parameters) 209 | { 210 | execute ([] (Columns) -> bool { return true; }, parameters); 211 | } 212 | 213 | void 214 | Command::execute (RowCallback& cb, const DbValues& parameters) 215 | { 216 | cb.onStart (); 217 | 218 | auto cbf = [&cb] (Columns cols) -> bool { return cb.onRow (cols); }; 219 | 220 | execute (cbf, parameters); 221 | 222 | cb.onEnd (); 223 | } 224 | 225 | void 226 | Command::execute (Callback callback, const DbValues& parameters) 227 | { 228 | _connection->ensureValid (); 229 | 230 | if (parameters.size () > 0) 231 | setParameters (parameters); 232 | 233 | bind (_stmt, _parameters); 234 | 235 | // use this to ensure a reset of _stmt 236 | using ResetGuard 237 | = std::unique_ptr; 238 | ResetGuard resetGuard (_stmt, &sqlite3_reset); 239 | 240 | bool loop = true; 241 | while (loop) 242 | { 243 | int rc = sqlite3_step (_stmt); 244 | 245 | switch (rc) 246 | { 247 | case SQLITE_OK: 248 | case SQLITE_DONE: 249 | { 250 | loop = false; 251 | break; 252 | } 253 | case SQLITE_ROW: 254 | { 255 | loop = callback (Columns{_stmt}); 256 | break; 257 | } 258 | 259 | default: 260 | { 261 | auto db = sqlite3_db_handle (_stmt); 262 | SQLite3Error sl3error (rc, sqlite3_errmsg (db)); 263 | throw sl3error; 264 | } 265 | } 266 | } 267 | } 268 | 269 | DbValues& 270 | Command::getParameters () 271 | { 272 | return _parameters; 273 | } 274 | 275 | const DbValues& 276 | Command::getParameters () const 277 | { 278 | return _parameters; 279 | } 280 | 281 | DbValue& 282 | Command::getParameter (int idx) 283 | { 284 | return _parameters.at (as_size_t (idx)); 285 | } 286 | 287 | const DbValue& 288 | Command::getParameter (int idx) const 289 | { 290 | return _parameters.at (as_size_t (idx)); 291 | } 292 | 293 | void 294 | Command::setParameters (const DbValues& values) 295 | { 296 | if (values.size () != _parameters.size ()) 297 | { 298 | throw ErrTypeMisMatch ("parameter size incorrect"); 299 | } 300 | 301 | for (size_t i = 0; i < values.size (); ++i) 302 | { 303 | _parameters[i] = values[i]; 304 | } 305 | } 306 | 307 | void 308 | Command::resetParameters (DbValues values) 309 | { 310 | ASSERT_EXCEPT (values.size () == _parameters.size (), ErrTypeMisMatch); 311 | // auto tmp = values; 312 | _parameters.swap (values); 313 | } 314 | 315 | std::vector 316 | Command::getParameterNames () const 317 | { 318 | std::vector names; 319 | names.resize (_parameters.size ()); 320 | 321 | for (unsigned int i = 0; i < _parameters.size (); ++i) 322 | { 323 | const char* chrname 324 | = sqlite3_bind_parameter_name (_stmt, as_int (i + 1)); 325 | names[i] = chrname ? chrname : ""; 326 | } 327 | return names; 328 | } 329 | 330 | } // ns 331 | -------------------------------------------------------------------------------- /include/sl3/value.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3_VALUE_HPP_ 10 | #define SL3_VALUE_HPP_ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace sl3 19 | { 20 | 21 | /** 22 | * \brief 23 | * 24 | * This class models the duck typing sqlite uses. 25 | * It supports int, real, text, blob and null values. 26 | * 27 | * The class has the current type info availalbe. 28 | * 29 | * 30 | */ 31 | class LIBSL3_API Value 32 | { 33 | public: 34 | /** 35 | * \brief Constructor 36 | * 37 | * Creates a Null Value 38 | */ 39 | Value () noexcept; 40 | 41 | /** \brief Constructor 42 | * 43 | * This constructor wiht an initialization value 44 | * \param val initial value 45 | */ 46 | explicit Value (int val) noexcept; 47 | 48 | /** 49 | * \copydoc Value(int val) 50 | */ 51 | explicit Value (int64_t val) noexcept; 52 | 53 | /** 54 | * \copydoc Value(int val) 55 | */ 56 | explicit Value (std::string val) noexcept; 57 | 58 | /** 59 | * \copydoc Value(int val) 60 | */ 61 | explicit Value (const char* val); 62 | 63 | /** 64 | * \copydoc Value(int val) 65 | */ 66 | explicit Value (double val) noexcept; 67 | 68 | /** 69 | * \copydoc Value(int val) 70 | */ 71 | explicit Value (Blob val) noexcept; 72 | 73 | /** 74 | * \brief Destructor 75 | */ 76 | ~Value () noexcept; 77 | 78 | /** 79 | * \brief Copy constructor 80 | */ 81 | Value (const Value&) noexcept; 82 | 83 | /** 84 | * \brief Move constructor 85 | */ 86 | Value (Value&&) noexcept; 87 | 88 | /** \brief Assignment 89 | * \throw sl3::ErrTypeMisMatch if getType is incompatible 90 | * \note , only value assignment happens here, 91 | * the type does not change, the storage type might change in case of type 92 | * is a variant. 93 | * 94 | * \param val new value 95 | * \return reference to this 96 | */ 97 | Value& operator= (const Value& val); 98 | 99 | /** 100 | * \copydoc operator=(const Value& val) 101 | */ 102 | Value& operator= (Value&& val); 103 | 104 | /** 105 | * \copydoc operator=(const Value& val) 106 | */ 107 | Value& operator= (int val); 108 | 109 | /** 110 | * \copydoc operator=(const Value& val) 111 | */ 112 | Value& operator= (const int64_t& val); 113 | 114 | /** 115 | * \copydoc operator=(const Value& val) 116 | */ 117 | Value& operator= (const double& val); 118 | 119 | /** 120 | * \copydoc operator=(const Value& val) 121 | */ 122 | Value& operator= (const std::string& val); 123 | 124 | /** 125 | * \copydoc operator=(const Value& val) 126 | */ 127 | Value& operator= (const Blob& val); 128 | 129 | /** \brief Implicit conversion operator 130 | * \throw sl3::ErrNullValueAccess if value is null. 131 | * \throw sl3::ErrTypeMisMatch if getType is incompatible 132 | * \throw sl3::ErrOutOfRange is the stored value is an int64_t 133 | * \return the value 134 | */ 135 | explicit operator int () const; 136 | 137 | /** \brief Implicit conversion operator 138 | * \throw sl3::ErrNullValueAccess if value is null. 139 | * \throw sl3::ErrTypeMisMatch if getType is incompatible 140 | * \return the value 141 | */ 142 | explicit operator int64_t () const; 143 | 144 | /** \brief Implicit conversion operator 145 | * \throw sl3::ErrNullValueAccess if value is null. 146 | * \throw sl3::ErrTypeMisMatch if getType is incompatible 147 | * \throw sl3::ErrOutOfRange is the stored value is an int64_t and out of 148 | * the min or max double range. 149 | * \return the value 150 | */ 151 | explicit operator double () const; 152 | 153 | /** \brief Implicit conversion operator 154 | * \throw sl3::ErrNullValueAccess if value is null. 155 | * \throw sl3::ErrTypeMisMatch if getType is incompatible 156 | * \return the value 157 | */ 158 | explicit operator const std::string& () const; // TODO ref or val 159 | 160 | /** \brief Implicit conversion operator 161 | * \throw sl3::ErrNullValueAccess if value is null. 162 | * \throw sl3::ErrTypeMisMatch if getType is incompatible 163 | * \return the value 164 | */ 165 | explicit operator const Blob& () const; // TODO ref or val 166 | 167 | /** \brief Access the value 168 | * \throw sl3::ErrNullValueAccess if value is null. 169 | * \throw sl3::ErrTypeMisMatch if the current value has a different type. 170 | * \return reference to the value 171 | */ 172 | const int64_t& int64 () const; 173 | 174 | /** \brief Access the value 175 | * \throw sl3::ErrNullValueAccess if value is null. 176 | * \throw sl3::ErrTypeMisMatch if the current value has a different type. 177 | * \return reference to the value 178 | */ 179 | const double& real () const; 180 | 181 | /** \brief Access the value 182 | * \throw sl3::ErrNullValueAccess if value is null. 183 | * \throw sl3::ErrTypeMisMatch if the current value has a different type. 184 | * \return reference to the value 185 | */ 186 | const std::string& text () const; 187 | 188 | /** \brief Access the value 189 | * \throw sl3::ErrNullValueAccess if value is null. 190 | * \throw sl3::ErrTypeMisMatch if the current value has a different type. 191 | * \return reference to the value 192 | */ 193 | const Blob& blob () const; 194 | 195 | /** \brief Moves the current value into the return value 196 | * 197 | * After calling this function the value will be Null. 198 | * 199 | * \throw sl3::ErrTypeMisMatch in case of wrong type. 200 | * \return The value 201 | */ 202 | std::string ejectText (); 203 | 204 | /** 205 | * \copydoc ejectText() 206 | */ 207 | Blob ejectBlob (); 208 | 209 | /** 210 | * \brief Set to NULL 211 | */ 212 | void setNull () noexcept; 213 | 214 | /** 215 | * \brief Check Null 216 | * \return if the value is null 217 | */ 218 | bool isNull () const noexcept; 219 | 220 | /** 221 | * \brief The Type of the value. 222 | * 223 | * \return the type 224 | */ 225 | Type getType () const noexcept; 226 | 227 | // friend bool operator== (const Value& a, const Value& b) noexcept; 228 | // friend bool operator< (const Value& a, const Value& b) noexcept; 229 | friend std::ostream& operator<< (std::ostream& stm, const sl3::Value& v); 230 | 231 | friend bool value_type_eq (const Value& a, const Value& b) noexcept; 232 | friend bool value_type_lt (const Value& a, const Value& b) noexcept; 233 | 234 | friend bool value_eq (const Value& a, const Value& b) noexcept; 235 | friend bool value_lt (const Value& a, const Value& b) noexcept; 236 | 237 | /** 238 | * \brief swap function 239 | * 240 | * Independent of the type, a Value is always swapable. 241 | * 242 | * \param other value to swap with 243 | */ 244 | void swap (Value& other) noexcept; 245 | 246 | private: 247 | Type _type{Type::Null}; 248 | 249 | // that's the type for the union and what is applied in the db 250 | union Store 251 | { 252 | Store () {} 253 | ~Store () {} 254 | int64_t intval; 255 | double realval; 256 | std::string textval; 257 | Blob blobval; 258 | }; 259 | 260 | Store _store; 261 | }; 262 | 263 | /** 264 | * \brief Stream op for a Value 265 | * 266 | * \param stm an outstream 267 | * \param v the value to stream 268 | * \return ostream 269 | */ 270 | std::ostream& operator<< (std::ostream& stm, const sl3::Value& v); 271 | 272 | /** 273 | * \brief equality, including type info 274 | * 275 | * Check if 2 Value instances are of the same type and of the same value. 276 | * 277 | * \param a first element to compare 278 | * \param b second element to compare 279 | * 280 | * \return true if the type and the current value are equal, false otherwise 281 | */ 282 | bool value_type_eq (const Value& a, const Value& b) noexcept; 283 | 284 | /** 285 | * \brief less than, including type info 286 | * 287 | * Applies following rules which are equal to the sorting rules of sqlite. 288 | * 289 | * - Type::Null is alwasy less than any other storage type. 290 | * - Type::Interger or Type::Real 291 | * -- if the type is different, but the value equal, 292 | * than Type::Int < Type::Real . 293 | * if this is unwanted, use the weak_lt function. 294 | * -- always less than Type::Text or Type::Blob 295 | * - Type::Text is less than Type::Blob 296 | * 297 | * The comparison of the value itself is implemented via std::less. 298 | * 299 | * \param a first element to compare 300 | * \param b second element to compare 301 | * 302 | * \returns true if given Value a is less than given Value b 303 | */ 304 | bool value_type_lt (const Value& a, const Value& b) noexcept; 305 | 306 | /** 307 | * \brief equality, ignoring type info 308 | * 309 | * Compares only the stored value and ignores type information. 310 | * 311 | * \param a first value to compare 312 | * \param b second value to compare 313 | * \return the comparison result 314 | */ 315 | bool value_eq (const Value& a, const Value& b) noexcept; 316 | 317 | /** 318 | * \brief less than, ignoring type info 319 | * 320 | * Compares only the stored value and ignores type information. 321 | * 322 | * \param a first value to compare 323 | * \param b second value to compare 324 | * \return the comparison result 325 | */ 326 | bool value_lt (const Value& a, const Value& b) noexcept; 327 | 328 | /** 329 | * \brief Value specialized swap function 330 | * 331 | * Independent of the type, a Value is always swapable. 332 | * 333 | * \param a first value to swap with second value 334 | * \param b second value to swap with first value 335 | */ 336 | void swap (Value& a, Value& b) noexcept; 337 | 338 | /// Define a constant for a Value that is null 339 | // static const Value NullValue{}; 340 | } 341 | 342 | namespace std 343 | { // only allowed to extend namespace std with specializations 344 | 345 | template <> // specialization 346 | inline void 347 | swap (sl3::Value& lhs, sl3::Value& rhs) 348 | { 349 | sl3::swap (lhs, rhs); 350 | } 351 | } 352 | 353 | #endif 354 | -------------------------------------------------------------------------------- /src/sl3/dbvalue.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | namespace sl3 22 | { 23 | 24 | namespace 25 | { 26 | template 27 | constexpr bool 28 | oneof (const T&) 29 | { 30 | return false; 31 | } 32 | 33 | template 34 | constexpr bool 35 | oneof (const T& a, const T1& b, REST... rest) 36 | { 37 | return (a == b ? true : oneof (a, rest...)); 38 | } 39 | 40 | struct check 41 | { 42 | check (Type t) 43 | : type (t) 44 | { 45 | } //---------------------------------------------------------------------- 46 | 47 | bool 48 | notNull () 49 | { 50 | return type != Type::Null; 51 | } //---------------------------------------------------------------------- 52 | 53 | template 54 | bool 55 | oneOf (ARGS... args) 56 | { 57 | return oneof (type, args...); 58 | 59 | } //-------------------------------------------------------------------- 60 | 61 | bool 62 | sameAs (Type t) 63 | { 64 | return type == t; 65 | } //---------------------------------------------------------------------- 66 | 67 | private: 68 | Type type; 69 | }; 70 | 71 | struct ensure 72 | { 73 | ensure (Type t) 74 | : type (t) 75 | { 76 | } //---------------------------------------------------------------------- 77 | 78 | ensure& 79 | notNull () 80 | { 81 | if (type == Type::Null) 82 | throw ErrNullValueAccess (); 83 | 84 | return *this; 85 | } //---------------------------------------------------------------------- 86 | 87 | template 88 | ensure& 89 | oneOf (ARGS... args) 90 | { 91 | if (!oneof (type, args...)) 92 | throw ErrTypeMisMatch (typeName (type) 93 | + " not one of required types"); 94 | 95 | return *this; 96 | } //-------------------------------------------------------------------- 97 | 98 | ensure& 99 | sameAs (Type t) 100 | { 101 | if (type != t) 102 | throw ErrTypeMisMatch (typeName (type) + "!=" + typeName (t)); 103 | 104 | return *this; 105 | } //---------------------------------------------------------------------- 106 | 107 | private: 108 | Type type; 109 | }; 110 | 111 | } //-------------------------------------------------------------------------- 112 | 113 | bool 114 | dbval_type_eq (const DbValue& a, const DbValue& b) noexcept 115 | { 116 | if (a.dbtype () == b.dbtype ()) 117 | { 118 | return value_type_eq (a.getValue (), b.getValue ()); 119 | } 120 | 121 | return false; 122 | } 123 | 124 | bool 125 | dbval_type_lt (const DbValue& a, const DbValue& b) noexcept 126 | { 127 | if (value_type_lt (a.getValue (), b.getValue ())) 128 | { 129 | return true; 130 | } 131 | 132 | if (value_type_eq (a.getValue (), b.getValue ())) 133 | { // a variant is bigger 134 | return a.dbtype () < b.dbtype (); 135 | } 136 | 137 | return false; 138 | } 139 | 140 | // how, and does this makesense ? 141 | 142 | bool 143 | dbval_eq (const DbValue& a, const DbValue& b) noexcept 144 | { 145 | return value_eq (a.getValue (), b.getValue ()); 146 | } 147 | 148 | bool 149 | dbval_lt (const DbValue& a, const DbValue& b) noexcept 150 | { 151 | return value_lt (a.getValue (), b.getValue ()); 152 | } 153 | 154 | DbValue::DbValue (Type type) noexcept 155 | : _type (type == Type::Null ? Type::Variant : type) 156 | { 157 | } 158 | 159 | DbValue::DbValue (int val, Type type) 160 | : DbValue (type) 161 | { 162 | ensure (type).oneOf (Type::Int, Type::Variant); 163 | _value = val; 164 | } 165 | 166 | DbValue::DbValue (int64_t val, Type type) 167 | : DbValue (type) 168 | { 169 | ensure (type).oneOf (Type::Int, Type::Variant); 170 | _value = val; 171 | } 172 | 173 | DbValue::DbValue (std::string val, Type type) 174 | : DbValue (type) 175 | { 176 | ensure (type).oneOf (Type::Text, Type::Variant); 177 | _value = val; 178 | } 179 | 180 | DbValue::DbValue (double val, Type type) 181 | : DbValue (type) 182 | { 183 | ensure (type).oneOf (Type::Real, Type::Variant); 184 | _value = val; 185 | } 186 | 187 | DbValue::DbValue (Blob val, Type type) 188 | : DbValue (type) 189 | { 190 | ensure (type).oneOf (Type::Blob, Type::Variant); 191 | _value = val; 192 | } 193 | 194 | DbValue& 195 | DbValue::operator= (const DbValue& other) 196 | { 197 | if (!canAssign (other)) 198 | { 199 | throw ErrTypeMisMatch (typeName (_type) + "=" 200 | + (other._type == Type::Variant 201 | ? typeName (other._type) 202 | + " with storage type" 203 | + typeName (other.type ()) 204 | : typeName (other._type))); 205 | } 206 | 207 | assign (other); 208 | return *this; 209 | } 210 | 211 | DbValue& 212 | DbValue::operator= (DbValue&& other) 213 | { 214 | if (!canAssign (other)) 215 | { 216 | throw ErrTypeMisMatch (typeName (_type) + "=" 217 | + (other._type == Type::Variant 218 | ? typeName (other._type) 219 | + " with storage type" 220 | + typeName (other.type ()) 221 | : typeName (other._type))); 222 | } 223 | _value = std::move (other._value); 224 | 225 | return *this; 226 | } 227 | 228 | DbValue& 229 | DbValue::operator= (int val) 230 | { 231 | set (val); 232 | return *this; 233 | } 234 | 235 | DbValue& 236 | DbValue::operator= (const int64_t& val) 237 | { 238 | set (val); 239 | return *this; 240 | } 241 | 242 | DbValue& 243 | DbValue::operator= (const double& val) 244 | { 245 | set (val); 246 | return *this; 247 | } 248 | 249 | DbValue& 250 | DbValue::operator= (const std::string& val) 251 | { 252 | set (val); 253 | return *this; 254 | } 255 | 256 | DbValue& 257 | DbValue::operator= (const Blob& val) 258 | { 259 | set (val); 260 | return *this; 261 | } 262 | 263 | DbValue& 264 | DbValue::operator= (const Value& val) 265 | { 266 | ensure (_type).oneOf (val.getType (), Type::Variant); 267 | _value = val; 268 | return *this; 269 | } 270 | 271 | void 272 | DbValue::set (int val) 273 | { 274 | // not sure if I leaf this conversion, 275 | // but its better in text dbval.set(12); it type is real 276 | if (_type == Type::Real) 277 | set (static_cast (val)); 278 | else 279 | set (static_cast (val)); 280 | } 281 | 282 | void 283 | DbValue::set (int64_t val) 284 | { 285 | ensure (_type).oneOf (Type::Int, Type::Variant); 286 | _value = val; 287 | } 288 | 289 | void 290 | DbValue::set (double val) 291 | { 292 | ensure (_type).oneOf (Type::Real, Type::Variant); 293 | _value = val; 294 | } 295 | 296 | void 297 | DbValue::set (const std::string& val) 298 | { 299 | ensure (_type).oneOf (Type::Text, Type::Variant); 300 | _value = val; 301 | } 302 | 303 | const Value& 304 | DbValue::getValue () const noexcept 305 | { 306 | return _value; 307 | } 308 | 309 | void 310 | DbValue::set (const Blob& val) 311 | { 312 | ensure (_type).oneOf (Type::Blob, Type::Variant); 313 | _value = val; 314 | } 315 | 316 | const int64_t& 317 | DbValue::getInt () const 318 | { 319 | return _value.int64 (); 320 | } 321 | 322 | int64_t 323 | DbValue::getInt (int64_t defval) const 324 | { 325 | if (isNull ()) 326 | return defval; 327 | 328 | return _value.int64 (); 329 | } 330 | 331 | const double& 332 | DbValue::getReal () const 333 | { 334 | return _value.real (); 335 | } 336 | 337 | double 338 | DbValue::getReal (double defval) const 339 | { 340 | if (isNull ()) 341 | return defval; 342 | 343 | return _value.real (); 344 | } 345 | 346 | const std::string& 347 | DbValue::getText () const 348 | { 349 | return _value.text (); 350 | } 351 | 352 | std::string 353 | DbValue::getText (const std::string& defval) const 354 | { 355 | if (isNull ()) 356 | return defval; 357 | 358 | return _value.text (); 359 | } 360 | 361 | const Blob& 362 | DbValue::getBlob () const 363 | { 364 | return _value.blob (); 365 | } 366 | 367 | Blob 368 | DbValue::getBlob (const Blob& defval) const 369 | { 370 | if (isNull ()) 371 | return defval; 372 | 373 | return _value.blob (); 374 | } 375 | 376 | int64_t 377 | DbValue::get (int64_t defval) const 378 | { 379 | if (_value.getType () != Type::Int) 380 | return defval; 381 | 382 | return _value.int64 (); 383 | } 384 | 385 | int64_t 386 | DbValue::get (int defval) const 387 | { 388 | if (_value.getType () != Type::Int) 389 | return defval; 390 | 391 | return _value.int64 (); 392 | } 393 | 394 | double 395 | DbValue::get (double defval) const 396 | { 397 | if (_value.getType () != Type::Real) 398 | return defval; 399 | 400 | return _value.real (); 401 | } 402 | 403 | std::string // TODO consider change to reference, with warning in the doc 404 | DbValue::get (const std::string& defval) const 405 | { 406 | if (_value.getType () != Type::Text) 407 | return defval; 408 | 409 | return _value.text (); 410 | } 411 | 412 | Blob // TODO consider change to reference, with warning in the doc 413 | DbValue::get (const Blob& defval) const 414 | { 415 | if (_value.getType () != Type::Blob) 416 | return defval; 417 | 418 | return _value.blob (); 419 | } 420 | 421 | std::string 422 | DbValue::ejectText () 423 | { 424 | return _value.ejectText (); 425 | } 426 | 427 | Blob 428 | DbValue::ejectBlob () 429 | { 430 | return _value.ejectBlob (); 431 | } 432 | 433 | std::ostream& 434 | operator<< (std::ostream& stm, const sl3::DbValue& v) 435 | { 436 | stm << v.getValue (); 437 | return stm; 438 | } 439 | 440 | void 441 | DbValue::setNull () 442 | { 443 | _value.setNull (); 444 | } 445 | 446 | bool 447 | DbValue::isNull () const 448 | { 449 | return _value.isNull (); 450 | } 451 | 452 | Type 453 | DbValue::dbtype () const 454 | { 455 | return _type; 456 | } 457 | 458 | Type 459 | DbValue::type () const 460 | { 461 | return _value.getType (); 462 | } 463 | 464 | bool 465 | DbValue::canAssign (const DbValue& other) const 466 | { 467 | if (this->dbtype () != Type::Variant) 468 | { 469 | if (other.dbtype () == Type::Variant) 470 | { 471 | return check (other.type ()).oneOf (_type, Type::Null); 472 | } 473 | else 474 | { 475 | return check (_type).sameAs (other.dbtype ()); 476 | } 477 | } 478 | 479 | return true; 480 | } 481 | 482 | void 483 | DbValue::assign (const DbValue& other) 484 | { 485 | _value = other._value; 486 | } 487 | 488 | } // ns 489 | -------------------------------------------------------------------------------- /include/sl3/database.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ------------- Copyright (c) 2009-2023 H a r a l d A c h i t z --------------- 3 | ---------- < h a r a l d dot a c h i t z at g m a i l dot c o m > ------------ 4 | ---- This Source Code Form is subject to the terms of the Mozilla Public ----- 5 | ---- License, v. 2.0. If a copy of the MPL was not distributed with this ----- 6 | ---------- file, You can obtain one at http://mozilla.org/MPL/2.0/. ---------- 7 | ******************************************************************************/ 8 | 9 | #ifndef SL3_DATABASE_HPP_ 10 | #define SL3_DATABASE_HPP_ 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | struct sqlite3; 21 | 22 | namespace sl3 23 | { 24 | namespace internal 25 | { 26 | class Connection; 27 | } 28 | 29 | /** 30 | * \brief represents a SQLite3 Database 31 | * 32 | * 33 | * Encapsulate some/the most - useful methods for a SQLite3 database object. 34 | * 35 | * A Database is opened when a instance is created and close when the 36 | * instance 37 | * goes out of scope. 38 | * 39 | * \note for valgrind: http://www.sqlite.org/cvstrac/tktview?tn=3428 \n 40 | * since I don't know where to put this note else I place it just here 41 | */ 42 | class LIBSL3_API Database 43 | { 44 | public: 45 | Database (const Database&) = delete; 46 | Database& operator= (const Database&) = delete; 47 | Database& operator= (Database&&) = delete; 48 | 49 | /** 50 | * \brief Constructor 51 | * 52 | * Construction of this object opens, or creates a sqlite3 database. 53 | * The exact behavior can be fine tuned using the openFlags parameter. 54 | * 55 | * \param name database name. 56 | * \n If name is ":memory:" than the database will be an in memory 57 | * database. 58 | * \n If name has a size of 0 than the database will be private on disk 59 | * database. 60 | * \n Otherwise name will be interpreted as a file. 61 | * 62 | * \param openFlags if no flags are given, the defaults are 63 | * SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE. 64 | * 65 | * \sa https://www.sqlite.org/c3ref/open.html 66 | * 67 | * \throw sl3::SQLite3Error if the database can not be opened 68 | */ 69 | explicit Database (const std::string& name, int openFlags = 0); 70 | 71 | /** 72 | * \brief Destructor. 73 | */ 74 | virtual ~Database () noexcept; 75 | 76 | /** 77 | * \brief Move constructor 78 | * 79 | * A Database is movable 80 | */ 81 | Database (Database&&) noexcept; 82 | 83 | /** 84 | * \brief create a SqlCommand instance. 85 | * 86 | * Parameters that the statement might contain will be automatically 87 | * deduced and created as DbVaraint values 88 | * 89 | * \param sql SQL statement 90 | * \return a Command instance 91 | */ 92 | Command prepare (const std::string& sql); 93 | 94 | /** 95 | * \brief create a Command. 96 | * 97 | * Given parameters will be used to set up the command parameters, 98 | * 99 | * \param sql SQL statement 100 | * \param params parameters 101 | * 102 | * \throw sl3::ErrTypeMisMatch if params count is wrong 103 | * \return a Command instance 104 | */ 105 | Command prepare (const std::string& sql, const DbValues& params); 106 | 107 | /** 108 | * \brief Execute one or more SQL statements. 109 | * 110 | * \throw sl3::SQLite3Error in case of a problem. 111 | * 112 | * \param sql SQL Statements 113 | */ 114 | void execute (const std::string& sql); 115 | 116 | /** 117 | * \brief Execute one or more SQL statements. 118 | * 119 | * Overload for const char to avoid a copy for larg command like 120 | * create database scripts which are often static char* 121 | * 122 | * \throw sl3::SQLite3Error in case of a problem. 123 | * 124 | * \param sql SQL Statements 125 | */ 126 | void execute (const char* sql); 127 | 128 | /** 129 | * \brief Execute one SQL statements and apply a SqlQueryHandler. 130 | * 131 | * \throw sl3::SQLite3Error in case of a problem. 132 | * 133 | * \param sql SQL Statements 134 | * \param cb RowCallback to handle query result 135 | */ 136 | void execute (const std::string& sql, RowCallback& cb); 137 | 138 | /// shortcut for Command::Callback 139 | using Callback = Command::Callback; 140 | 141 | /** 142 | * \brief Execute one SQL statements and apply a QueryCallback. 143 | * 144 | * \throw sl3::SQLite3Error in case of a problem. 145 | * 146 | * \param sql SQL Statements 147 | * \param cb Callback to handle query result 148 | */ 149 | void execute (const std::string& sql, Callback cb); 150 | 151 | /** 152 | * \brief Execute a SQL query and return the result. 153 | * 154 | * \throw sl3::SQLite3Error in case of a problem. 155 | * 156 | * \param sql SQL Statements 157 | * \return a Dataset with the result. 158 | */ 159 | Dataset select (const std::string& sql); 160 | 161 | /** 162 | * \brief Execute a SQL query and returns the result . 163 | * 164 | * If Types are 165 | * 166 | * \throw sl3::SQLite3Error in case of a problems. 167 | * \throw sl3::ErrTypeMisMatch in case of incorrect types. 168 | * 169 | * \param sql SQL Statements 170 | * \param types wanted types of the returned Dataset 171 | * \return a Dataset with the result. 172 | */ 173 | Dataset select (const std::string& sql, const Types& types); 174 | 175 | /** 176 | * \brief Select a single value form the database. 177 | * 178 | * This is a quick way to get single value results for queries like 179 | * "SELECT COUNT(*) FROM someTable;" \n 180 | * 181 | * This function returns the first column of the first result row of the 182 | * given query. 183 | * If query returns no rows, the return value will be null. 184 | * 185 | * \throw sl3::SQLite3Error in case of a problems. 186 | * 187 | * \param sql SQL statement 188 | * \return DbValue of the first available result. 189 | */ 190 | DbValue selectValue (const std::string& sql); 191 | 192 | /** 193 | * \brief select a single typed value form the database. 194 | * 195 | * This is a quick way to get single value results for queries like 196 | * "SELECT COUNT(*) FROM someTable;" \n 197 | * 198 | * This function returns the first column of the first result row of the 199 | * given query. 200 | * If query returns no rows, the return value will be null. 201 | * 202 | * \throw sl3::ErrTypeMisMatch if return type differs from requested type 203 | * \throw sl3::SQLite3Error in case of a problems. 204 | * \param sql SQL statement 205 | * \param type result has to be of that Type 206 | * 207 | * \return DbValue of the first available result. 208 | */ 209 | DbValue selectValue (const std::string& sql, Type type); 210 | 211 | /** 212 | * \brief Returns last SQLite3 extended result code 213 | * 214 | * \note SQLite3 result code in case of a failure is triggered within a 215 | * SQLite3Error. 216 | * 217 | * The return value is only valid if the last call failed. 218 | * 219 | * \note This function returns extended result codes 220 | * 221 | * \return SQLite3 extended error code 222 | */ 223 | int getMostRecentErrCode (); 224 | 225 | /** 226 | * \brief Returns most recent error message. 227 | * 228 | * 229 | * \return SQLite3 error message. 230 | */ 231 | std::string getMostRecentErrMsg (); 232 | 233 | /** 234 | * \brief Returns number of row that have changed since database opening. 235 | * 236 | * Returns the number of rows that have been changed 237 | * Through successful SQL INSERT/UPDATE or DELETE statements since database 238 | * was opened. 239 | * 240 | * \return Count of changed rows 241 | */ 242 | std::size_t getTotalChanges (); 243 | 244 | /** 245 | * \brief Rows that have been changed from the most recent SQL statement. 246 | * 247 | * Returns the number of rows that have been changed from the most recent 248 | * successful SQL INSERT/UPDATE or DELETE statement. 249 | * 250 | * \return Count of changed rows 251 | */ 252 | std::size_t getRecentlyChanged (); 253 | 254 | /** 255 | * \brief returns rowid of the most recent successful INSERT statement 256 | * 257 | * Returns the rowid (ROWID, OID, or _ROWID_ or the column of 258 | * type INTEGER PRIMARY KEY ) 259 | * of the most recent successful INSERT into the database. 260 | * \n If no successful INSERTs have ever occurred on that database, 261 | * zero is returned. 262 | * 263 | * \return rowid 264 | */ 265 | int64_t getLastInsertRowid (); 266 | 267 | /** 268 | * \brief Transaction Guard 269 | * 270 | * Scope guard for transaction. 271 | * If an instance of this class goes out of scope and commit has 272 | * not been called, it will call Rollback. 273 | */ 274 | class Transaction 275 | { 276 | sl3::Database* _db; 277 | 278 | explicit Transaction (Database&); 279 | friend class Database; 280 | 281 | public: 282 | Transaction (const Transaction&) = delete; 283 | Transaction& operator= (const Transaction&) = delete; 284 | Transaction& operator= (Transaction&&) = delete; 285 | 286 | /** \brief Move constructor 287 | * A Transaction is movalble. 288 | */ 289 | Transaction (Transaction&&) noexcept; 290 | 291 | /** \brief Destructor 292 | * 293 | * Calls ROLLBACK if the Transaction commit has not been called. 294 | */ 295 | ~Transaction (); 296 | 297 | /** \brief Commit the transaction 298 | * 299 | * Calls commit transaction. 300 | */ 301 | void commit (); 302 | }; 303 | 304 | /** 305 | * \brief Create a TransactionGuard 306 | * \return Transaction instance 307 | */ 308 | Transaction beginTransaction (); 309 | 310 | protected: 311 | /** 312 | * \brief Access the underlying sqlite3 database. 313 | * 314 | * Derived classes may, if needed, access the pointer to 315 | * the sqlite3 database. 316 | * 317 | * \note: Do not change the state of database (call close on it). 318 | * 319 | * \return a handle to the underlying sqlite3 database; 320 | */ 321 | sqlite3* db (); 322 | 323 | private: 324 | /** 325 | * \brief Define internal::Connection type. 326 | * 327 | * Only internal used 328 | * 329 | * This uses a shared_ptr. 330 | * libsl3 allows a sl3::Database do go out of scope, 331 | * but keep a sl3::Command instance. 332 | * The Command instance is still connected, and the underlying sqlite3 333 | * connection will be closed when the last Command instance is destroyed. 334 | * 335 | */ 336 | using ConnectionPtr = std::shared_ptr; 337 | 338 | /** 339 | * \brief Shared pointer for internal::Connection. 340 | * 341 | * Only internal used. A connection is shared with Command instances. 342 | * 343 | * If a database closes, the connection closes. 344 | * A Command knows about that and will throw an exception if used 345 | * after closing the database. 346 | */ 347 | ConnectionPtr _connection; 348 | }; 349 | 350 | /** 351 | * \brief Wraps sqlite3_errstr() function 352 | * 353 | * The sqlite3_errstr() interface returns the English-language text 354 | * that describes the result code, as UTF 355 | * \param errcode err number 356 | * \return Count of changed rows 357 | */ 358 | std::string getErrStr (int errcode); 359 | 360 | } 361 | 362 | #endif /* ...DATABASE_HPP_ */ 363 | --------------------------------------------------------------------------------