summaryrefslogtreecommitdiff
path: root/subprojects/boost-sqlite/test
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/boost-sqlite/test')
-rw-r--r--subprojects/boost-sqlite/test/CMakeLists.txt11
-rw-r--r--subprojects/boost-sqlite/test/Jamfile25
-rw-r--r--subprojects/boost-sqlite/test/allocator.cpp23
-rw-r--r--subprojects/boost-sqlite/test/backup.cpp46
-rw-r--r--subprojects/boost-sqlite/test/blob.cpp56
-rw-r--r--subprojects/boost-sqlite/test/catch.cpp18
-rw-r--r--subprojects/boost-sqlite/test/collation.cpp45
-rw-r--r--subprojects/boost-sqlite/test/connection.cpp32
-rw-r--r--subprojects/boost-sqlite/test/extension/CMakeLists.txt19
-rw-r--r--subprojects/boost-sqlite/test/extension/simple_scalar.cpp27
-rw-r--r--subprojects/boost-sqlite/test/extension/simple_scalar.sql4
-rw-r--r--subprojects/boost-sqlite/test/field.cpp46
-rw-r--r--subprojects/boost-sqlite/test/function.cpp306
-rw-r--r--subprojects/boost-sqlite/test/hooks.cpp52
-rw-r--r--subprojects/boost-sqlite/test/json.cpp104
-rw-r--r--subprojects/boost-sqlite/test/main_test.cpp9
-rw-r--r--subprojects/boost-sqlite/test/meta_data.cpp45
-rw-r--r--subprojects/boost-sqlite/test/mutex.cpp31
-rw-r--r--subprojects/boost-sqlite/test/statement.cpp74
-rw-r--r--subprojects/boost-sqlite/test/static_resultset.cpp95
-rw-r--r--subprojects/boost-sqlite/test/test-db.sql29
-rw-r--r--subprojects/boost-sqlite/test/test.hpp22
-rw-r--r--subprojects/boost-sqlite/test/transaction.cpp89
-rw-r--r--subprojects/boost-sqlite/test/vtable.cpp256
24 files changed, 1464 insertions, 0 deletions
diff --git a/subprojects/boost-sqlite/test/CMakeLists.txt b/subprojects/boost-sqlite/test/CMakeLists.txt
new file mode 100644
index 0000000..79750fb
--- /dev/null
+++ b/subprojects/boost-sqlite/test/CMakeLists.txt
@@ -0,0 +1,11 @@
+file(GLOB ALL_TEST_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
+
+add_executable(boost_sqlite_tests ${ALL_TEST_FILES})
+target_link_libraries(boost_sqlite_tests PUBLIC SQLite::SQLite3
+ Boost::json Boost::sqlite Boost::unit_test_framework)
+target_compile_definitions(boost_sqlite_tests PUBLIC BOOST_SQLITE_SEPARATE_COMPILATION=1)
+
+add_test(NAME boost_sqlite_tests COMMAND boost_sqlite_tests)
+
+add_subdirectory(extension)
+
diff --git a/subprojects/boost-sqlite/test/Jamfile b/subprojects/boost-sqlite/test/Jamfile
new file mode 100644
index 0000000..85d19ed
--- /dev/null
+++ b/subprojects/boost-sqlite/test/Jamfile
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+#
+# Distributed under the Boost Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#
+# Official repository: https://github.com/boostorg/json
+#
+
+project :
+requirements <include>../../..
+
+;
+
+import testing ;
+
+lib sqlite3 ;
+
+run [ glob *.cpp ] sqlite3 /boost//sqlite /boost//json /boost//unit_test_framework ;
+
+
+lib simple_scalar : extension/simple_scalar.cpp /boost/sqlite//extension /boost//json
+ : <link>shared ;
+
+# TODO run simple_scalar as sqlite3 :memory: ".read extension/simple_scalar.sql" \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/allocator.cpp b/subprojects/boost-sqlite/test/allocator.cpp
new file mode 100644
index 0000000..a9d9b7c
--- /dev/null
+++ b/subprojects/boost-sqlite/test/allocator.cpp
@@ -0,0 +1,23 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/allocator.hpp>
+#include <boost/test/unit_test.hpp>
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(allocator)
+{
+
+ sqlite::allocator<int> alloc;
+
+ auto p = alloc.allocate(32);
+ BOOST_CHECK(p != nullptr);
+ alloc.deallocate(p, 32);
+
+ BOOST_CHECK_THROW(boost::ignore_unused(alloc.allocate((std::numeric_limits<std::size_t>::max)())), std::bad_alloc);
+}
diff --git a/subprojects/boost-sqlite/test/backup.cpp b/subprojects/boost-sqlite/test/backup.cpp
new file mode 100644
index 0000000..5a42c50
--- /dev/null
+++ b/subprojects/boost-sqlite/test/backup.cpp
@@ -0,0 +1,46 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/backup.hpp>
+#include <boost/sqlite/connection.hpp>
+
+#include <string>
+#include <vector>
+
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(backup)
+{
+ sqlite::connection conn1{":memory:"};
+ conn1.execute(
+#include "test-db.sql"
+ );
+ // language=sqlite
+ conn1.query("select * from author;");
+
+
+ sqlite::connection conn2{":memory:"};
+ sqlite::backup(conn1, conn2);
+
+ std::vector<std::string> names1, names2;
+
+ // language=sqlite
+ for (auto r : conn1.query("select first_name from author;"))
+ names1.emplace_back(r.at(0u).get_text());
+
+ // language=sqlite
+ for (auto r : conn2.query("select first_name from author;"))
+ names2.emplace_back(r.at(0u).get_text());
+
+ BOOST_CHECK(!names1.empty());
+ BOOST_CHECK(!names2.front().empty());
+ BOOST_CHECK(names1 == names2);
+ BOOST_CHECK_THROW(sqlite::backup(conn1, conn2, "foo", "bar"), boost::system::system_error);
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/blob.cpp b/subprojects/boost-sqlite/test/blob.cpp
new file mode 100644
index 0000000..ca6ce83
--- /dev/null
+++ b/subprojects/boost-sqlite/test/blob.cpp
@@ -0,0 +1,56 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/blob.hpp>
+#include <boost/sqlite/connection.hpp>
+
+#include <random>
+
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(blob)
+{
+ sqlite::connection conn{":memory:"};
+ // language=sqlite
+ conn.execute("create table blobs(id integer primary key autoincrement, bb blob);");
+
+ std::vector<unsigned char> blobby;
+ blobby.resize(4096*4096);
+ std::random_device dev;
+ std::mt19937 rng(dev());
+ std::uniform_int_distribution<std::mt19937::result_type> dist(0,255); // distribution in range [1, 6]
+
+ std::generate(blobby.begin(), blobby.end(),
+ [&]{return static_cast<unsigned char>(dist(rng));});
+
+
+ conn.prepare("insert into blobs(bb) values ($1);").execute(std::make_tuple(sqlite::zero_blob(4096 * 4096 )));
+
+ auto bh = open_blob(conn, "main", "blobs", "bb", 1);
+
+ BOOST_CHECK(bh.size() == 4096 * 4096);
+
+
+ unsigned char buf[4096];
+ std::generate(std::begin(buf), std::end(buf), [&]{return static_cast<unsigned char>(dist(rng));});
+ bh.read_at(buf, 4096, 4096);
+ BOOST_CHECK(std::all_of(std::begin(buf), std::end(buf), [](unsigned char c) {return c == 0u;}));
+
+ bh.write_at(blobby.data(), blobby.size(), 0u);
+ bh.read_at(buf, 4096, 4096);
+ BOOST_CHECK(std::memcmp(buf, blobby.data() + 4096, 4096) == 0);
+
+ BOOST_CHECK_THROW(open_blob(conn, "main", "doesnt-exit", "blobber", 2), boost::system::system_error);
+
+ sqlite::blob_handle bb;
+ BOOST_CHECK_THROW(bb.read_at(blobby.data(), blobby.size(), 0), boost::system::system_error);
+ BOOST_CHECK_THROW(bb.write_at(blobby.data(), blobby.size(), 0), boost::system::system_error);
+
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/catch.cpp b/subprojects/boost-sqlite/test/catch.cpp
new file mode 100644
index 0000000..37b8dbb
--- /dev/null
+++ b/subprojects/boost-sqlite/test/catch.cpp
@@ -0,0 +1,18 @@
+// Copyright (c) 2023 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/detail/catch.hpp>
+#include <boost/test/unit_test.hpp>
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(prefix)
+{
+ system::system_error se(SQLITE_TOOBIG, sqlite::sqlite_category());
+ BOOST_CHECK(sqlite::detail::get_message(se).empty());
+
+ se = system::system_error(SQLITE_TOOBIG, sqlite::sqlite_category(), "foobar");
+
+ BOOST_CHECK(sqlite::detail::get_message(se) == "foobar");
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/collation.cpp b/subprojects/boost-sqlite/test/collation.cpp
new file mode 100644
index 0000000..8893e42
--- /dev/null
+++ b/subprojects/boost-sqlite/test/collation.cpp
@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/collation.hpp>
+#include <string>
+#include <vector>
+
+#include "test.hpp"
+
+using namespace boost;
+
+struct collate_length
+{
+ int operator()(core::string_view l, core::string_view r) noexcept
+ {
+ return std::stoull(r) - l.size();
+ }
+};
+
+BOOST_AUTO_TEST_CASE(collation)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ sqlite::create_collation(conn, "length", collate_length{});
+
+ std::vector<std::string> names;
+
+ // language=sqlite
+ for (auto r : conn.query("select first_name from author where first_name = 5 collate length order by last_name asc;"))
+ names.emplace_back(r.at(0).get_text());
+
+ std::vector<std::string> cmp = {"peter", "ruben"};
+ BOOST_CHECK(names == cmp);
+
+ sqlite::delete_collation(conn, "length");
+
+ BOOST_CHECK_THROW(conn.query("select first_name from author where first_name = 5 collate length order by last_name asc;"), system::system_error);
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/connection.cpp b/subprojects/boost-sqlite/test/connection.cpp
new file mode 100644
index 0000000..b3c63ca
--- /dev/null
+++ b/subprojects/boost-sqlite/test/connection.cpp
@@ -0,0 +1,32 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+
+#include <filesystem>
+
+using namespace boost;
+
+
+BOOST_AUTO_TEST_CASE(connection)
+{
+ sqlite::connection conn;
+ conn.connect(std::filesystem::path(":memory:"));
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ BOOST_CHECK_THROW(conn.execute("elect * from nothing;"), boost::system::system_error);
+ conn.close();
+}
+
+BOOST_AUTO_TEST_CASE(exc)
+{
+ sqlite::connection conn;
+ conn.connect(sqlite::in_memory);
+ BOOST_CHECK_THROW(conn.execute("select 932 fro 12;"), boost::system::system_error);
+ conn.close();
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/extension/CMakeLists.txt b/subprojects/boost-sqlite/test/extension/CMakeLists.txt
new file mode 100644
index 0000000..a52a92c
--- /dev/null
+++ b/subprojects/boost-sqlite/test/extension/CMakeLists.txt
@@ -0,0 +1,19 @@
+
+file(GLOB_RECURSE ALL_SQLITE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.sql)
+file(GLOB_RECURSE ALL_CPP_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
+
+foreach(module ${ALL_CPP_FILES})
+ get_filename_component(stem ${module} NAME_WE)
+ add_library(boost_sqlite_test_extension_${stem} SHARED ${module})
+ target_link_libraries(boost_sqlite_test_extension_${stem} PUBLIC Boost::sqlite_ext)
+ target_include_directories(boost_sqlite_test_extension_${stem} PUBLIC ../../include)
+ set_property(TARGET boost_sqlite_test_extension_${stem} PROPERTY PREFIX "")
+ set_target_properties(boost_sqlite_test_extension_${stem} PROPERTIES OUTPUT_NAME ${stem})
+endforeach()
+
+foreach(script ${ALL_SQLITE_FILES})
+ get_filename_component(stem ${script} NAME_WE)
+ add_test(NAME boost_sqlite_test_extension_${stem} COMMAND
+ sqlite3 :memory: ".read ${CMAKE_CURRENT_SOURCE_DIR}/${script}"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+endforeach()
diff --git a/subprojects/boost-sqlite/test/extension/simple_scalar.cpp b/subprojects/boost-sqlite/test/extension/simple_scalar.cpp
new file mode 100644
index 0000000..4a05afe
--- /dev/null
+++ b/subprojects/boost-sqlite/test/extension/simple_scalar.cpp
@@ -0,0 +1,27 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/extension.hpp>
+#include <boost/sqlite/function.hpp>
+
+BOOST_SQLITE_EXTENSION(simplescalar, conn)
+{
+ create_scalar_function(
+ conn, "assert",
+ [](boost::sqlite::context<>, boost::span<boost::sqlite::value, 1u> sp)
+ {
+ if (sp.front().get_int() == 0)
+ throw std::logic_error("test failed");
+ });
+
+ create_scalar_function(
+ conn, "my_add",
+ [](boost::sqlite::context<>, boost::span<boost::sqlite::value, 2u> sp)-> sqlite3_int64
+ {
+ return sp[0].get_int() + sp[1].get_int();
+ });
+}
diff --git a/subprojects/boost-sqlite/test/extension/simple_scalar.sql b/subprojects/boost-sqlite/test/extension/simple_scalar.sql
new file mode 100644
index 0000000..00064ef
--- /dev/null
+++ b/subprojects/boost-sqlite/test/extension/simple_scalar.sql
@@ -0,0 +1,4 @@
+SELECT load_extension('./simple_scalar');
+
+select assert(5 = (select my_add(2, 3)));
+select assert(7 = (select my_add(4, 3)));
diff --git a/subprojects/boost-sqlite/test/field.cpp b/subprojects/boost-sqlite/test/field.cpp
new file mode 100644
index 0000000..a3fae32
--- /dev/null
+++ b/subprojects/boost-sqlite/test/field.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/field.hpp>
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(field)
+{
+ sqlite::connection conn(":memory:");
+ // language=sqlite
+ conn.execute(R"(
+create table type_tester(
+ id integer primary key autoincrement,
+ num real,
+ nl null,
+ txt text,
+ blb blob);
+
+ insert into type_tester values(42, 1.2, null, 'text', x'04050607');
+)");
+
+ auto res = conn.query("select * from type_tester");
+ auto r = res.current();
+
+ BOOST_CHECK(r[0].type() == sqlite::value_type::integer);
+ BOOST_CHECK(r[0].get_int() == 42);
+
+ BOOST_CHECK(r[1].type() == sqlite::value_type::floating);
+ BOOST_CHECK(r[1].get_double() == 1.2);
+
+ BOOST_CHECK(r[2].type() == sqlite::value_type::null);
+ BOOST_CHECK(r[3].type() == sqlite::value_type::text);
+ BOOST_CHECK(r[3].get_text() == "text");
+
+ BOOST_CHECK(r[4].type() == sqlite::value_type::blob);
+
+ sqlite::blob bl{4u};
+ char raw_data[4] = {4,5,6,7};
+ std::memcpy(bl.data(), raw_data, 4);
+ BOOST_CHECK(std::memcmp(bl.data(), r[4].get_blob().data(), 4u) == 0u);
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/function.cpp b/subprojects/boost-sqlite/test/function.cpp
new file mode 100644
index 0000000..b9a4c57
--- /dev/null
+++ b/subprojects/boost-sqlite/test/function.cpp
@@ -0,0 +1,306 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/function.hpp>
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+
+#include <string>
+#include <vector>
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(scalar)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ sqlite::create_scalar_function(
+ conn,
+ "to_upper",
+ [](sqlite::context<>, boost::span<sqlite::value, 1u> val)
+ -> variant2::variant<variant2::monostate, sqlite::value, std::string>
+ {
+ if (val.empty())
+ return {};
+
+ if (val[0].type() != sqlite::value_type::text)
+ return val[0];
+
+ auto txt = val[0].get_text();
+ std::string res;
+ res.resize(txt.size());
+ std::transform(txt.begin(), txt.end(), res.begin(), [](char c){return std::toupper(c);});
+ return res;
+ });
+
+ std::vector<std::string> names;
+
+ // language=sqlite
+ for (auto r : conn.query("select to_upper(first_name) from author order by last_name asc;"))
+ names.emplace_back(r.at(0).get_text());
+
+
+ std::vector<std::string> nm = {"PETER", "VINNIE", "RICHARD", "RUBEN"};
+ BOOST_CHECK(nm == names);
+}
+
+
+BOOST_AUTO_TEST_CASE(scalar_pointer)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ sqlite::create_scalar_function(
+ conn,
+ "to_upper",
+ +[](sqlite::context<>, boost::span<sqlite::value, 1u> val)
+ -> variant2::variant<variant2::monostate, sqlite::value, std::string>
+ {
+ if (val.empty())
+ return {};
+
+ if (val[0].type() != sqlite::value_type::text)
+ return val[0];
+
+ auto txt = val[0].get_text();
+ std::string res;
+ res.resize(txt.size());
+ std::transform(txt.begin(), txt.end(), res.begin(), [](char c){return std::toupper(c);});
+ return res;
+ });
+
+ std::vector<std::string> names;
+
+ // language=sqlite
+ for (auto r : conn.query("select to_upper(first_name) from author order by last_name asc;"))
+ names.emplace_back(r.at(0).get_text());
+
+
+ std::vector<std::string> nm = {"PETER", "VINNIE", "RICHARD", "RUBEN"};
+ BOOST_CHECK(nm == names);
+}
+
+
+BOOST_AUTO_TEST_CASE(scalar_void)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ sqlite::create_scalar_function(
+ conn,
+ "to_upper",
+ [](sqlite::context<>, boost::span<sqlite::value, 1u> )
+ {
+ return ;
+ });
+
+
+ // language=sqlite
+ for (auto r : conn.query("select to_upper(first_name) from author order by last_name asc;"))
+ BOOST_CHECK(r[0].is_null());
+}
+
+
+BOOST_AUTO_TEST_CASE(scalar_void_pointer)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ sqlite::create_scalar_function(
+ conn,
+ "to_upper",
+ +[](sqlite::context<>, boost::span<sqlite::value, 1u> )
+ {
+ return ;
+ });
+
+ std::vector<std::string> names;
+
+ // language=sqlite
+ for (auto r : conn.query("select to_upper(first_name) from author order by last_name asc;"))
+ BOOST_CHECK(r[0].is_null());
+}
+
+
+
+BOOST_AUTO_TEST_CASE(aggregate)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ struct aggregate_func
+ {
+ aggregate_func(int value) : counter(value) {}
+ std::size_t counter;
+ void step(boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ }
+
+ std::int64_t final()
+ {
+ return counter;
+ }
+ };
+
+ sqlite::create_aggregate_function<aggregate_func>(
+ conn,
+ "char_counter", std::make_tuple(0));
+
+ std::vector<std::size_t> lens;
+
+ // language=sqlite
+ for (auto r : conn.query("select char_counter(first_name) from author;"))
+ lens.emplace_back(r.at(0).get_int());
+
+ BOOST_CHECK(lens.size() == 1u);
+ BOOST_CHECK(lens[0] == (5 + 6 + 7 + 5));
+}
+
+BOOST_AUTO_TEST_CASE(aggregate_result)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ struct aggregate_func
+ {
+ aggregate_func(int value) : counter(value) {}
+ std::size_t counter;
+ sqlite::result<void> step(boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ return {};
+ }
+
+ sqlite::result<std::int64_t> final()
+ {
+ return counter;
+ }
+ };
+
+ sqlite::create_aggregate_function<aggregate_func>(
+ conn,
+ "char_counter", std::make_tuple(0));
+
+ std::vector<std::size_t> lens;
+
+ // language=sqlite
+ for (auto r : conn.query("select char_counter(first_name) from author;"))
+ lens.emplace_back(r.at(0).get_int());
+
+ BOOST_CHECK(lens.size() == 1u);
+ BOOST_CHECK(lens[0] == (5 + 6 + 7 + 5));
+}
+
+#if SQLITE_VERSION_NUMBER >= 3025000
+BOOST_AUTO_TEST_CASE(window)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ struct window_func
+ {
+ std::size_t counter;
+ void step(boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ }
+
+ void inverse(boost::span<sqlite::value, 1u> val)
+ {
+ counter -= val[0].get_text().size();
+ }
+
+
+ std::int64_t value()
+ {
+ return counter;
+ }
+ };
+
+ sqlite::create_window_function<window_func>(
+ conn,
+ "win_counter");
+
+ std::vector<std::size_t> lens;
+
+ // language=sqlite
+ for (auto r : conn.query(R"(
+select win_counter(first_name) over (
+ order by last_name rows between 1 preceding and 1 following ) as subrows
+ from author order by last_name asc;)"))
+ lens.emplace_back(r.at(0).get_int());
+
+ BOOST_CHECK(lens.size() == 4u);
+ BOOST_CHECK(lens[0] == 11);
+ BOOST_CHECK(lens[1] == 18);
+ BOOST_CHECK(lens[2] == 18);
+ BOOST_CHECK(lens[3] == 12);
+}
+
+BOOST_AUTO_TEST_CASE(window_result)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ struct window_func
+ {
+ std::size_t counter;
+ sqlite::result<void> step(boost::span<sqlite::value, 1u> val)
+ {
+ counter += val[0].get_text().size();
+ return{};
+ }
+
+ sqlite::result<void> inverse(boost::span<sqlite::value, 1u> val)
+ {
+ counter -= val[0].get_text().size();
+ return {};
+ }
+
+
+ sqlite::result<std::int64_t> value()
+ {
+ return counter;
+ }
+ };
+
+ sqlite::create_window_function<window_func>(
+ conn,
+ "win_counter");
+
+ std::vector<std::size_t> lens;
+
+ // language=sqlite
+ for (auto r : conn.query(R"(
+select win_counter(first_name) over (
+ order by last_name rows between 1 preceding and 1 following ) as subrows
+ from author order by last_name asc;)"))
+ lens.emplace_back(r.at(0).get_int());
+
+ BOOST_CHECK(lens.size() == 4u);
+ BOOST_CHECK(lens[0] == 11);
+ BOOST_CHECK(lens[1] == 18);
+ BOOST_CHECK(lens[2] == 18);
+ BOOST_CHECK(lens[3] == 12);
+}
+#endif
diff --git a/subprojects/boost-sqlite/test/hooks.cpp b/subprojects/boost-sqlite/test/hooks.cpp
new file mode 100644
index 0000000..2aa371f
--- /dev/null
+++ b/subprojects/boost-sqlite/test/hooks.cpp
@@ -0,0 +1,52 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/hooks.hpp>
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(hooks)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ bool called = false;
+ auto l =
+ [&](int op, core::string_view db, core::string_view table, sqlite3_int64 ) noexcept
+ {
+ BOOST_CHECK(op == SQLITE_INSERT);
+ BOOST_CHECK(db == "main");
+ BOOST_CHECK(table == "library");
+ called = true;
+ };
+
+
+ sqlite::update_hook(conn, l);
+ // language=sqlite
+ conn.query(R"(
+ insert into library ("name", "author") values
+ ('mustache',(select id from author where first_name = 'peter' and last_name = 'dimov'));
+ )");
+
+ BOOST_CHECK(called);
+
+#if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+ auto hk = [](sqlite::preupdate_context ctx,
+ int op,
+ const char * db_name,
+ const char * table_name,
+ sqlite3_int64 current_key,
+ sqlite3_int64 new_key) noexcept
+ {
+
+ };
+
+ preupdate_hook(conn, hk);
+#endif
+}
diff --git a/subprojects/boost-sqlite/test/json.cpp b/subprojects/boost-sqlite/test/json.cpp
new file mode 100644
index 0000000..fe924ab
--- /dev/null
+++ b/subprojects/boost-sqlite/test/json.cpp
@@ -0,0 +1,104 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/json.hpp>
+#include <boost/sqlite/connection.hpp>
+#include <boost/sqlite/function.hpp>
+#include <boost/json.hpp>
+#include "test.hpp"
+
+using namespace boost;
+
+
+BOOST_AUTO_TEST_SUITE(json_);
+
+BOOST_AUTO_TEST_CASE(to_value)
+{
+ sqlite::connection conn(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ auto q = conn.prepare("select 'foo', json_array($1, '2', null)").execute(std::make_tuple(1));
+
+ sqlite::row r = q.current();
+
+ BOOST_CHECK(!sqlite::is_json(r[0]));
+ BOOST_CHECK( sqlite::is_json(r[1]));
+ BOOST_CHECK(sqlite::as_json(r[1]) == (json::array{1, "2", nullptr}));
+ BOOST_CHECK(json::value_from(r[1]) == (json::array{1, "2", nullptr}));
+ BOOST_CHECK(!q.read_next());
+ BOOST_CHECK(!q.read_next());
+
+ // language=sqlite
+ q = conn.query(R"(select first_name, "name" from library inner join author a on a.id = library.author order by library.name asc)");
+
+ auto js = json::value_from(std::move(q));
+
+ json::array aa {
+ {
+ {"first_name", "vinnie"},
+ {"name", "beast"}
+ },
+ {
+ {"first_name", "peter"},
+ {"name", "mp11"}
+ },
+ {
+ {"first_name", "ruben"},
+ {"name", "mysql"}
+ },
+ {
+ {"first_name","peter"},
+ {"name", "variant2"}
+ },
+ };
+
+ BOOST_CHECK(aa == aa);
+};
+
+BOOST_AUTO_TEST_CASE(blob)
+{
+ sqlite::connection conn(":memory:");
+ BOOST_CHECK_THROW(json::value_from(conn.prepare("select $1;").execute({sqlite::zero_blob(1024)})), std::invalid_argument);
+ BOOST_CHECK(nullptr == json::value_from(conn.query("select null;").current().at(0)));
+ BOOST_CHECK(1234 == json::value_from(conn.query("select 1234;"). current().at(0)));
+ BOOST_CHECK(12.4 == json::value_from(conn.query("select 12.4;"). current().at(0)));
+}
+
+BOOST_AUTO_TEST_CASE(value)
+{
+ sqlite::connection conn(":memory:");
+ BOOST_CHECK_THROW(json::value_from(conn.prepare("select $1;").execute({sqlite::zero_blob(1024)}).current().at(0)), std::invalid_argument);
+ BOOST_CHECK(nullptr == json::value_from(conn.query("select null;").current().at(0).get_value()));
+ BOOST_CHECK(1234 == json::value_from(conn.query("select 1234;"). current().at(0).get_value()));
+ BOOST_CHECK(12.4 == json::value_from(conn.query("select 12.4;"). current().at(0).get_value()));
+ BOOST_CHECK("txt" == json::value_from(conn.query("select 'txt';"). current().at(0).get_value()));
+}
+
+
+BOOST_AUTO_TEST_CASE(subtype)
+{
+ sqlite::connection conn(":memory:");
+ BOOST_CHECK(!sqlite::is_json(conn.prepare("select $1;").execute({"foobar"}).current().at(0)));
+ BOOST_CHECK(sqlite::is_json(conn.prepare("select json_array($1);").execute({"foobar"}).current().at(0)));
+}
+
+
+BOOST_AUTO_TEST_CASE(function)
+{
+ sqlite::connection conn(":memory:");
+ sqlite::create_scalar_function(conn, "my_json_parse",
+ [](boost::sqlite::context<> , boost::span<boost::sqlite::value, 1u> s)
+ {
+ return json::parse(s[0].get_text());
+ });
+
+ BOOST_CHECK(sqlite::is_json(conn.prepare("select my_json_parse($1);")
+ .execute({R"({"foo" : 42, "bar" : "xyz"})"}).current().at(0)));
+}
+
+
+BOOST_AUTO_TEST_SUITE_END(); \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/main_test.cpp b/subprojects/boost-sqlite/test/main_test.cpp
new file mode 100644
index 0000000..23c2f70
--- /dev/null
+++ b/subprojects/boost-sqlite/test/main_test.cpp
@@ -0,0 +1,9 @@
+// Copyright (c) 2021 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#define BOOST_TEST_MODULE sqlite_test
+#include <boost/test/unit_test.hpp>
+
+#include <boost/sqlite.hpp>
diff --git a/subprojects/boost-sqlite/test/meta_data.cpp b/subprojects/boost-sqlite/test/meta_data.cpp
new file mode 100644
index 0000000..2861a1c
--- /dev/null
+++ b/subprojects/boost-sqlite/test/meta_data.cpp
@@ -0,0 +1,45 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include <boost/sqlite/meta_data.hpp>
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+#include <boost/algorithm/string.hpp>
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(meta_data)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+
+
+ auto fn = table_column_meta_data(conn, "author", "first_name");
+ BOOST_CHECK_MESSAGE(boost::iequals(fn.data_type, "TEXT"), fn.data_type);
+ BOOST_CHECK(!fn.auto_increment);
+ BOOST_CHECK_MESSAGE(boost::iequals(fn.collation, "BINARY"), fn.collation);
+ BOOST_CHECK(!fn.primary_key);
+ BOOST_CHECK( fn.not_null);
+
+ auto ln = table_column_meta_data(conn, "main", "author", "last_name");
+ BOOST_CHECK_MESSAGE(boost::iequals(ln.data_type, "TEXT"), ln.data_type);
+ BOOST_CHECK(!ln.auto_increment);
+ BOOST_CHECK_MESSAGE(boost::iequals(ln.collation, "BINARY"), ln.collation);
+ BOOST_CHECK(!ln.primary_key);
+ BOOST_CHECK(!ln.not_null);
+
+ auto id = table_column_meta_data(conn, "main", "author", "id");
+ BOOST_CHECK(boost::iequals(id.data_type, "INTEGER"));
+ BOOST_CHECK( id.auto_increment);
+ BOOST_CHECK(boost::iequals(id.collation, "BINARY"));
+ BOOST_CHECK( id.primary_key);
+ BOOST_CHECK( id.not_null);
+
+ conn.close();
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/mutex.cpp b/subprojects/boost-sqlite/test/mutex.cpp
new file mode 100644
index 0000000..89023e1
--- /dev/null
+++ b/subprojects/boost-sqlite/test/mutex.cpp
@@ -0,0 +1,31 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/mutex.hpp>
+
+#include <boost/test/unit_test.hpp>
+#include <mutex>
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(mutex)
+{
+ sqlite::mutex mtx;
+ sqlite::recursive_mutex rmtx;
+ BOOST_CHECK(mtx.try_lock());
+ BOOST_CHECK(!mtx.try_lock());
+ mtx.unlock();
+
+ std::lock_guard<sqlite::mutex> l1{mtx};
+ std::lock_guard<sqlite::recursive_mutex> l2{rmtx};
+ std::lock_guard<sqlite::recursive_mutex> l{rmtx};
+
+ BOOST_CHECK(rmtx.try_lock());
+ BOOST_CHECK(rmtx.try_lock());
+ BOOST_CHECK(rmtx.try_lock());
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/statement.cpp b/subprojects/boost-sqlite/test/statement.cpp
new file mode 100644
index 0000000..b9e96f6
--- /dev/null
+++ b/subprojects/boost-sqlite/test/statement.cpp
@@ -0,0 +1,74 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/connection.hpp>
+#include "test.hpp"
+
+#include <boost/json.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <unordered_map>
+
+
+using namespace boost;
+
+
+BOOST_AUTO_TEST_CASE(statement)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+#if SQLITE_VERSION_NUMBER >= 3020000
+ std::unique_ptr<int> data{new int(42)};
+
+ auto ip = data.get();
+ auto q = conn.prepare("select $1;").execute(std::make_tuple(std::move(data)));
+ sqlite::row r = q.current();
+ BOOST_CHECK(r.size() == 1u);
+
+ auto v = r.at(0).get_value();
+ BOOST_CHECK(v.type() == sqlite::value_type::null);
+ BOOST_CHECK(v.get_pointer<int>() != nullptr);
+ BOOST_CHECK(v.get_pointer<int>() == ip);
+ BOOST_CHECK(v.get_pointer<double>() == nullptr);
+#endif
+ BOOST_CHECK_THROW(conn.prepare("select * from nothing where name = $name;").execute({}), boost::system::system_error);
+}
+
+
+BOOST_AUTO_TEST_CASE(decltype_)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+ auto q = conn.prepare("select* from author;");
+
+ BOOST_CHECK(boost::iequals(q.declared_type(0), "INTEGER"));
+ BOOST_CHECK(boost::iequals(q.declared_type(1), "TEXT"));
+ BOOST_CHECK(boost::iequals(q.declared_type(2), "TEXT"));
+
+ BOOST_CHECK_THROW(conn.prepare("elect * from nothing;"), boost::system::system_error);
+}
+
+
+BOOST_AUTO_TEST_CASE(map)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+ auto q = conn.prepare("select * from author where first_name = $name;").execute({{"name", 42}});
+ BOOST_CHECK_THROW(conn.prepare("select * from nothing where name = $name;").execute({{"n4ame", 123}}), boost::system::system_error);
+
+ std::unordered_map<std::string, variant2::variant<int, std::string>> params = {{"name", 42}};
+ q = conn.prepare("select * from author where first_name = $name;").execute(params);
+ BOOST_CHECK_THROW(conn.prepare("select * from nothing where name = $name;").execute(params), boost::system::system_error);
+
+ BOOST_CHECK_THROW(conn.prepare("elect * from nothing;"), boost::system::system_error);
+} \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/static_resultset.cpp b/subprojects/boost-sqlite/test/static_resultset.cpp
new file mode 100644
index 0000000..6a9b7c0
--- /dev/null
+++ b/subprojects/boost-sqlite/test/static_resultset.cpp
@@ -0,0 +1,95 @@
+//
+// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+
+#include <boost/sqlite/static_resultset.hpp>
+#include <boost/sqlite/connection.hpp>
+
+#include <boost/describe/class.hpp>
+
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(tuple)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+ using tup = std::tuple<sqlite_int64, sqlite::string_view, sqlite::string_view>;
+
+ bool found = false;
+ for (tup t : conn.query<tup>("select id, first_name, last_name from author where last_name = 'hodges';"))
+ {
+ BOOST_CHECK(std::get<1>(t) == "richard");
+ found = true;
+ }
+ BOOST_CHECK(found);
+
+ found = false;
+ for (tup t : conn.prepare("select id, first_name, last_name from author where last_name = ?;")
+ .execute<tup>({"hodges"}))
+ {
+ BOOST_CHECK(std::get<1>(t) == "richard");
+ found = true;
+ }
+ BOOST_CHECK(found);
+
+ BOOST_CHECK_THROW(conn.query<tup>("select first_name, last_name from author where last_name = 'hodges';"),
+ system::system_error);
+ conn.close();
+}
+
+struct author
+{
+ std::string last_name;
+ std::string first_name;
+};
+
+#if __cplusplus < 202002L
+BOOST_DESCRIBE_STRUCT(author, (), (last_name, first_name));
+#endif
+
+#if __cplusplus > 201402L
+
+BOOST_AUTO_TEST_CASE(reflection)
+{
+ sqlite::connection conn;
+ conn.connect(":memory:");
+ conn.execute(
+#include "test-db.sql"
+ );
+
+
+ bool found = false;
+ for (author t : conn.query<author>("select first_name, last_name from author where last_name = 'hodges';"))
+ {
+ BOOST_CHECK(t.first_name == "richard");
+ BOOST_CHECK(t.last_name == "hodges");
+ found = true;
+ }
+ BOOST_CHECK(found);
+
+ found = false;
+ for (author t : conn.prepare("select first_name, last_name from author where last_name = ?;")
+ .execute<author>({"hodges"}))
+ {
+ BOOST_CHECK(t.first_name == "richard");
+ BOOST_CHECK(t.last_name == "hodges");
+ found = true;
+ }
+ BOOST_CHECK(found);
+
+ BOOST_CHECK_THROW(conn.query<author>("select id, first_name, last_name from author where last_name = 'hodges';"),
+ system::system_error);
+ conn.close();
+}
+
+#endif
diff --git a/subprojects/boost-sqlite/test/test-db.sql b/subprojects/boost-sqlite/test/test-db.sql
new file mode 100644
index 0000000..251313c
--- /dev/null
+++ b/subprojects/boost-sqlite/test/test-db.sql
@@ -0,0 +1,29 @@
+--query_helper(R"(
+
+create table author (
+ id integer primary key autoincrement not null,
+ first_name text unique not null,
+ last_name text
+);
+
+insert into author (first_name, last_name) values
+ ('vinnie', 'falco'),
+ ('richard', 'hodges'),
+ ('ruben', 'perez'),
+ ('peter', 'dimov')
+;
+
+create table library(
+ id integer primary key autoincrement,
+ name text unique,
+ author integer references author(id)
+);
+
+insert into library ("name", author) values
+ ('beast', (select id from author where first_name = 'vinnie' and last_name = 'falco')),
+ ('mysql', (select id from author where first_name = 'ruben' and last_name = 'perez')),
+ ('mp11', (select id from author where first_name = 'peter' and last_name = 'dimov')),
+ ('variant2', (select id from author where first_name = 'peter' and last_name = 'dimov'))
+;
+
+--)") \ No newline at end of file
diff --git a/subprojects/boost-sqlite/test/test.hpp b/subprojects/boost-sqlite/test/test.hpp
new file mode 100644
index 0000000..3244d9e
--- /dev/null
+++ b/subprojects/boost-sqlite/test/test.hpp
@@ -0,0 +1,22 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_SQLITE_TEST_HPP
+#define BOOST_SQLITE_TEST_HPP
+
+#include <boost/test/unit_test.hpp>
+
+struct query_helper
+{
+ const char * query;
+ query_helper(const char * query) : query(query) {}
+
+ const char * operator--() const {return query;}
+};
+
+
+#endif //BOOST_SQLITE_TEST_HPP
diff --git a/subprojects/boost-sqlite/test/transaction.cpp b/subprojects/boost-sqlite/test/transaction.cpp
new file mode 100644
index 0000000..f1331b7
--- /dev/null
+++ b/subprojects/boost-sqlite/test/transaction.cpp
@@ -0,0 +1,89 @@
+//
+// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/sqlite/transaction.hpp>
+
+#include "test.hpp"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_CASE(transaction)
+{
+ sqlite::connection conn{":memory:"};
+ conn.execute("create table test(nr integer);");
+
+ auto check_size = [&]{
+ std::size_t n = 0ull;
+ for (auto l : conn.query("select * from test"))
+ {
+ boost::ignore_unused(l);
+ n++;
+ }
+
+ return n;
+ };
+
+ {
+ sqlite::transaction t{conn};
+ BOOST_CHECK_THROW(sqlite::transaction{conn}, system::system_error);
+
+ conn.execute("insert into test values(1), (2)");
+ BOOST_CHECK(check_size() == 2);
+
+ {
+ sqlite::savepoint sq{conn, "s1"};
+ conn.execute("insert into test values(3)");
+ BOOST_CHECK(check_size() == 3);
+ }
+
+ BOOST_CHECK(check_size() == 2);
+
+ {
+ sqlite::savepoint sq{conn, "s1"};
+ conn.execute("insert into test values(4)");
+ BOOST_CHECK(check_size() == 3);
+ sq.commit();
+ }
+
+ BOOST_CHECK(check_size() == 3);
+
+ {
+ sqlite::savepoint sq{conn, "s1"};
+ conn.execute("insert into test values(5)");
+ BOOST_CHECK(check_size() == 4);
+ {
+ sqlite::savepoint sq2{conn, "s2"};
+ conn.execute("insert into test values (6), (7)");
+ BOOST_CHECK(check_size() == 6);
+ sq2.commit();
+ }
+ BOOST_CHECK_EQUAL(check_size(), 6u);
+ }
+ BOOST_CHECK_EQUAL(check_size(), 3u);
+
+ }
+
+ BOOST_CHECK(conn.query("select * from test").done());
+
+ {
+ system::error_code ec;
+ sqlite::error_info ei;
+ conn.execute("BEGIN", ec, ei);
+ BOOST_CHECK(!ec);
+ conn.execute("BEGIN", ec, ei);
+ BOOST_CHECK(ec);
+
+ BOOST_CHECK_THROW(sqlite::transaction{conn}, system::system_error);
+ sqlite::transaction t{conn, sqlite::transaction::adopt_transaction};
+
+ conn.execute("insert into test values (42), (3);");
+ t.commit();
+ }
+
+ BOOST_CHECK_EQUAL(check_size(), 2u);
+}
+
diff --git a/subprojects/boost-sqlite/test/vtable.cpp b/subprojects/boost-sqlite/test/vtable.cpp
new file mode 100644
index 0000000..4f38732
--- /dev/null
+++ b/subprojects/boost-sqlite/test/vtable.cpp
@@ -0,0 +1,256 @@
+//
+// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/test/unit_test.hpp>
+#include "test.hpp"
+
+#include <iostream>
+#include <boost/sqlite.hpp>
+#include <boost/unordered_map.hpp>
+#include <boost/intrusive/list.hpp>
+
+#include <string>
+#include <vector>
+
+using namespace boost;
+
+
+BOOST_AUTO_TEST_SUITE(vtable_);
+
+struct del_info : sqlite3_index_info
+{
+ del_info() : sqlite3_index_info()
+ {
+
+ }
+
+ ~del_info()
+ {
+ if (needToFreeIdxStr)
+ sqlite3_free(idxStr);
+ }
+};
+
+struct trivial_struct
+{
+ trivial_struct() = default;
+ int i{1}, j{2}, k{3};
+};
+
+
+
+struct simple_cursor final : sqlite::vtab::cursor<core::string_view>
+{
+
+ simple_cursor(std::vector<std::string>::const_iterator itr,
+ std::vector<std::string>::const_iterator end) : itr(itr), end(end) {}
+ std::vector<std::string>::const_iterator itr, end;
+
+ sqlite::result<void> next() {itr++; return {};}
+ sqlite::result<sqlite3_int64> row_id() {return *reinterpret_cast<sqlite3_int64*>(&itr);}
+
+ sqlite::result<core::string_view> column(int i, bool /* no_change */)
+ {
+ if (i > 0)
+ throw_exception(std::out_of_range("column out of range"));
+
+ return *itr;
+ }
+
+ bool eof() noexcept {return itr == end;}
+};
+
+struct simple_table final : sqlite::vtab::table<simple_cursor>
+{
+ const char * declaration()
+ {
+ return R"(create table x(name text);)";
+ }
+
+ simple_table(std::vector<std::string> & names) : names(names) {}
+ std::vector<std::string> & names;
+
+ sqlite::result<cursor_type> open()
+ {
+ return cursor_type{names.begin(), names.end()};
+ }
+
+};
+
+struct simple_test_impl final : sqlite::vtab::eponymous_module<simple_table>
+{
+ std::vector<std::string> names = {"ruben", "vinnie", "richard", "klemens"};
+
+
+
+
+ sqlite::result<table_type> connect(sqlite::connection,
+ int /*argc*/, const char * const * /*argv*/)
+ {
+ return table_type{names};
+ }
+
+};
+
+
+BOOST_AUTO_TEST_CASE(simple_reader)
+{
+ sqlite::connection conn(":memory:");
+ auto & m = create_module(conn, "test_table", simple_test_impl{});
+
+ auto itr = m.names.begin();
+ for (auto q : conn.query("select * from test_table ;"))
+ {
+ BOOST_CHECK(q.size() == 1);
+ BOOST_CHECK(q.at(0).get_text() == *itr++);
+ }
+
+ m.names.emplace_back("marcelo");
+ itr = m.names.begin();
+ for (auto q : conn.query("select * from test_table ;"))
+ {
+ BOOST_CHECK(q.size() == 1);
+ BOOST_CHECK(q.at(0).get_text() == *itr++);
+ }
+
+ BOOST_CHECK_THROW(conn.query("insert into test_table values('chris')"), boost::system::system_error);
+}
+
+struct modifyable_table;
+
+struct modifyable_cursor final : sqlite::vtab::cursor<variant2::variant<std::int64_t, core::string_view>>
+{
+ using iterator = boost::unordered_map<std::int64_t, std::string>::const_iterator;
+
+ modifyable_cursor(iterator itr, iterator end) : itr(itr), end(end) {}
+
+ iterator itr, end;
+
+ sqlite::result<void> next() noexcept {itr++; return {};}
+ sqlite::result<sqlite3_int64> row_id() noexcept {return itr->first;}
+
+ sqlite::result<column_type> column(int i, bool /* no_change */) noexcept
+ {
+ switch (i)
+ {
+ case 0: return itr->first;
+ case 1: return itr->second;
+ default:
+ return sqlite::error(SQLITE_RANGE, sqlite::error_info("column out of range"));
+
+ }
+ }
+
+ bool eof() noexcept {return itr == end;}
+};
+
+struct modifyable_table final :
+ sqlite::vtab::table<modifyable_cursor>,
+ intrusive::list_base_hook<intrusive::link_mode<intrusive::auto_unlink> >,
+ sqlite::vtab::modifiable
+{
+ const char * declaration()
+ {
+ return R"(create table x(id integer primary key autoincrement, name text);)";
+ }
+
+ std::string name;
+ boost::unordered_map<std::int64_t, std::string> names;
+
+ int last_index = 0;
+
+ modifyable_table() = default;
+ modifyable_table(modifyable_table && lhs)
+ : name(std::move(lhs.name)), names(std::move(lhs.names)), last_index(lhs.last_index )
+ {
+ this->swap_nodes(lhs);
+ }
+
+
+ sqlite::result<modifyable_cursor> open()
+ {
+ return modifyable_cursor{names.begin(), names.end()};
+ }
+
+ sqlite::result<void> delete_(sqlite::value key)
+ {
+ BOOST_CHECK(names.erase(key.get_int()) == 1);
+ return {};
+ }
+ sqlite::result<sqlite_int64> insert(sqlite::value /*key*/, span<sqlite::value> values,
+ int /*on_conflict*/)
+ {
+ int id = values[0].is_null() ? last_index++ : values[0].get_int();
+ auto itr = names.emplace(id, values[1].get_text()).first;
+ return itr->first;
+ }
+ sqlite::result<sqlite_int64> update(sqlite::value old_key, sqlite::value new_key,
+ span<sqlite::value> values,
+ int /*on_conflict*/)
+ {
+ if (new_key.get_int() != old_key.get_int())
+ names.erase(old_key.get_int());
+ names.insert_or_assign(new_key.get_int(), values[1].get_text());
+ return 0u;
+ }
+};
+
+struct modifyable_test_impl final : sqlite::vtab::eponymous_module<modifyable_table>
+{
+ modifyable_test_impl() = default;
+
+ intrusive::list<table_type, intrusive::constant_time_size<false>> list;
+
+ sqlite::result<table_type> connect(sqlite::connection,
+ int /*argc*/, const char * const argv[]) noexcept
+ {
+ table_type tt{};
+ tt.name.assign(argv[2]);
+ list.push_back(tt);
+ return sqlite::result<table_type>(std::move(tt));
+ }
+};
+
+
+BOOST_AUTO_TEST_CASE(modifyable_reader)
+{
+ sqlite::connection conn(":memory:");
+
+ auto & m = create_module(conn, "name_table", modifyable_test_impl{});
+ conn.execute("create virtual table test_table USING name_table; ");
+
+ BOOST_CHECK(m.list.size() == 1);
+ BOOST_CHECK(m.list.front().name == "test_table");
+ BOOST_CHECK(m.list.front().names.empty());
+
+ conn.prepare("insert into test_table(name) values ($1), ($2), ($3), ($4);")
+ .execute(std::make_tuple("vinnie", "richard", "ruben", "peter"));
+
+ auto & t = m.list.front();
+ BOOST_CHECK(t.names.size() == 4);
+ BOOST_CHECK(t.names[0] == "vinnie");
+ BOOST_CHECK(t.names[1] == "richard");
+ BOOST_CHECK(t.names[2] == "ruben");
+ BOOST_CHECK(t.names[3] == "peter");
+
+ conn.prepare("delete from test_table where name = $1;").execute(std::make_tuple("richard"));
+
+ BOOST_CHECK(t.names.size() == 3);
+ BOOST_CHECK(t.names[0] == "vinnie");
+ BOOST_CHECK(t.names[2] == "ruben");
+ BOOST_CHECK(t.names[3] == "peter");
+
+ conn.prepare("update test_table set name = $1 where id = 0;").execute(std::make_tuple("richard"));
+
+ BOOST_CHECK(t.names.size() == 3);
+ BOOST_CHECK(t.names[0] == "richard");
+ BOOST_CHECK(t.names[2] == "ruben");
+ BOOST_CHECK(t.names[3] == "peter");
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()