From ee3abe362ff6c758d1cd4000a90bc24d2aa41ee9 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 7 Jun 2024 19:51:20 +0200 Subject: [PATCH 01/49] Started implementing SQL views --- dev/cte_storage.h | 2 +- dev/functional/config.h | 7 + dev/implementations/storage_definitions.h | 2 +- dev/object_from_column_builder.h | 20 +- dev/{view.h => object_view.h} | 6 +- dev/schema/mapped_object.h | 153 +++++++ dev/schema/table.h | 9 +- dev/schema/view.h | 180 ++++++++ dev/statement_serializer.h | 21 + dev/storage.h | 4 +- include/sqlite_orm/sqlite_orm.h | 426 +++++++++++++++++- tests/schema/view_tests.cpp | 30 ++ .../schema/view.cpp | 43 ++ .../schema/virtual_table.cpp | 2 +- tests/static_tests/view_static_tests.cpp | 24 + tests/table_name_collector.cpp | 16 + tests/tests5.cpp | 4 +- 17 files changed, 914 insertions(+), 35 deletions(-) rename dev/{view.h => object_view.h} (92%) create mode 100644 dev/schema/mapped_object.h create mode 100644 dev/schema/view.h create mode 100644 tests/schema/view_tests.cpp create mode 100644 tests/statement_serializer_tests/schema/view.cpp create mode 100644 tests/static_tests/view_static_tests.cpp diff --git a/dev/cte_storage.h b/dev/cte_storage.h index 60efc82e0..959334998 100644 --- a/dev/cte_storage.h +++ b/dev/cte_storage.h @@ -74,7 +74,7 @@ namespace sqlite_orm { using object_type = table_type_of_t; using column_type = column_t; - return column_type{std::move(name), finalColRef, empty_setter{}}; + return column_type{std::move(name), finalColRef, empty_setter{}, std::tuple<>{}}; } /** diff --git a/dev/functional/config.h b/dev/functional/config.h index 5a3e95b6e..7cd4c0a1e 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -63,3 +63,10 @@ #if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) && defined(SQLITE_ORM_IF_CONSTEXPR_SUPPORTED) #define SQLITE_ORM_WITH_CTE #endif + +// note: PFR depends on `SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED` for field name determination +#if(defined(SQLITE_ORM_AGGREGATE_BASES_SUPPORTED) && defined(SQLITE_ORM_CONSTEVAL_SUPPORTED) && \ + defined(SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED)) && \ + (__cpp_lib_byte >= 201603L && __cpp_lib_remove_cvref >= 201711L) +#define SQLITE_ORM_WITH_VIEW +#endif diff --git a/dev/implementations/storage_definitions.h b/dev/implementations/storage_definitions.h index 30d498b13..86f8bf061 100644 --- a/dev/implementations/storage_definitions.h +++ b/dev/implementations/storage_definitions.h @@ -4,7 +4,7 @@ */ #pragma once #include // std::is_same -#include +#include // std::stringstream #include // std::reference_wrapper, std::cref #include // std::find_if, std::ranges::find diff --git a/dev/object_from_column_builder.h b/dev/object_from_column_builder.h index 86aae7f40..ad7d573f7 100644 --- a/dev/object_from_column_builder.h +++ b/dev/object_from_column_builder.h @@ -3,9 +3,13 @@ #include #include // std::is_member_object_pointer #include // std::move +#ifdef SQLITE_ORM_WITH_VIEW +#include // std::byte +#endif #include "functional/static_magic.h" #include "member_traits/member_traits.h" +#include "type_traits.h" #include "table_reference.h" #include "row_extractor.h" #include "schema/column.h" @@ -49,12 +53,26 @@ namespace sqlite_orm { (object.*column.setter)(std::move(value)); })(column); } + +#ifdef SQLITE_ORM_WITH_VIEW + template + requires(is_column_pointer_v) + void operator()(const column_field& column) { + using field_type = field_type_t>; + const auto rowExtractor = row_value_extractor(); + auto value = rowExtractor.extract(this->stmt, ++this->columnIndex); + // calculate absolute address of member from relative address + field_type* field = reinterpret_cast(reinterpret_cast(&object) + + reinterpret_cast(column.member_pointer)); + *field = std::move(value); + } +#endif }; /** * Specialization for a table reference. * - * This plays together with `column_result_of_t`, which returns `object_t` as `table_referenece` + * This plays together with `column_result_of_t`, which returns `object_t` as `table_reference` */ template struct struct_extractor, DBOs> { diff --git a/dev/view.h b/dev/object_view.h similarity index 92% rename from dev/view.h rename to dev/object_view.h index 36c544258..3d674737d 100644 --- a/dev/view.h +++ b/dev/object_view.h @@ -27,16 +27,16 @@ namespace sqlite_orm { * All these functions are not right const cause all of them may open SQLite connections. */ template - struct view_t { + struct object_view { using mapped_type = T; using storage_type = S; - using self = view_t; + using self = object_view; storage_type& storage; connection_ref connection; get_all_t, Args...> args; - view_t(storage_type& stor, decltype(connection) conn, Args&&... args_) : + object_view(storage_type& stor, decltype(connection) conn, Args&&... args_) : storage(stor), connection(std::move(conn)), args{std::make_tuple(std::forward(args_)...)} {} size_t size() const { diff --git a/dev/schema/mapped_object.h b/dev/schema/mapped_object.h new file mode 100644 index 000000000..fb9e06b2e --- /dev/null +++ b/dev/schema/mapped_object.h @@ -0,0 +1,153 @@ +#pragma once + +#include // std::is_member_pointer +#include // std::string +#include // std::tuple_element +#include // std::move + +#include "../functional/cxx_universal.h" // ::size_t +#include "../functional/cxx_type_traits_polyfill.h" +#include "../functional/cxx_functional_polyfill.h" +#include "../functional/static_magic.h" +#include "../functional/mpl.h" +#include "../functional/index_sequence_util.h" +#include "../tuple_helper/tuple_filter.h" +#include "../tuple_helper/tuple_traits.h" +#include "../tuple_helper/tuple_iteration.h" +#include "../tuple_helper/tuple_transformer.h" +#include "../member_traits/member_traits.h" +#include "../typed_comparator.h" +#include "../type_traits.h" +#include "../alias_traits.h" +#include "../constraints.h" +#include "../table_info.h" +#include "column.h" + +namespace sqlite_orm { + + namespace internal { + + struct basic_table { + + /** + * Table name. + */ + std::string name; + }; + + /** + * Base for a mapped schema object aka table, view. + */ + template + struct mapped_object_t : basic_table { + using object_type = O; + using elements_type = std::tuple; + + elements_type elements; + +#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED + mapped_object_t(std::string name, elements_type elements) : + basic_table{std::move(name)}, elements{std::move(elements)} {} +#endif + + /* + * Returns the number of elements of the specified type. + */ + template class Trait> + static constexpr int count_of() { + using sequence_of = filter_tuple_sequence_t; + return int(sequence_of::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_with() { + using filtered_index_sequence = col_index_sequence_with; + return int(filtered_index_sequence::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_excluding() { + using excluded_col_index_sequence = col_index_sequence_excluding; + return int(excluded_col_index_sequence::size()); + } + + /** + * Function used to get field value from object by mapped member pointer/setter/getter. + * + * For a setter the corresponding getter has to be searched, + * so the method returns a pointer to the field as returned by the found getter. + * Otherwise the method invokes the member pointer and returns its result. + */ + template = true> + decltype(auto) object_field_value(const object_type& object, M memberPointer) const { + return polyfill::invoke(memberPointer, object); + } + + template = true> + const member_field_type_t* object_field_value(const object_type& object, M memberPointer) const { + using field_type = member_field_type_t; + const field_type* res = nullptr; + iterate_tuple(this->elements, + col_index_sequence_with_field_type{}, + call_as_template_base([&res, &memberPointer, &object](const auto& column) { + if(compare_any(column.setter, memberPointer)) { + res = &polyfill::invoke(column.member_pointer, object); + } + })); + return res; + } + + /** + * Searches column name by class member pointer passed as the first argument. + * @return column name or empty string if nothing found. + */ + template = true> + const std::string* find_column_name(M m) const { + const std::string* res = nullptr; + using field_type = member_field_type_t; + iterate_tuple(this->elements, + col_index_sequence_with_field_type{}, + [&res, m](auto& c) { + if(compare_any(c.member_pointer, m) || compare_any(c.setter, m)) { + res = &c.name; + } + }); + return res; + } + + /** + * Call passed lambda with all defined columns. + * @param lambda Lambda called for each column. Function signature: `void(auto& column)` + */ + template + void for_each_column(L&& lambda) const { + using col_index_sequence = filter_tuple_sequence_t; + iterate_tuple(this->elements, col_index_sequence{}, lambda); + } + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template class OpTraitFn, class L> + void for_each_column_excluding(L&& lambda) const { + iterate_tuple(this->elements, col_index_sequence_excluding{}, lambda); + } + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template = true> + void for_each_column_excluding(L&& lambda) const { + this->template for_each_column_excluding(lambda); + } + }; + } +} diff --git a/dev/schema/table.h b/dev/schema/table.h index 3fec77cf9..979b98335 100644 --- a/dev/schema/table.h +++ b/dev/schema/table.h @@ -22,6 +22,7 @@ #include "../alias_traits.h" #include "../constraints.h" #include "../table_info.h" +#include "mapped_object.h" #include "column.h" namespace sqlite_orm { @@ -55,14 +56,6 @@ namespace sqlite_orm { using mapped_object_type_for_t = polyfill::detected_or_t; #endif - struct basic_table { - - /** - * Table name. - */ - std::string name; - }; - /** * Table definition. */ diff --git a/dev/schema/view.h b/dev/schema/view.h new file mode 100644 index 000000000..5ae93ce17 --- /dev/null +++ b/dev/schema/view.h @@ -0,0 +1,180 @@ +#pragma once +#ifdef SQLITE_ORM_WITH_VIEW +#include // std::remove_cvref +#include // std::forward, std::move, std::index_sequence, std::make_index_sequence +#include // std::byte +#include // offsetof +#include +#include "../functional/cxx_universal.h" // ::size_t +#include "../column_pointer.h" +#include "../select_constraints.h" +#include "column.h" +#include "mapped_object.h" + +namespace boost::pfr { + namespace detail { + namespace sequence_tuple { + template + consteval auto get_nth_base(const base_from_member& t) noexcept { + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return t; + } + + template + constexpr auto* get_nth_relative_address() noexcept { + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + static_assert(N < Tpl::size_v); + using nth_type = decltype(get_nth_base(std::declval())); + using field_type = decltype(nth_type::value); + return (field_type*)(std::byte*)offsetof(Tpl, nth_type::value); + } + } + } + + template + constexpr auto* get_relative_address() { + static_assert(sizeof(O) == sizeof(TS), + "====================> Boost.PFR: Member sequence does not indicate correct size for struct " + "type! Maybe the user-provided type is not a SimpleAggregate?"); + static_assert( + alignof(O) == alignof(TS), + "====================> Boost.PFR: Member sequence does not indicate correct alignment for struct type!"); + + return detail::sequence_tuple::get_nth_relative_address(); + } +} + +namespace sqlite_orm { + + /** + * Factory function for a column definition from a relative pointer to an object of the object to be mapped. + */ + template + requires(internal::is_column_pointer_v) + internal::column_t + make_column(std::string name, C relativeField, Op... constraints) { + static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + + // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, + // as this will lead to UB with Clang on MinGW! + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), relativeField, {}, std::tuple{std::move(constraints)...}}); + } + + namespace internal { + template + decltype(auto) get_cte_driving_subselect(const Select& select); + + template + struct column_field, empty_setter> { + using member_pointer_t = F O::*; + using setter_type = empty_setter; + using object_type = O; + using field_type = F; + + /** + * Relative pointer to member (offset) used to read and write a field value. + */ + const column_pointer member_pointer; + + SQLITE_ORM_NOUNIQUEADDRESS + const empty_setter setter; + + /** + * Simplified interface for `NOT NULL` constraint + */ + constexpr bool is_not_null() const { + return !type_is_nullable::value; + } + }; + + template + bool compare_any(F O::*m, const column_pointer& relative) { + constexpr O* object = nullptr; + return &(object->*m) == relative.field; + } + + template + bool compare_any(const column_pointer& relative, F O::*m) { + constexpr O* object = nullptr; + return relative.field == &(object->*m); + } + + /** + * View definition, mapping an aggregate object type to a corresponding select statement. + */ + template + struct view_t : mapped_object_t { + using base_type = mapped_object_t; + using object_type = typename base_type::object_type; + using elements_type = typename base_type::elements_type; + using select_type = Select; + + select_type select; + + //#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED + view_t(std::string name, elements_type elements, Select select) : + base_type{std::move(name), std::move(elements)}, select{std::move(select)} {} + //#endif + }; + + template + using columns_size_t = std::tuple_size; + + template + auto make_view(std::string name, std::index_sequence, Select select) { + namespace pfr = boost::pfr; + namespace pfrd = pfr::detail; + namespace pfrs = pfrd::sequence_tuple; + + static_assert(std::is_aggregate_v); + + using PfrTpl = decltype(pfrd::tie_as_tuple(pfrd::fake_object())); + // object's member types as a tuple + using TS = pfrs::tuple::type>...>; + + using DrivingSelect = std::remove_cvref_t; + + using columns_size = polyfill::detected_or_t, columns_size_t, DrivingSelect>; + static_assert(columns_size::value == PfrTpl::size_v); + + using view_type = + view_t(std::string(pfr::get_name()), + column_pointer())>{ + pfr::get_relative_address()}))...>; + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return view_type{std::move(name), + {make_column<>(std::string(pfr::get_name()), + column_pointer())>{ + pfr::get_relative_address()})...}, + std::move(select)}); + } + } + + template + auto make_view(std::string name, Select select) { + static_assert(polyfill::conjunction_v>, "You must specify a select statement"); + + select.highest_level = true; + return internal::make_view(std::move(name), + std::make_index_sequence>{}, + std::move(select)); + } + +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Factory function for a view definition. + * + * The mapped object type is explicitly specified, columns and their names are deferred from the object type. + * The object type must be an aggregate. + */ + template + auto make_view(std::string name, Select select) { + return make_view>(std::move(name), std::forward(select)); + } +#endif +} +#endif + // #include "util.h" namespace sqlite_orm { @@ -18736,6 +19110,26 @@ namespace sqlite_orm { } }; +#ifdef SQLITE_ORM_WITH_VIEW + template + struct statement_serializer, void> { + using statement_type = view_t; + + template + std::string operator()(const statement_type& statement, const Ctx& context) { + auto subContext = context; + subContext.fts5_columns = true; + std::stringstream ss; + ss << "CREATE VIEW " << streaming_identifier(statement.name) << " (" + << streaming_expressions_tuple(statement.elements, subContext) + << ")" + " AS " + << serialize(statement.select, context); + return ss.str(); + } + }; +#endif + template<> struct statement_serializer { using statement_type = current_time_t; @@ -21071,7 +21465,7 @@ namespace sqlite_orm { using object_type = table_type_of_t; using column_type = column_t; - return column_type{std::move(name), finalColRef, empty_setter{}}; + return column_type{std::move(name), finalColRef, empty_setter{}, std::tuple<>{}}; } /** @@ -21520,7 +21914,7 @@ namespace sqlite_orm { public: template - view_t iterate(Args&&... args) { + object_view iterate(Args&&... args) { this->assert_mapped_type(); auto con = this->get_connection(); @@ -23627,7 +24021,7 @@ namespace sqlite_orm { */ #include // std::is_same -#include +#include // std::stringstream #include // std::reference_wrapper, std::cref #include // std::find_if, std::ranges::find diff --git a/tests/schema/view_tests.cpp b/tests/schema/view_tests.cpp new file mode 100644 index 000000000..6a3c53a69 --- /dev/null +++ b/tests/schema/view_tests.cpp @@ -0,0 +1,30 @@ +#include +#include + +#ifdef SQLITE_ORM_WITH_VIEW +using namespace sqlite_orm; + +struct UserViewSchemaTests { + int id = 0; + std::string name; +}; + +TEST_CASE("view::find_column_name") { + struct User { + int id = 0; + std::string name; + }; + + auto table = make_table("user", + make_column("id", &User::id, primary_key().autoincrement()), + make_column("name", &User::name)); + auto view = make_view("user_view", select(columns(&User::id, &User::name))); + + SECTION("fields") { + REQUIRE((view.find_column_name(&UserViewSchemaTests::id) && + *view.find_column_name(&UserViewSchemaTests::id) == "id")); + REQUIRE((view.find_column_name(&UserViewSchemaTests::name) && + *view.find_column_name(&UserViewSchemaTests::name) == "name")); + } +} +#endif diff --git a/tests/statement_serializer_tests/schema/view.cpp b/tests/statement_serializer_tests/schema/view.cpp new file mode 100644 index 000000000..82f7d4b0f --- /dev/null +++ b/tests/statement_serializer_tests/schema/view.cpp @@ -0,0 +1,43 @@ +#include +#include + +#ifdef SQLITE_ORM_WITH_VIEW +using namespace sqlite_orm; +using internal::serialize; + +struct UserViewSerializerTests { + int id = 0; + std::string name; +}; + +TEST_CASE("statement_serializer view_t") { + struct User { + int id = 0; + std::string name; + }; + + auto table = make_table("user", make_column("id", &User::id), make_column("name", &User::name)); + auto view = make_view("user_view", select(columns(&User::id, &User::name))); + using db_objects_t = internal::db_objects_tuple; + auto dbObjects = db_objects_t{table, view}; + using context_t = internal::serializer_context; + context_t context{dbObjects}; + + SECTION("create") { + std::string value = serialize(view, context); + REQUIRE(value == R"(CREATE VIEW "user_view" ("id", "name") AS SELECT "user"."id", "user"."name" FROM "user")"); + } + SECTION("as object") { + auto expression = select(object()); + expression.highest_level = true; + std::string value = serialize(expression, context); + REQUIRE(value == R"(SELECT "user_view".* FROM "user_view")"); + } + SECTION("asterisk") { + auto expression = select(asterisk()); + expression.highest_level = true; + std::string value = serialize(expression, context); + REQUIRE(value == R"(SELECT "user_view".* FROM "user_view")"); + } +} +#endif diff --git a/tests/statement_serializer_tests/schema/virtual_table.cpp b/tests/statement_serializer_tests/schema/virtual_table.cpp index e64b4d89b..cfa3f63d4 100644 --- a/tests/statement_serializer_tests/schema/virtual_table.cpp +++ b/tests/statement_serializer_tests/schema/virtual_table.cpp @@ -13,5 +13,5 @@ TEST_CASE("statement_serializer FTS5") { auto node = make_virtual_table("posts", using_fts5(make_column("title", &Post::title), make_column("body", &Post::body))); auto value = serialize(node, context); - REQUIRE(value == "CREATE VIRTUAL TABLE IF NOT EXISTS \"posts\" USING FTS5(\"title\", \"body\")"); + REQUIRE(value == R"(CREATE VIRTUAL TABLE IF NOT EXISTS "posts" USING FTS5("title", "body"))"); } diff --git a/tests/static_tests/view_static_tests.cpp b/tests/static_tests/view_static_tests.cpp new file mode 100644 index 000000000..eea6fd141 --- /dev/null +++ b/tests/static_tests/view_static_tests.cpp @@ -0,0 +1,24 @@ +#include +#include + +#ifdef SQLITE_ORM_WITH_VIEW +using namespace sqlite_orm; +using internal::is_column; + +struct UserViewStaticTests { + int id = 0; + std::string name; +}; + +TEST_CASE("view static count_of()") { + struct User { + int id = 0; + std::string name; + }; + + { + auto view = make_view("user_view", select(columns(&User::id, &User::name))); + STATIC_REQUIRE(view.count_of() == 2); + } +} +#endif diff --git a/tests/table_name_collector.cpp b/tests/table_name_collector.cpp index 3a870392a..738cc6bcb 100644 --- a/tests/table_name_collector.cpp +++ b/tests/table_name_collector.cpp @@ -46,6 +46,22 @@ TEST_CASE("table name collector") { expected.emplace(table.name, ""); iterate_ast(expression, collector); } + SECTION("asterisk") { + auto expression = asterisk(); + expected.emplace(table.name, ""); + iterate_ast(expression, collector); + } + SECTION("object") { + auto expression = object(); + expected.emplace(table.name, ""); + iterate_ast(expression, collector); + } + SECTION("aliased asterisk") { + using als = alias_z; + auto expression = asterisk(); + expected.emplace(table.name, "z"); + iterate_ast(expression, collector); + } REQUIRE(collector.table_names == expected); } diff --git a/tests/tests5.cpp b/tests/tests5.cpp index 2f1c060e3..051d4658f 100644 --- a/tests/tests5.cpp +++ b/tests/tests5.cpp @@ -31,11 +31,11 @@ TEST_CASE("Iterate blob") { TestComparator testComparator; for(auto& obj: db.iterate()) { REQUIRE(testComparator(obj, v)); - } // test that view_t and iterator_t compile + } // test that object_view and iterator_t compile for(const auto& obj: db.iterate()) { REQUIRE(testComparator(obj, v)); - } // test that view_t and iterator_t compile + } // test that object_view and iterator_t compile { auto keysCount = db.count(where(c(&Test::key) == key)); From 89b4805cf6bd4709abda210c922ba0d9d7030743 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 24 Sep 2025 20:48:44 +0300 Subject: [PATCH 02/49] Renamed `storage_t<>::sync_table()` -> `storage_t<>::sync_dbo()` --- dev/implementations/storage_definitions.h | 6 +++--- dev/storage.h | 10 +++++----- include/sqlite_orm/sqlite_orm.h | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/dev/implementations/storage_definitions.h b/dev/implementations/storage_definitions.h index 2bb22103f..74a180141 100644 --- a/dev/implementations/storage_definitions.h +++ b/dev/implementations/storage_definitions.h @@ -24,9 +24,9 @@ namespace sqlite_orm { namespace internal { template template> - sync_schema_result storage_t::sync_table([[maybe_unused]] const Table& table, - [[maybe_unused]] sqlite3* db, - [[maybe_unused]] bool preserve) { + sync_schema_result storage_t::sync_dbo([[maybe_unused]] const Table& table, + [[maybe_unused]] sqlite3* db, + [[maybe_unused]] bool preserve) { if constexpr ( #ifdef SQLITE_ENABLE_DBSTAT_VTAB std::is_same, dbstat>::value || diff --git a/dev/storage.h b/dev/storage.h index e4f39cca5..080d4d577 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -1214,7 +1214,7 @@ namespace sqlite_orm { } template - sync_schema_result sync_table(const virtual_table_t& virtualTable, sqlite3* db, bool) { + sync_schema_result sync_dbo(const virtual_table_t& virtualTable, sqlite3* db, bool) { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; @@ -1225,7 +1225,7 @@ namespace sqlite_orm { } template - sync_schema_result sync_table(const index_t& index, sqlite3* db, bool) { + sync_schema_result sync_dbo(const index_t& index, sqlite3* db, bool) { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; @@ -1236,7 +1236,7 @@ namespace sqlite_orm { } template - sync_schema_result sync_table(const trigger_t& trigger, sqlite3* db, bool) { + sync_schema_result sync_dbo(const trigger_t& trigger, sqlite3* db, bool) { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; // TODO Change accordingly @@ -1247,7 +1247,7 @@ namespace sqlite_orm { } template = true> - sync_schema_result sync_table(const Table& table, sqlite3* db, bool preserve); + sync_schema_result sync_dbo(const Table& table, sqlite3* db, bool preserve); template = true> sync_schema_result sync_regular_table(const Table& table, sqlite3* db, bool preserve); @@ -1353,7 +1353,7 @@ namespace sqlite_orm { auto con = this->get_connection(); std::map result; iterate_tuple(this->db_objects, [this, db = con.get(), preserve, &result](auto& schemaObject) { - sync_schema_result status = this->sync_table(schemaObject, db, preserve); + sync_schema_result status = this->sync_dbo(schemaObject, db, preserve); result.emplace(schemaObject.name, status); }); return result; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 71de38c8b..e380176fc 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -24710,7 +24710,7 @@ namespace sqlite_orm { } template - sync_schema_result sync_table(const virtual_table_t& virtualTable, sqlite3* db, bool) { + sync_schema_result sync_dbo(const virtual_table_t& virtualTable, sqlite3* db, bool) { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; @@ -24721,7 +24721,7 @@ namespace sqlite_orm { } template - sync_schema_result sync_table(const index_t& index, sqlite3* db, bool) { + sync_schema_result sync_dbo(const index_t& index, sqlite3* db, bool) { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; @@ -24732,7 +24732,7 @@ namespace sqlite_orm { } template - sync_schema_result sync_table(const trigger_t& trigger, sqlite3* db, bool) { + sync_schema_result sync_dbo(const trigger_t& trigger, sqlite3* db, bool) { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; // TODO Change accordingly @@ -24743,7 +24743,7 @@ namespace sqlite_orm { } template = true> - sync_schema_result sync_table(const Table& table, sqlite3* db, bool preserve); + sync_schema_result sync_dbo(const Table& table, sqlite3* db, bool preserve); template = true> sync_schema_result sync_regular_table(const Table& table, sqlite3* db, bool preserve); @@ -24849,7 +24849,7 @@ namespace sqlite_orm { auto con = this->get_connection(); std::map result; iterate_tuple(this->db_objects, [this, db = con.get(), preserve, &result](auto& schemaObject) { - sync_schema_result status = this->sync_table(schemaObject, db, preserve); + sync_schema_result status = this->sync_dbo(schemaObject, db, preserve); result.emplace(schemaObject.name, status); }); return result; @@ -25798,9 +25798,9 @@ namespace sqlite_orm { namespace internal { template template> - sync_schema_result storage_t::sync_table([[maybe_unused]] const Table& table, - [[maybe_unused]] sqlite3* db, - [[maybe_unused]] bool preserve) { + sync_schema_result storage_t::sync_dbo([[maybe_unused]] const Table& table, + [[maybe_unused]] sqlite3* db, + [[maybe_unused]] bool preserve) { if constexpr ( #ifdef SQLITE_ENABLE_DBSTAT_VTAB std::is_same, dbstat>::value || From 8fc85063904de2a8bcb638c6489834f34d62e549 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 24 Sep 2025 20:53:06 +0300 Subject: [PATCH 03/49] Used `c_str()` instead of `data()` for executing SQL string `c_str()` expresses the intent better than `data()` --- dev/storage.h | 6 +++--- dev/storage_base.h | 12 ++++++------ include/sqlite_orm/sqlite_orm.h | 18 +++++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev/storage.h b/dev/storage.h index 080d4d577..f7ef42d95 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -1220,7 +1220,7 @@ namespace sqlite_orm { const auto res = sync_schema_result::already_in_sync; context_t context{this->db_objects}; const auto sql = serialize(virtualTable, context); - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); return res; } @@ -1231,7 +1231,7 @@ namespace sqlite_orm { const auto res = sync_schema_result::already_in_sync; context_t context{this->db_objects}; const auto sql = serialize(index, context); - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); return res; } @@ -1242,7 +1242,7 @@ namespace sqlite_orm { const auto res = sync_schema_result::already_in_sync; // TODO Change accordingly context_t context{this->db_objects}; const auto sql = serialize(trigger, context); - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); return res; } diff --git a/dev/storage_base.h b/dev/storage_base.h index b60ede12a..ecb7417dc 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -156,7 +156,7 @@ namespace sqlite_orm { << streaming_identifier(newName) << std::flush; sql = ss.str(); } - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); } /** @@ -745,7 +745,7 @@ namespace sqlite_orm { void begin_transaction_internal(const std::string& sql) { this->connection->retain(); sqlite3* db = this->connection->get(); - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); } connection_ref get_connection() { @@ -781,7 +781,7 @@ namespace sqlite_orm { ss << "PRAGMA foreign_keys = " << value << std::flush; sql = ss.str(); } - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); } bool foreign_keys(sqlite3* db) { @@ -1004,7 +1004,7 @@ namespace sqlite_orm { ss << ' ' << streaming_identifier(tableName) << std::flush; sql = ss.str(); } - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); } void drop_index_internal(const std::string& indexName, bool ifExists) { @@ -1019,7 +1019,7 @@ namespace sqlite_orm { sql = ss.str(); } auto connection = this->get_connection(); - this->executor.perform_void_exec(connection.get(), sql.data()); + this->executor.perform_void_exec(connection.get(), sql.c_str()); } void drop_trigger_internal(const std::string& triggerName, bool ifExists) { @@ -1034,7 +1034,7 @@ namespace sqlite_orm { sql = ss.str(); } auto connection = this->get_connection(); - this->executor.perform_void_exec(connection.get(), sql.data()); + this->executor.perform_void_exec(connection.get(), sql.c_str()); } static int diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index e380176fc..bce4ae614 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -18454,7 +18454,7 @@ namespace sqlite_orm { << streaming_identifier(newName) << std::flush; sql = ss.str(); } - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); } /** @@ -19043,7 +19043,7 @@ namespace sqlite_orm { void begin_transaction_internal(const std::string& sql) { this->connection->retain(); sqlite3* db = this->connection->get(); - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); } connection_ref get_connection() { @@ -19079,7 +19079,7 @@ namespace sqlite_orm { ss << "PRAGMA foreign_keys = " << value << std::flush; sql = ss.str(); } - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); } bool foreign_keys(sqlite3* db) { @@ -19302,7 +19302,7 @@ namespace sqlite_orm { ss << ' ' << streaming_identifier(tableName) << std::flush; sql = ss.str(); } - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); } void drop_index_internal(const std::string& indexName, bool ifExists) { @@ -19317,7 +19317,7 @@ namespace sqlite_orm { sql = ss.str(); } auto connection = this->get_connection(); - this->executor.perform_void_exec(connection.get(), sql.data()); + this->executor.perform_void_exec(connection.get(), sql.c_str()); } void drop_trigger_internal(const std::string& triggerName, bool ifExists) { @@ -19332,7 +19332,7 @@ namespace sqlite_orm { sql = ss.str(); } auto connection = this->get_connection(); - this->executor.perform_void_exec(connection.get(), sql.data()); + this->executor.perform_void_exec(connection.get(), sql.c_str()); } static int @@ -24716,7 +24716,7 @@ namespace sqlite_orm { const auto res = sync_schema_result::already_in_sync; context_t context{this->db_objects}; const auto sql = serialize(virtualTable, context); - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); return res; } @@ -24727,7 +24727,7 @@ namespace sqlite_orm { const auto res = sync_schema_result::already_in_sync; context_t context{this->db_objects}; const auto sql = serialize(index, context); - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); return res; } @@ -24738,7 +24738,7 @@ namespace sqlite_orm { const auto res = sync_schema_result::already_in_sync; // TODO Change accordingly context_t context{this->db_objects}; const auto sql = serialize(trigger, context); - this->executor.perform_void_exec(db, sql.data()); + this->executor.perform_void_exec(db, sql.c_str()); return res; } From 3627014a74599157ea6979987769fb36463bc134 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 24 Sep 2025 20:56:06 +0300 Subject: [PATCH 04/49] Added SQL view to the schema --- dev/object_from_column_builder.h | 4 +- dev/schema/view.h | 17 ++++- dev/select_constraints.h | 10 +++ dev/storage.h | 21 ++++++ dev/storage_base.h | 9 +++ include/sqlite_orm/sqlite_orm.h | 62 +++++++++++++++-- tests/logger_tests.cpp | 10 ++- tests/prepared_statement_tests/update.cpp | 1 - tests/schema/table_tests.cpp | 1 + tests/static_tests/view_static_tests.cpp | 9 +++ tests/view_tests.cpp | 82 +++++++++++++++++++++++ 11 files changed, 216 insertions(+), 10 deletions(-) create mode 100644 tests/view_tests.cpp diff --git a/dev/object_from_column_builder.h b/dev/object_from_column_builder.h index 2ba11abde..e4d55066f 100644 --- a/dev/object_from_column_builder.h +++ b/dev/object_from_column_builder.h @@ -57,8 +57,8 @@ namespace sqlite_orm { const auto rowExtractor = row_value_extractor(); auto value = rowExtractor.extract(this->stmt, ++this->columnIndex); // calculate absolute address of member from relative address - field_type* field = reinterpret_cast(reinterpret_cast(&object) + - reinterpret_cast(column.member_pointer)); + const std::byte* fieldAddress = (std::byte*)(size_t(&object) + size_t(column.member_pointer.field)); + field_type* field = (field_type*)fieldAddress; *field = std::move(value); } #endif diff --git a/dev/schema/view.h b/dev/schema/view.h index a97492423..40acd3248 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -1,4 +1,5 @@ #pragma once +#ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_WITH_VIEW #include // std::remove_cvref #include // std::forward, std::move, std::index_sequence, std::make_index_sequence @@ -6,6 +7,7 @@ #include // offsetof #include #include "../functional/cxx_universal.h" // ::size_t +#endif #include "../column_pointer.h" #include "../select_constraints.h" #include "column.h" @@ -48,6 +50,13 @@ namespace sqlite_orm::internal { template decltype(auto) get_cte_driving_subselect(const Select& select); +#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + decltype(auto) get_cte_driving_subselect(const with_t& select) { + return get_cte_driving_subselect(select.expression); + } +#endif + /** * Factory function for a column definition from a relative pointer to an object of the object to be mapped. */ @@ -150,9 +159,13 @@ namespace sqlite_orm::internal { SQLITE_ORM_EXPORT namespace sqlite_orm { template auto make_view(std::string name, Select select) { - static_assert(polyfill::conjunction_v>, "You must specify a select statement"); + using namespace ::sqlite_orm::internal; + static_assert(polyfill::disjunction_v, is_with_clause) { + select.highest_level = true; + } return internal::make_view(std::move(name), std::make_index_sequence>{}, std::move(select)); diff --git a/dev/select_constraints.h b/dev/select_constraints.h index 2d74ece6c..150076dd4 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -318,8 +318,18 @@ namespace sqlite_orm { } } }; + + template + inline constexpr bool is_with_clause_v = polyfill::is_specialization_of::value; +#else + + template + inline constexpr bool is_with_clause_v = false; #endif + template + using is_with_clause = polyfill::bool_constant>; + template struct asterisk_t { using type = T; diff --git a/dev/storage.h b/dev/storage.h index f7ef42d95..0bef204ec 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -58,6 +58,7 @@ #include "object_from_column_builder.h" #include "row_extractor.h" #include "schema/table.h" +#include "schema/view.h" #include "schema/column.h" #include "schema/index.h" #include "cte_storage.h" @@ -1126,6 +1127,13 @@ namespace sqlite_orm { return sync_schema_result::already_in_sync; } +#ifdef SQLITE_ORM_WITH_VIEW + template + sync_schema_result schema_status(const view_t&, sqlite3*, bool, bool*) { + return sync_schema_result::already_in_sync; + } +#endif + template sync_schema_result schema_status(const table_t& table, sqlite3* db, @@ -1246,6 +1254,19 @@ namespace sqlite_orm { return res; } +#ifdef SQLITE_ORM_WITH_VIEW + template + sync_schema_result sync_dbo(const view_t& view, sqlite3* db, bool) { + using context_t = serializer_context; + + const auto res = sync_schema_result::already_in_sync; + context_t context{this->db_objects}; + const auto sql = serialize(view, context); + this->executor.perform_void_exec(db, sql.c_str()); + return res; + } +#endif + template = true> sync_schema_result sync_dbo(const Table& table, sqlite3* db, bool preserve); diff --git a/dev/storage_base.h b/dev/storage_base.h index ecb7417dc..6a6db4257 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -270,6 +270,15 @@ namespace sqlite_orm { } #endif + /** + * Returns the names of existing permanent view in the database. Doesn't check storage itself - works only with + * actual database. + * @return Returns list of tables in database. + */ + std::vector view_names() { + return this->object_names("view"); + } + /** * Returns existing permanent table names in database. Doesn't check storage itself - works only with * actual database. diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index bce4ae614..0bc08aa67 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -9144,8 +9144,18 @@ namespace sqlite_orm { } } }; + + template + inline constexpr bool is_with_clause_v = polyfill::is_specialization_of::value; +#else + + template + inline constexpr bool is_with_clause_v = false; #endif + template + using is_with_clause = polyfill::bool_constant>; + template struct asterisk_t { using type = T; @@ -13949,8 +13959,8 @@ namespace sqlite_orm { const auto rowExtractor = row_value_extractor(); auto value = rowExtractor.extract(this->stmt, ++this->columnIndex); // calculate absolute address of member from relative address - field_type* field = reinterpret_cast(reinterpret_cast(&object) + - reinterpret_cast(column.member_pointer)); + const std::byte* fieldAddress = (std::byte*)(size_t(&object) + size_t(column.member_pointer.field)); + field_type* field = (field_type*)fieldAddress; *field = std::move(value); } #endif @@ -18568,6 +18578,15 @@ namespace sqlite_orm { } #endif + /** + * Returns the names of existing permanent view in the database. Doesn't check storage itself - works only with + * actual database. + * @return Returns list of tables in database. + */ + std::vector view_names() { + return this->object_names("view"); + } + /** * Returns existing permanent table names in database. Doesn't check storage itself - works only with * actual database. @@ -20397,6 +20416,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "schema/view.h" +#ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_WITH_VIEW #include // std::remove_cvref #include // std::forward, std::move, std::index_sequence, std::make_index_sequence @@ -20405,6 +20425,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #include // #include "../functional/cxx_universal.h" // ::size_t +#endif // #include "../column_pointer.h" // #include "../select_constraints.h" @@ -20450,6 +20471,13 @@ namespace sqlite_orm::internal { template decltype(auto) get_cte_driving_subselect(const Select& select); +#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) + template + decltype(auto) get_cte_driving_subselect(const with_t& select) { + return get_cte_driving_subselect(select.expression); + } +#endif + /** * Factory function for a column definition from a relative pointer to an object of the object to be mapped. */ @@ -20552,9 +20580,13 @@ namespace sqlite_orm::internal { SQLITE_ORM_EXPORT namespace sqlite_orm { template auto make_view(std::string name, Select select) { - static_assert(polyfill::conjunction_v>, "You must specify a select statement"); + using namespace ::sqlite_orm::internal; + static_assert(polyfill::disjunction_v, is_with_clause) { + select.highest_level = true; + } return internal::make_view(std::move(name), std::make_index_sequence>{}, std::move(select)); @@ -23019,6 +23051,8 @@ namespace sqlite_orm { // #include "schema/table.h" +// #include "schema/view.h" + // #include "schema/column.h" // #include "schema/index.h" @@ -24622,6 +24656,13 @@ namespace sqlite_orm { return sync_schema_result::already_in_sync; } +#ifdef SQLITE_ORM_WITH_VIEW + template + sync_schema_result schema_status(const view_t&, sqlite3*, bool, bool*) { + return sync_schema_result::already_in_sync; + } +#endif + template sync_schema_result schema_status(const table_t& table, sqlite3* db, @@ -24742,6 +24783,19 @@ namespace sqlite_orm { return res; } +#ifdef SQLITE_ORM_WITH_VIEW + template + sync_schema_result sync_dbo(const view_t& view, sqlite3* db, bool) { + using context_t = serializer_context; + + const auto res = sync_schema_result::already_in_sync; + context_t context{this->db_objects}; + const auto sql = serialize(view, context); + this->executor.perform_void_exec(db, sql.c_str()); + return res; + } +#endif + template = true> sync_schema_result sync_dbo(const Table& table, sqlite3* db, bool preserve); diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index a6c1724e7..1fd5cf9b4 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -219,10 +219,18 @@ TEST_CASE("logger") { SECTION("db_release_memory") { std::ignore = storage.db_release_memory(); } - SECTION("table_names") { + SECTION("trigger_names") { std::ignore = storage.table_names(); pushExpected("SELECT name FROM sqlite_master WHERE type='table'"); } + SECTION("view_names") { + std::ignore = storage.view_names(); + pushExpected("SELECT name FROM sqlite_master WHERE type='view'"); + } + SECTION("table_names") { + std::ignore = storage.trigger_names(); + pushExpected("SELECT name FROM sqlite_master WHERE type='trigger'"); + } SECTION("open_forever") { storage.open_forever(); } diff --git a/tests/prepared_statement_tests/update.cpp b/tests/prepared_statement_tests/update.cpp index bb7b61936..0667becdc 100644 --- a/tests/prepared_statement_tests/update.cpp +++ b/tests/prepared_statement_tests/update.cpp @@ -8,7 +8,6 @@ using namespace sqlite_orm; TEST_CASE("Prepared update") { using namespace PreparedStatementTests; - using Catch::Matchers::UnorderedEquals; const int defaultVisitTime = 50; diff --git a/tests/schema/table_tests.cpp b/tests/schema/table_tests.cpp index 053f25383..e0693cfe9 100644 --- a/tests/schema/table_tests.cpp +++ b/tests/schema/table_tests.cpp @@ -26,6 +26,7 @@ TEST_CASE("table::find_column_name") { make_column("country_code", &Contact::countryCode), make_column("phone_number", &Contact::phoneNumber), make_column("visits_count", &Contact::visitsCount)); + STATIC_REQUIRE(table.count_of() == 6); REQUIRE(*table.find_column_name(&Contact::id) == "contact_id"); REQUIRE(*table.find_column_name(&Contact::firstName) == "first_name"); REQUIRE(*table.find_column_name(&Contact::lastName) == "last_name"); diff --git a/tests/static_tests/view_static_tests.cpp b/tests/static_tests/view_static_tests.cpp index eea6fd141..b5fc74e10 100644 --- a/tests/static_tests/view_static_tests.cpp +++ b/tests/static_tests/view_static_tests.cpp @@ -9,6 +9,9 @@ struct UserViewStaticTests { int id = 0; std::string name; }; +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +constexpr orm_table_reference auto user_view = c(); +#endif TEST_CASE("view static count_of()") { struct User { @@ -20,5 +23,11 @@ TEST_CASE("view static count_of()") { auto view = make_view("user_view", select(columns(&User::id, &User::name))); STATIC_REQUIRE(view.count_of() == 2); } +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + { + auto view = make_view("user_view", select(columns(&User::id, &User::name))); + STATIC_REQUIRE(view.count_of() == 2); + } +#endif } #endif diff --git a/tests/view_tests.cpp b/tests/view_tests.cpp new file mode 100644 index 000000000..62da57c2c --- /dev/null +++ b/tests/view_tests.cpp @@ -0,0 +1,82 @@ +#include +#include + +#ifdef SQLITE_ORM_WITH_VIEW +using namespace sqlite_orm; + +struct UserViewTests { + int id = 0; + std::string name; + +#ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED + bool operator==(const UserViewTests&) const = default; +#else + bool operator==(const UserViewTests& right) const { + return id == right.id && name == right.name; + } +#endif +}; + +TEST_CASE("sql view") { + using Catch::Matchers::UnorderedEquals; + + struct User { + int id = 0; + std::string name; + }; + + SECTION("normal") { + auto storage = make_storage( + "", + make_table("user", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), + make_view("user_view", select(columns(&User::id, &User::name)))); + + storage.sync_schema(); + + storage.transaction([&storage] { + storage.insert({0, "name"}); + return true; + }); + + SECTION("created view") { + auto viewNames = storage.view_names(); + REQUIRE_THAT(viewNames, UnorderedEquals({"user_view"})); + } + SECTION("view select") { + auto users = storage.select(object()); + REQUIRE_THAT(users, UnorderedEquals({{1, "name"}})); + } + } +#if 0 + SECTION("view with CTE") { +#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + constexpr orm_cte_moniker auto users_cte = "users"_cte; + auto storage = make_storage( + "", + make_table("user", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), + make_view("user_view", + with(users_cte().as(select(asterisk())), + select(columns(users_cte->*&User::id, users_cte->*&User::name))))); + + storage.sync_schema(); + + storage.transaction([&storage] { + storage.insert({0, "name"}); + return true; + }); + + SECTION("created view") { + auto viewNames = storage.view_names(); + REQUIRE_THAT(viewNames, UnorderedEquals({"user_view"})); + } + SECTION("view select") { + auto users = storage.select(object()); + REQUIRE_THAT(users, UnorderedEquals({{1, "name"}})); + } +#endif +#endif + } +#endif +} +#endif From 759aa241c8db726ca0f6708a4f680ab5a307932a Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Wed, 24 Sep 2025 23:00:08 +0300 Subject: [PATCH 05/49] Guarded availability of the SQL view feature Note: C++ reflection-based approach isn't implemented yet. --- dev/functional/config.h | 3 +- dev/object_from_column_builder.h | 4 +-- dev/schema/view.h | 49 ++++++++++++++++++--------- include/sqlite_orm/sqlite_orm.h | 57 +++++++++++++++++++++----------- 4 files changed, 76 insertions(+), 37 deletions(-) diff --git a/dev/functional/config.h b/dev/functional/config.h index 6cb4ead87..98d2b03ac 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -80,7 +80,8 @@ // note: PFR depends on `SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED` for field name determination #if (defined(SQLITE_ORM_CONSTEVAL_SUPPORTED) && defined(SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED)) && \ - (__cpp_lib_byte >= 201603L && __cpp_lib_remove_cvref >= 201711L) + (__cpp_lib_byte >= 201603L && __cpp_lib_remove_cvref >= 201711L) && \ + (__cpp_impl_reflection >= 202500L || BOOST_PFR_ENABLED == 1) #define SQLITE_ORM_WITH_VIEW #endif diff --git a/dev/object_from_column_builder.h b/dev/object_from_column_builder.h index e4d55066f..e23bc56ab 100644 --- a/dev/object_from_column_builder.h +++ b/dev/object_from_column_builder.h @@ -4,7 +4,7 @@ #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include // std::is_member_object_pointer #include // std::move -#ifdef SQLITE_ORM_WITH_VIEW +#if defined(SQLITE_ORM_WITH_VIEW) && (BOOST_PFR_ENABLED == 1) #include // std::byte #endif #endif @@ -49,7 +49,7 @@ namespace sqlite_orm { }; } -#ifdef SQLITE_ORM_WITH_VIEW +#if defined(SQLITE_ORM_WITH_VIEW) && (BOOST_PFR_ENABLED == 1) template requires (is_column_pointer_v) void operator()(const column_field& column) { diff --git a/dev/schema/view.h b/dev/schema/view.h index 40acd3248..d18b7e898 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -5,14 +5,19 @@ #include // std::forward, std::move, std::index_sequence, std::make_index_sequence #include // std::byte #include // offsetof -#include -#include "../functional/cxx_universal.h" // ::size_t +#if __cpp_impl_reflection >= 202500L +#include #endif +#endif +#if __cpp_impl_reflection < 202500L && BOOST_PFR_ENABLED == 1 #include "../column_pointer.h" +#endif #include "../select_constraints.h" #include "column.h" #include "mapped_object.h" +#if __cpp_impl_reflection >= 202500L +#elif BOOST_PFR_ENABLED == 1 namespace boost::pfr { namespace detail { namespace sequence_tuple { @@ -45,8 +50,22 @@ namespace boost::pfr { return detail::sequence_tuple::get_nth_relative_address(); } } +#endif namespace sqlite_orm::internal { + /** + * View definition, mapping an aggregate object type to a corresponding select statement. + */ + template + struct view_t : mapped_object_t { + using base_type = mapped_object_t; + using object_type = typename base_type::object_type; + using elements_type = typename base_type::elements_type; + using select_type = Select; + + select_type select; + }; + template decltype(auto) get_cte_driving_subselect(const Select& select); @@ -56,7 +75,11 @@ namespace sqlite_orm::internal { return get_cte_driving_subselect(select.expression); } #endif +} +#if __cpp_impl_reflection >= 202500L +#elif BOOST_PFR_ENABLED == 1 +namespace sqlite_orm::internal { /** * Factory function for a column definition from a relative pointer to an object of the object to be mapped. */ @@ -72,6 +95,12 @@ namespace sqlite_orm::internal { return {std::move(name), relativeField, {}, std::tuple{std::move(constraints)...}}); } + /* + * A column field carrying a relative address to a member of an object. + * + * Internal note: According to my tests msvc or compilers in general have a hard time to use pointer-to-members at compile-time. + * That's why we use a relative address. + */ template struct column_field, empty_setter> { using member_pointer_t = F O::*; @@ -107,19 +136,6 @@ namespace sqlite_orm::internal { return relative.field == &(object->*m); } - /** - * View definition, mapping an aggregate object type to a corresponding select statement. - */ - template - struct view_t : mapped_object_t { - using base_type = mapped_object_t; - using object_type = typename base_type::object_type; - using elements_type = typename base_type::elements_type; - using select_type = Select; - - select_type select; - }; - template using columns_size_t = std::tuple_size; @@ -170,7 +186,10 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { std::make_index_sequence>{}, std::move(select)); } +} +#endif +SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Factory function for a view definition. diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 0bc08aa67..ee07469e7 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -311,7 +311,8 @@ using std::nullptr_t; // note: PFR depends on `SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED` for field name determination #if (defined(SQLITE_ORM_CONSTEVAL_SUPPORTED) && defined(SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED)) && \ - (__cpp_lib_byte >= 201603L && __cpp_lib_remove_cvref >= 201711L) + (__cpp_lib_byte >= 201603L && __cpp_lib_remove_cvref >= 201711L) && \ + (__cpp_impl_reflection >= 202500L || BOOST_PFR_ENABLED == 1) #define SQLITE_ORM_WITH_VIEW #endif @@ -13900,7 +13901,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include // std::is_member_object_pointer #include // std::move -#ifdef SQLITE_ORM_WITH_VIEW +#if defined(SQLITE_ORM_WITH_VIEW) && (BOOST_PFR_ENABLED == 1) #include // std::byte #endif #endif @@ -13951,7 +13952,7 @@ namespace sqlite_orm { }; } -#ifdef SQLITE_ORM_WITH_VIEW +#if defined(SQLITE_ORM_WITH_VIEW) && (BOOST_PFR_ENABLED == 1) template requires (is_column_pointer_v) void operator()(const column_field& column) { @@ -20422,18 +20423,22 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #include // std::forward, std::move, std::index_sequence, std::make_index_sequence #include // std::byte #include // offsetof -#include -// #include "../functional/cxx_universal.h" -// ::size_t +#if __cpp_impl_reflection >= 202500L +#include +#endif #endif +#if __cpp_impl_reflection < 202500L && BOOST_PFR_ENABLED == 1 // #include "../column_pointer.h" +#endif // #include "../select_constraints.h" // #include "column.h" // #include "mapped_object.h" +#if __cpp_impl_reflection >= 202500L +#elif BOOST_PFR_ENABLED == 1 namespace boost::pfr { namespace detail { namespace sequence_tuple { @@ -20466,8 +20471,22 @@ namespace boost::pfr { return detail::sequence_tuple::get_nth_relative_address(); } } +#endif namespace sqlite_orm::internal { + /** + * View definition, mapping an aggregate object type to a corresponding select statement. + */ + template + struct view_t : mapped_object_t { + using base_type = mapped_object_t; + using object_type = typename base_type::object_type; + using elements_type = typename base_type::elements_type; + using select_type = Select; + + select_type select; + }; + template decltype(auto) get_cte_driving_subselect(const Select& select); @@ -20477,7 +20496,11 @@ namespace sqlite_orm::internal { return get_cte_driving_subselect(select.expression); } #endif +} +#if __cpp_impl_reflection >= 202500L +#elif BOOST_PFR_ENABLED == 1 +namespace sqlite_orm::internal { /** * Factory function for a column definition from a relative pointer to an object of the object to be mapped. */ @@ -20493,6 +20516,12 @@ namespace sqlite_orm::internal { return {std::move(name), relativeField, {}, std::tuple{std::move(constraints)...}}); } + /* + * A column field carrying a relative address to a member of an object. + * + * Internal note: According to my tests msvc or compilers in general have a hard time to use pointer-to-members at compile-time. + * That's why we use a relative address. + */ template struct column_field, empty_setter> { using member_pointer_t = F O::*; @@ -20528,19 +20557,6 @@ namespace sqlite_orm::internal { return relative.field == &(object->*m); } - /** - * View definition, mapping an aggregate object type to a corresponding select statement. - */ - template - struct view_t : mapped_object_t { - using base_type = mapped_object_t; - using object_type = typename base_type::object_type; - using elements_type = typename base_type::elements_type; - using select_type = Select; - - select_type select; - }; - template using columns_size_t = std::tuple_size; @@ -20591,7 +20607,10 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { std::make_index_sequence>{}, std::move(select)); } +} +#endif +SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Factory function for a view definition. From 5f9b0c7cc0b6d791f0feb053da16eaf317e5ce29 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 25 Sep 2025 16:38:42 +0300 Subject: [PATCH 06/49] Alias template that filters columns of a table --- dev/schema/column.h | 3 + dev/schema/mapped_object.h | 3 +- dev/schema/table.h | 6 +- dev/storage.h | 4 +- dev/storage_impl.h | 2 +- include/sqlite_orm/sqlite_orm.h | 18 +++--- tests/static_tests/table_static_tests.cpp | 79 +++++++++++++++++++---- tests/static_tests/view_static_tests.cpp | 11 +++- 8 files changed, 95 insertions(+), 31 deletions(-) diff --git a/dev/schema/column.h b/dev/schema/column.h index 4d7b02a87..9f1750ff0 100644 --- a/dev/schema/column.h +++ b/dev/schema/column.h @@ -118,6 +118,9 @@ namespace sqlite_orm { template using is_column = polyfill::bool_constant>; + template + using col_index_sequence_of = filter_tuple_sequence_t; + template using col_index_sequence_with_field_type = filter_tuple_sequence_t void for_each_column(L&& lambda) const { - using col_index_sequence = filter_tuple_sequence_t; - iterate_tuple(this->elements, col_index_sequence{}, lambda); + iterate_tuple(this->elements, col_index_sequence_of{}, lambda); } /** diff --git a/dev/schema/table.h b/dev/schema/table.h index 9ba65023e..a34a7db28 100644 --- a/dev/schema/table.h +++ b/dev/schema/table.h @@ -257,8 +257,7 @@ namespace sqlite_orm { */ template void for_each_column(L&& lambda) const { - using col_index_sequence = filter_tuple_sequence_t; - iterate_tuple(this->elements, col_index_sequence{}, lambda); + iterate_tuple(this->elements, col_index_sequence_of{}, lambda); } /** @@ -372,8 +371,7 @@ namespace sqlite_orm { */ template void for_each_column(L&& lambda) const { - using col_index_sequence = filter_tuple_sequence_t; - iterate_tuple(this->columns, col_index_sequence{}, lambda); + iterate_tuple(this->columns, col_index_sequence_of{}, lambda); } template = true> diff --git a/dev/storage.h b/dev/storage.h index 0bef204ec..873d02be7 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -222,7 +222,7 @@ namespace sqlite_orm { void assert_updatable_type() const { using table_type = storage_pick_table_t; using elements_type = elements_type_t; - using col_index_sequence = filter_tuple_sequence_t; + using column_index_sequence = col_index_sequence_of; using pk_index_sequence = filter_tuple_sequence_t; using pkcol_index_sequence = col_index_sequence_with; constexpr size_t dedicatedPrimaryKeyColumnsCount = @@ -230,7 +230,7 @@ namespace sqlite_orm { constexpr size_t primaryKeyColumnsCount = dedicatedPrimaryKeyColumnsCount + pkcol_index_sequence::size(); - constexpr ptrdiff_t nonPrimaryKeysColumnsCount = col_index_sequence::size() - primaryKeyColumnsCount; + constexpr ptrdiff_t nonPrimaryKeysColumnsCount = column_index_sequence::size() - primaryKeyColumnsCount; static_assert(primaryKeyColumnsCount > 0, "A table without primary keys cannot be updated"); static_assert( nonPrimaryKeysColumnsCount > 0, diff --git a/dev/storage_impl.h b/dev/storage_impl.h index 27e51c72e..e35c7a8d9 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -103,7 +103,7 @@ namespace sqlite_orm { const column_pointer>&) { using table_type = storage_pick_table_t; using cte_colrefs_tuple = typename cte_mapper_type_t::final_colrefs_tuple; - using column_index_sequence = filter_tuple_sequence_t, is_column>; + using column_index_sequence = col_index_sequence_of>; // note: even though the columns contain the [`aliased_field<>::*`] we perform the lookup using the column references. // lookup ColAlias in the final column references diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index ee07469e7..bf4246568 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -8777,6 +8777,9 @@ namespace sqlite_orm { template using is_column = polyfill::bool_constant>; + template + using col_index_sequence_of = filter_tuple_sequence_t; + template using col_index_sequence_with_field_type = filter_tuple_sequence_t void for_each_column(L&& lambda) const { - using col_index_sequence = filter_tuple_sequence_t; - iterate_tuple(this->elements, col_index_sequence{}, lambda); + iterate_tuple(this->elements, col_index_sequence_of{}, lambda); } /** @@ -12797,8 +12799,7 @@ namespace sqlite_orm { */ template void for_each_column(L&& lambda) const { - using col_index_sequence = filter_tuple_sequence_t; - iterate_tuple(this->elements, col_index_sequence{}, lambda); + iterate_tuple(this->elements, col_index_sequence_of{}, lambda); } /** @@ -12912,8 +12913,7 @@ namespace sqlite_orm { */ template void for_each_column(L&& lambda) const { - using col_index_sequence = filter_tuple_sequence_t; - iterate_tuple(this->columns, col_index_sequence{}, lambda); + iterate_tuple(this->columns, col_index_sequence_of{}, lambda); } template = true> @@ -13117,7 +13117,7 @@ namespace sqlite_orm { const column_pointer>&) { using table_type = storage_pick_table_t; using cte_colrefs_tuple = typename cte_mapper_type_t::final_colrefs_tuple; - using column_index_sequence = filter_tuple_sequence_t, is_column>; + using column_index_sequence = col_index_sequence_of>; // note: even though the columns contain the [`aliased_field<>::*`] we perform the lookup using the column references. // lookup ColAlias in the final column references @@ -23770,7 +23770,7 @@ namespace sqlite_orm { void assert_updatable_type() const { using table_type = storage_pick_table_t; using elements_type = elements_type_t; - using col_index_sequence = filter_tuple_sequence_t; + using column_index_sequence = col_index_sequence_of; using pk_index_sequence = filter_tuple_sequence_t; using pkcol_index_sequence = col_index_sequence_with; constexpr size_t dedicatedPrimaryKeyColumnsCount = @@ -23778,7 +23778,7 @@ namespace sqlite_orm { constexpr size_t primaryKeyColumnsCount = dedicatedPrimaryKeyColumnsCount + pkcol_index_sequence::size(); - constexpr ptrdiff_t nonPrimaryKeysColumnsCount = col_index_sequence::size() - primaryKeyColumnsCount; + constexpr ptrdiff_t nonPrimaryKeysColumnsCount = column_index_sequence::size() - primaryKeyColumnsCount; static_assert(primaryKeyColumnsCount > 0, "A table without primary keys cannot be updated"); static_assert( nonPrimaryKeysColumnsCount > 0, diff --git a/tests/static_tests/table_static_tests.cpp b/tests/static_tests/table_static_tests.cpp index 7020a7969..d35d9318f 100644 --- a/tests/static_tests/table_static_tests.cpp +++ b/tests/static_tests/table_static_tests.cpp @@ -2,6 +2,8 @@ #include using namespace sqlite_orm; +using internal::col_index_sequence_of, internal::col_index_sequence_excluding, internal::col_index_sequence_with, + internal::col_index_sequence_with_field_type; using internal::is_column; using internal::is_primary_key; @@ -18,84 +20,139 @@ TEST_CASE("table static count_of()") { }; { // 1 column no pk auto table = make_table("users", make_column("id", &User::id)); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 1); STATIC_REQUIRE(table.count_of() == 0); STATIC_REQUIRE(table.count_of_columns_with() == 0); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); + STATIC_REQUIRE(col_index_sequence_of::size() == 1); + STATIC_REQUIRE(col_index_sequence_with::size() == 0); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } { // 1 column with 1 inline pk auto table = make_table("users", make_column("id", &User::id, primary_key())); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 1); STATIC_REQUIRE(table.count_of() == 0); STATIC_REQUIRE(table.count_of_columns_with() == 1); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); + STATIC_REQUIRE(col_index_sequence_of::size() == 1); + STATIC_REQUIRE(col_index_sequence_with::size() == 1); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 0); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } { // 1 column with 1 inline pk autoincrement auto table = make_table("users", make_column("id", &User::id, primary_key().autoincrement())); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 1); STATIC_REQUIRE(table.count_of() == 0); STATIC_REQUIRE(table.count_of_columns_with() == 1); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); + STATIC_REQUIRE(col_index_sequence_of::size() == 1); + STATIC_REQUIRE(col_index_sequence_with::size() == 1); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 0); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } { // 1 column with 1 dedicated pk auto table = make_table("users", make_column("id", &User::id), primary_key(&User::id)); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 1); STATIC_REQUIRE(table.count_of() == 1); STATIC_REQUIRE(table.count_of_columns_with() == 0); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); + STATIC_REQUIRE(col_index_sequence_of::size() == 1); + STATIC_REQUIRE(col_index_sequence_with::size() == 0); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); } { // 1 column with 1 dedicated pk autoincrement auto table = make_table("users", make_column("id", &User::id), primary_key(&User::id).autoincrement()); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 1); STATIC_REQUIRE(table.count_of() == 1); STATIC_REQUIRE(table.count_of_columns_with() == 0); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); + STATIC_REQUIRE(col_index_sequence_of::size() == 1); + STATIC_REQUIRE(col_index_sequence_with::size() == 0); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); } { // 2 columns no pk auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name)); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 2); STATIC_REQUIRE(table.count_of() == 0); STATIC_REQUIRE(table.count_of_columns_with() == 0); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); + STATIC_REQUIRE(col_index_sequence_of::size() == 2); + STATIC_REQUIRE(col_index_sequence_with::size() == 0); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 2); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } { // 2 columns with 1 inline id pk auto table = make_table("users", make_column("id", &User::id, primary_key()), make_column("id", &User::name)); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 2); STATIC_REQUIRE(table.count_of() == 0); STATIC_REQUIRE(table.count_of_columns_with() == 1); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); + STATIC_REQUIRE(col_index_sequence_of::size() == 2); + STATIC_REQUIRE(col_index_sequence_with::size() == 1); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } { // 2 columns with 1 inline name pk auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name, primary_key())); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 2); STATIC_REQUIRE(table.count_of() == 0); STATIC_REQUIRE(table.count_of_columns_with() == 1); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); + STATIC_REQUIRE(col_index_sequence_of::size() == 2); + STATIC_REQUIRE(col_index_sequence_with::size() == 1); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 0); } { // 2 columns with 1 dedicated id pk auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name), primary_key(&User::id)); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 2); STATIC_REQUIRE(table.count_of() == 1); STATIC_REQUIRE(table.count_of_columns_with() == 0); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); + STATIC_REQUIRE(col_index_sequence_of::size() == 2); + STATIC_REQUIRE(col_index_sequence_with::size() == 0); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 2); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); } { // 2 columns with 1 dedicated name pk auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name), primary_key(&User::name)); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 2); STATIC_REQUIRE(table.count_of() == 1); STATIC_REQUIRE(table.count_of_columns_with() == 0); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); + STATIC_REQUIRE(col_index_sequence_of::size() == 2); + STATIC_REQUIRE(col_index_sequence_with::size() == 0); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 2); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 1); } { // 2 columns with 2 dedicated pks auto table = make_table("users", make_column("id", &User::id), make_column("id", &User::name), primary_key(&User::id, &User::name)); + using elements_type = decltype(table.elements); STATIC_REQUIRE(table.count_of() == 2); STATIC_REQUIRE(table.count_of() == 1); STATIC_REQUIRE(table.count_of_columns_with() == 0); - STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 2); + STATIC_REQUIRE(col_index_sequence_of::size() == 2); + STATIC_REQUIRE(col_index_sequence_with::size() == 0); + STATIC_REQUIRE(col_index_sequence_excluding::size() == 2); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(dedicated_pk_columns_count_t::value == 2); } } diff --git a/tests/static_tests/view_static_tests.cpp b/tests/static_tests/view_static_tests.cpp index b5fc74e10..20f57a269 100644 --- a/tests/static_tests/view_static_tests.cpp +++ b/tests/static_tests/view_static_tests.cpp @@ -3,6 +3,7 @@ #ifdef SQLITE_ORM_WITH_VIEW using namespace sqlite_orm; +using internal::col_index_sequence_of, internal::col_index_sequence_with_field_type; using internal::is_column; struct UserViewStaticTests { @@ -19,14 +20,20 @@ TEST_CASE("view static count_of()") { std::string name; }; - { + SECTION("traditional") { auto view = make_view("user_view", select(columns(&User::id, &User::name))); + using elements_type = decltype(view.elements); STATIC_REQUIRE(view.count_of() == 2); + STATIC_REQUIRE(col_index_sequence_of::size() == 2); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES - { + SECTION("table reference") { auto view = make_view("user_view", select(columns(&User::id, &User::name))); + using elements_type = decltype(view.elements); STATIC_REQUIRE(view.count_of() == 2); + STATIC_REQUIRE(col_index_sequence_of::size() == 2); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); } #endif } From 8b3902a504bc4e039a711288da4203144a6fc4d2 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 25 Sep 2025 21:25:26 +0300 Subject: [PATCH 07/49] Made sql views working with CTEs --- dev/functional/config.h | 2 +- dev/object_from_column_builder.h | 2 +- dev/schema/view.h | 70 ++- dev/select_constraints.h | 31 + dev/storage.h | 42 +- dev/storage_impl.h | 6 + include/sqlite_orm/sqlite_orm.h | 544 ++++++++++-------- tests/schema/view_tests.cpp | 5 +- .../schema/view.cpp | 6 +- tests/static_tests/view_static_tests.cpp | 4 +- tests/view_tests.cpp | 7 +- 11 files changed, 418 insertions(+), 301 deletions(-) diff --git a/dev/functional/config.h b/dev/functional/config.h index 98d2b03ac..e0a3be45f 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -80,7 +80,7 @@ // note: PFR depends on `SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED` for field name determination #if (defined(SQLITE_ORM_CONSTEVAL_SUPPORTED) && defined(SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED)) && \ - (__cpp_lib_byte >= 201603L && __cpp_lib_remove_cvref >= 201711L) && \ + (defined(SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED)) && (__cpp_lib_byte >= 201603L) && \ (__cpp_impl_reflection >= 202500L || BOOST_PFR_ENABLED == 1) #define SQLITE_ORM_WITH_VIEW #endif diff --git a/dev/object_from_column_builder.h b/dev/object_from_column_builder.h index e23bc56ab..525545a64 100644 --- a/dev/object_from_column_builder.h +++ b/dev/object_from_column_builder.h @@ -57,7 +57,7 @@ namespace sqlite_orm { const auto rowExtractor = row_value_extractor(); auto value = rowExtractor.extract(this->stmt, ++this->columnIndex); // calculate absolute address of member from relative address - const std::byte* fieldAddress = (std::byte*)(size_t(&object) + size_t(column.member_pointer.field)); + const std::byte* fieldAddress = (std::byte*)(uintptr_t(&object) + size_t(column.member_pointer.field)); field_type* field = (field_type*)fieldAddress; *field = std::move(value); } diff --git a/dev/schema/view.h b/dev/schema/view.h index d18b7e898..e09b3d85d 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -1,4 +1,5 @@ #pragma once + #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_WITH_VIEW #include // std::remove_cvref @@ -9,13 +10,14 @@ #include #endif #endif -#if __cpp_impl_reflection < 202500L && BOOST_PFR_ENABLED == 1 -#include "../column_pointer.h" #endif + +#include "../column_pointer.h" #include "../select_constraints.h" #include "column.h" #include "mapped_object.h" +#ifdef SQLITE_ORM_WITH_VIEW #if __cpp_impl_reflection >= 202500L #elif BOOST_PFR_ENABLED == 1 namespace boost::pfr { @@ -51,8 +53,10 @@ namespace boost::pfr { } } #endif +#endif namespace sqlite_orm::internal { +#ifdef SQLITE_ORM_WITH_VIEW /** * View definition, mapping an aggregate object type to a corresponding select statement. */ @@ -66,17 +70,18 @@ namespace sqlite_orm::internal { select_type select; }; - template - decltype(auto) get_cte_driving_subselect(const Select& select); - -#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) - template - decltype(auto) get_cte_driving_subselect(const with_t& select) { - return get_cte_driving_subselect(select.expression); - } + template + inline constexpr bool is_view_v = polyfill::is_specialization_of_v; +#else + template + inline constexpr bool is_view_v = false; #endif + + template + struct is_view : polyfill::bool_constant> {}; } +#ifdef SQLITE_ORM_WITH_VIEW #if __cpp_impl_reflection >= 202500L #elif BOOST_PFR_ENABLED == 1 namespace sqlite_orm::internal { @@ -136,48 +141,41 @@ namespace sqlite_orm::internal { return relative.field == &(object->*m); } - template - using columns_size_t = std::tuple_size; - template auto make_view(std::string name, std::index_sequence, Select select) { namespace pfr = boost::pfr; namespace pfrd = pfr::detail; namespace pfrs = pfrd::sequence_tuple; +#if __cpp_lib_is_aggregate >= 201703L static_assert(std::is_aggregate_v); +#endif using PfrTpl = decltype(pfrd::tie_as_tuple(pfrd::fake_object())); // object's member types as a tuple - using TS = pfrs::tuple::type>...>; - - using DrivingSelect = std::remove_cvref_t; - - using columns_size = polyfill::detected_or_t, columns_size_t, DrivingSelect>; - static_assert(columns_size::value == PfrTpl::size_v); + using TS = pfrs::tuple::type>...>; using view_type = view_t(std::string(pfr::get_name()), - column_pointer())>{ - pfr::get_relative_address()}))...>; - - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return view_type{std::move(name), - {make_column<>(std::string(pfr::get_name()), - column_pointer())>{ - pfr::get_relative_address()})...}, - std::move(select)}); + decltype(internal::make_column<>(std::string(pfr::get_name()), + column_pointer())>{ + pfr::get_relative_address()}))...>; + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return view_type{ + std::move(name), + {internal::make_column<>(std::string(pfr::get_name()), + column_pointer())>{ + pfr::get_relative_address()})...}, + std::move(select)}); } } SQLITE_ORM_EXPORT namespace sqlite_orm { template + requires (internal::is_select_expression_v>, - "You must specify a select statement"); if constexpr (is_select_v(select)); diff --git a/dev/select_constraints.h b/dev/select_constraints.h index 150076dd4..f6495e8c8 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -398,6 +398,37 @@ namespace sqlite_orm { } }; + /** + * Specialize if a type is a select statement expression. + */ + template + inline constexpr bool is_select_expression_v = false; + + template + using is_select_expression = polyfill::bool_constant>; + + template + inline constexpr bool is_select_expression_v>> = true; + + template + inline constexpr bool is_select_expression_v< + With, + std::enable_if_t, is_select>>>> = true; + + /* + * Access the main select expression of a with clause or the passed in select expression. + */ + template = true> + constexpr decltype(auto) access_main_select(const T& select) { + if constexpr (is_with_clause_v) { + return (select.expression); + } else if constexpr (is_select_v) { + return select; + } else { + static_assert(polyfill::always_false_v); + } + } + template, bool> = true> const T& access_column_expression(const T& expression) { return expression; diff --git a/dev/storage.h b/dev/storage.h index 873d02be7..ed1cbbf88 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -28,6 +28,7 @@ #include "tuple_helper/tuple_filter.h" #include "tuple_helper/tuple_transformer.h" #include "tuple_helper/tuple_iteration.h" +#include "tuple_helper/tuple_fy.h" #include "type_traits.h" #include "alias.h" #include "error_code.h" @@ -120,7 +121,11 @@ namespace sqlite_orm { storage_opt_or_default(options), storage_opt_or_default(options), foreign_keys_count()}, - db_objects{std::move(dbObjects)} {} + db_objects{std::move(dbObjects)} { +#ifdef SQLITE_ORM_WITH_VIEW + this->validate_dbos(); +#endif + } storage_t(const storage_t&) = default; @@ -143,6 +148,25 @@ namespace sqlite_orm { return storage.db_objects; } +#ifdef SQLITE_ORM_WITH_VIEW + void validate_dbos() const { + // validate views: a view cannot select sub-objects, and column results must be convertible to view's object type + iterate_tuple(views_index_sequence{}, [this](const auto* view) { + using DrivingSelect = polyfill::remove_cvref_tselect))>; + using ExprDBOs = + polyfill::remove_cvref_tdb_objects, view->select))>; + using ColResult = column_result_of_t; + using elements_type = elements_type_t>; + using field_types = transform_tuple_t, field_type_t>; + + static_assert(std::is_same, ColResult>::value, + "A view cannot select sub-objects"); + static_assert(std::is_convertible, field_types>::value, + "Column results must be convertible to view's object type"); + }); + } +#endif + template void create_table(sqlite3* db, const std::string& tableName, const Table& table) { using context_t = serializer_context; @@ -1226,7 +1250,7 @@ namespace sqlite_orm { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; - context_t context{this->db_objects}; + const context_t context{this->db_objects}; const auto sql = serialize(virtualTable, context); this->executor.perform_void_exec(db, sql.c_str()); return res; @@ -1237,7 +1261,7 @@ namespace sqlite_orm { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; - context_t context{this->db_objects}; + const context_t context{this->db_objects}; const auto sql = serialize(index, context); this->executor.perform_void_exec(db, sql.c_str()); return res; @@ -1247,8 +1271,8 @@ namespace sqlite_orm { sync_schema_result sync_dbo(const trigger_t& trigger, sqlite3* db, bool) { using context_t = serializer_context; - const auto res = sync_schema_result::already_in_sync; // TODO Change accordingly - context_t context{this->db_objects}; + const auto res = sync_schema_result::already_in_sync; + const context_t context{this->db_objects}; const auto sql = serialize(trigger, context); this->executor.perform_void_exec(db, sql.c_str()); return res; @@ -1257,10 +1281,11 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_VIEW template sync_schema_result sync_dbo(const view_t& view, sqlite3* db, bool) { - using context_t = serializer_context; + const auto& exprDBOs = db_objects_for_expression(this->db_objects, view.select); + using context_t = serializer_context>; const auto res = sync_schema_result::already_in_sync; - context_t context{this->db_objects}; + const context_t context{exprDBOs}; const auto sql = serialize(view, context); this->executor.perform_void_exec(db, sql.c_str()); return res; @@ -1918,7 +1943,8 @@ namespace sqlite_orm { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) template auto execute(const prepared_statement_t, CTEs...>>& statement) { - using ExprDBOs = decltype(db_objects_for_expression(this->db_objects, statement.expression)); + using ExprDBOs = polyfill::remove_cvref_tdb_objects, + statement.expression))>; // note: it is enough to only use the 'expression DBOs' at compile-time to determine the column results; // because we cannot select objects/structs from a CTE, passing the permanently defined DBOs are enough. using ColResult = column_result_of_t; diff --git a/dev/storage_impl.h b/dev/storage_impl.h index e35c7a8d9..9a007d76e 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -13,6 +13,7 @@ #include "cte_types.h" #include "schema/column.h" #include "schema/table.h" +#include "schema/view.h" #include "storage_lookup.h" // interface functions @@ -22,6 +23,11 @@ namespace sqlite_orm { template using tables_index_sequence = filter_tuple_sequence_t; +#ifdef SQLITE_ORM_WITH_VIEW + template + using views_index_sequence = filter_tuple_sequence_t; +#endif + template = true> constexpr int foreign_keys_count() { int res = 0; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index bf4246568..203e61388 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -311,7 +311,7 @@ using std::nullptr_t; // note: PFR depends on `SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED` for field name determination #if (defined(SQLITE_ORM_CONSTEVAL_SUPPORTED) && defined(SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED)) && \ - (__cpp_lib_byte >= 201603L && __cpp_lib_remove_cvref >= 201711L) && \ + (defined(SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED)) && (__cpp_lib_byte >= 201603L) && \ (__cpp_impl_reflection >= 202500L || BOOST_PFR_ENABLED == 1) #define SQLITE_ORM_WITH_VIEW #endif @@ -1818,6 +1818,30 @@ namespace sqlite_orm { } } +// #include "tuple_helper/tuple_fy.h" + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#include +#endif + +namespace sqlite_orm { + + namespace internal { + + template + struct tuplify { + using type = std::tuple; + }; + template + struct tuplify> { + using type = std::tuple; + }; + + template + using tuplify_t = typename tuplify::type; + } +} + // #include "type_traits.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE @@ -9228,6 +9252,37 @@ namespace sqlite_orm { } }; + /** + * Specialize if a type is a select statement expression. + */ + template + inline constexpr bool is_select_expression_v = false; + + template + using is_select_expression = polyfill::bool_constant>; + + template + inline constexpr bool is_select_expression_v>> = true; + + template + inline constexpr bool is_select_expression_v< + With, + std::enable_if_t, is_select>>>> = true; + + /* + * Access the main select expression of a with clause or the passed in select expression. + */ + template = true> + constexpr decltype(auto) access_main_select(const T& select) { + if constexpr (is_with_clause_v) { + return (select.expression); + } else if constexpr (is_select_v) { + return select; + } else { + static_assert(polyfill::always_false_v); + } + } + template, bool> = true> const T& access_column_expression(const T& expression) { return expression; @@ -10615,28 +10670,6 @@ namespace sqlite_orm { // #include "tuple_helper/tuple_fy.h" -#ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include -#endif - -namespace sqlite_orm { - - namespace internal { - - template - struct tuplify { - using type = std::tuple; - }; - template - struct tuplify> { - using type = std::tuple; - }; - - template - using tuplify_t = typename tuplify::type; - } -} - // #include "tuple_helper/tuple_filter.h" // #include "tuple_helper/tuple_transformer.h" @@ -13027,6 +13060,214 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { } } +// #include "schema/view.h" + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#ifdef SQLITE_ORM_WITH_VIEW +#include // std::remove_cvref +#include // std::forward, std::move, std::index_sequence, std::make_index_sequence +#include // std::byte +#include // offsetof +#if __cpp_impl_reflection >= 202500L +#include +#endif +#endif +#endif + +// #include "../column_pointer.h" + +// #include "../select_constraints.h" + +// #include "column.h" + +// #include "mapped_object.h" + +#ifdef SQLITE_ORM_WITH_VIEW +#if __cpp_impl_reflection >= 202500L +#elif BOOST_PFR_ENABLED == 1 +namespace boost::pfr { + namespace detail { + namespace sequence_tuple { + template + consteval auto get_nth_base(const base_from_member& t) noexcept { + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return t; + } + + template + constexpr auto* get_nth_relative_address() noexcept { + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + static_assert(N < Tpl::size_v); + using nth_type = decltype(get_nth_base(std::declval())); + using field_type = decltype(nth_type::value); + return (field_type*)(std::byte*)offsetof(Tpl, nth_type::value); + } + } + } + + template + constexpr auto* get_relative_address() { + static_assert(sizeof(O) == sizeof(TS), + "====================> Boost.PFR: Member sequence does not indicate correct size for struct " + "type! Maybe the user-provided type is not a SimpleAggregate?"); + static_assert( + alignof(O) == alignof(TS), + "====================> Boost.PFR: Member sequence does not indicate correct alignment for struct type!"); + + return detail::sequence_tuple::get_nth_relative_address(); + } +} +#endif +#endif + +namespace sqlite_orm::internal { +#ifdef SQLITE_ORM_WITH_VIEW + /** + * View definition, mapping an aggregate object type to a corresponding select statement. + */ + template + struct view_t : mapped_object_t { + using base_type = mapped_object_t; + using object_type = typename base_type::object_type; + using elements_type = typename base_type::elements_type; + using select_type = Select; + + select_type select; + }; + + template + inline constexpr bool is_view_v = polyfill::is_specialization_of_v; +#else + template + inline constexpr bool is_view_v = false; +#endif + + template + struct is_view : polyfill::bool_constant> {}; +} + +#ifdef SQLITE_ORM_WITH_VIEW +#if __cpp_impl_reflection >= 202500L +#elif BOOST_PFR_ENABLED == 1 +namespace sqlite_orm::internal { + /** + * Factory function for a column definition from a relative pointer to an object of the object to be mapped. + */ + template + requires (internal::is_column_pointer_v) + internal::column_t + make_column(std::string name, C relativeField, Op... constraints) { + static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); + + // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, + // as this will lead to UB with Clang on MinGW! + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), relativeField, {}, std::tuple{std::move(constraints)...}}); + } + + /* + * A column field carrying a relative address to a member of an object. + * + * Internal note: According to my tests msvc or compilers in general have a hard time to use pointer-to-members at compile-time. + * That's why we use a relative address. + */ + template + struct column_field, empty_setter> { + using member_pointer_t = F O::*; + using setter_type = empty_setter; + using object_type = O; + using field_type = F; + + /** + * Relative pointer to member (offset) used to read and write a field value. + */ + const column_pointer member_pointer; + + SQLITE_ORM_NOUNIQUEADDRESS + const empty_setter setter; + + /** + * Simplified interface for `NOT NULL` constraint + */ + constexpr bool is_not_null() const { + return !type_is_nullable::value; + } + }; + + template + bool compare_fields(F O::* m, const column_pointer& relative) { + constexpr O* object = nullptr; + return &(object->*m) == relative.field; + } + + template + bool compare_fields(const column_pointer& relative, F O::* m) { + constexpr O* object = nullptr; + return relative.field == &(object->*m); + } + + template + auto make_view(std::string name, std::index_sequence, Select select) { + namespace pfr = boost::pfr; + namespace pfrd = pfr::detail; + namespace pfrs = pfrd::sequence_tuple; + +#if __cpp_lib_is_aggregate >= 201703L + static_assert(std::is_aggregate_v); +#endif + + using PfrTpl = decltype(pfrd::tie_as_tuple(pfrd::fake_object())); + // object's member types as a tuple + using TS = pfrs::tuple::type>...>; + + using view_type = + view_t(std::string(pfr::get_name()), + column_pointer())>{ + pfr::get_relative_address()}))...>; + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return view_type{ + std::move(name), + {internal::make_column<>(std::string(pfr::get_name()), + column_pointer())>{ + pfr::get_relative_address()})...}, + std::move(select)}); + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + template + requires (internal::is_select_expression_v) { + select.highest_level = true; + } + return internal::make_view(std::move(name), + std::make_index_sequence>{}, + std::move(select)); + } +} +#endif + +SQLITE_ORM_EXPORT namespace sqlite_orm { +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + /** + * Factory function for a view definition. + * + * The mapped object type is explicitly specified, columns and their names are deferred from the object type. + * The object type must be an aggregate. + */ + template + auto make_view(std::string name, Select select) { + return make_view>(std::move(name), std::forward>, - "You must specify a select statement"); - - if constexpr (is_select_v(select)); - } -#endif -} -#endif - // #include "util.h" namespace sqlite_orm { @@ -23668,7 +23705,11 @@ namespace sqlite_orm { storage_opt_or_default(options), storage_opt_or_default(options), foreign_keys_count()}, - db_objects{std::move(dbObjects)} {} + db_objects{std::move(dbObjects)} { +#ifdef SQLITE_ORM_WITH_VIEW + this->validate_dbos(); +#endif + } storage_t(const storage_t&) = default; @@ -23691,6 +23732,25 @@ namespace sqlite_orm { return storage.db_objects; } +#ifdef SQLITE_ORM_WITH_VIEW + void validate_dbos() const { + // validate views: a view cannot select sub-objects, and column results must be convertible to view's object type + iterate_tuple(views_index_sequence{}, [this](const auto* view) { + using DrivingSelect = polyfill::remove_cvref_tselect))>; + using ExprDBOs = + polyfill::remove_cvref_tdb_objects, view->select))>; + using ColResult = column_result_of_t; + using elements_type = elements_type_t>; + using field_types = transform_tuple_t, field_type_t>; + + static_assert(std::is_same, ColResult>::value, + "A view cannot select sub-objects"); + static_assert(std::is_convertible, field_types>::value, + "Column results must be convertible to view's object type"); + }); + } +#endif + template void create_table(sqlite3* db, const std::string& tableName, const Table& table) { using context_t = serializer_context; @@ -24774,7 +24834,7 @@ namespace sqlite_orm { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; - context_t context{this->db_objects}; + const context_t context{this->db_objects}; const auto sql = serialize(virtualTable, context); this->executor.perform_void_exec(db, sql.c_str()); return res; @@ -24785,7 +24845,7 @@ namespace sqlite_orm { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; - context_t context{this->db_objects}; + const context_t context{this->db_objects}; const auto sql = serialize(index, context); this->executor.perform_void_exec(db, sql.c_str()); return res; @@ -24795,8 +24855,8 @@ namespace sqlite_orm { sync_schema_result sync_dbo(const trigger_t& trigger, sqlite3* db, bool) { using context_t = serializer_context; - const auto res = sync_schema_result::already_in_sync; // TODO Change accordingly - context_t context{this->db_objects}; + const auto res = sync_schema_result::already_in_sync; + const context_t context{this->db_objects}; const auto sql = serialize(trigger, context); this->executor.perform_void_exec(db, sql.c_str()); return res; @@ -24805,10 +24865,11 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_VIEW template sync_schema_result sync_dbo(const view_t& view, sqlite3* db, bool) { - using context_t = serializer_context; + const auto& exprDBOs = db_objects_for_expression(this->db_objects, view.select); + using context_t = serializer_context>; const auto res = sync_schema_result::already_in_sync; - context_t context{this->db_objects}; + const context_t context{exprDBOs}; const auto sql = serialize(view, context); this->executor.perform_void_exec(db, sql.c_str()); return res; @@ -25466,7 +25527,8 @@ namespace sqlite_orm { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) template auto execute(const prepared_statement_t, CTEs...>>& statement) { - using ExprDBOs = decltype(db_objects_for_expression(this->db_objects, statement.expression)); + using ExprDBOs = polyfill::remove_cvref_tdb_objects, + statement.expression))>; // note: it is enough to only use the 'expression DBOs' at compile-time to determine the column results; // because we cannot select objects/structs from a CTE, passing the permanently defined DBOs are enough. using ColResult = column_result_of_t; diff --git a/tests/schema/view_tests.cpp b/tests/schema/view_tests.cpp index 6a3c53a69..c8566af8c 100644 --- a/tests/schema/view_tests.cpp +++ b/tests/schema/view_tests.cpp @@ -15,10 +15,7 @@ TEST_CASE("view::find_column_name") { std::string name; }; - auto table = make_table("user", - make_column("id", &User::id, primary_key().autoincrement()), - make_column("name", &User::name)); - auto view = make_view("user_view", select(columns(&User::id, &User::name))); + auto view = make_view("user_view", select(asterisk())); SECTION("fields") { REQUIRE((view.find_column_name(&UserViewSchemaTests::id) && diff --git a/tests/statement_serializer_tests/schema/view.cpp b/tests/statement_serializer_tests/schema/view.cpp index 82f7d4b0f..96c04c08e 100644 --- a/tests/statement_serializer_tests/schema/view.cpp +++ b/tests/statement_serializer_tests/schema/view.cpp @@ -17,11 +17,11 @@ TEST_CASE("statement_serializer view_t") { }; auto table = make_table("user", make_column("id", &User::id), make_column("name", &User::name)); - auto view = make_view("user_view", select(columns(&User::id, &User::name))); + auto view = make_view("user_view", select(asterisk(true))); using db_objects_t = internal::db_objects_tuple; - auto dbObjects = db_objects_t{table, view}; + const db_objects_t dbObjects{table, view}; using context_t = internal::serializer_context; - context_t context{dbObjects}; + const context_t context{dbObjects}; SECTION("create") { std::string value = serialize(view, context); diff --git a/tests/static_tests/view_static_tests.cpp b/tests/static_tests/view_static_tests.cpp index 20f57a269..071726ab6 100644 --- a/tests/static_tests/view_static_tests.cpp +++ b/tests/static_tests/view_static_tests.cpp @@ -21,7 +21,7 @@ TEST_CASE("view static count_of()") { }; SECTION("traditional") { - auto view = make_view("user_view", select(columns(&User::id, &User::name))); + auto view = make_view("user_view", select(asterisk())); using elements_type = decltype(view.elements); STATIC_REQUIRE(view.count_of() == 2); STATIC_REQUIRE(col_index_sequence_of::size() == 2); @@ -29,7 +29,7 @@ TEST_CASE("view static count_of()") { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES SECTION("table reference") { - auto view = make_view("user_view", select(columns(&User::id, &User::name))); + auto view = make_view("user_view", select(asterisk())); using elements_type = decltype(view.elements); STATIC_REQUIRE(view.count_of() == 2); STATIC_REQUIRE(col_index_sequence_of::size() == 2); diff --git a/tests/view_tests.cpp b/tests/view_tests.cpp index 62da57c2c..b5a181059 100644 --- a/tests/view_tests.cpp +++ b/tests/view_tests.cpp @@ -29,7 +29,7 @@ TEST_CASE("sql view") { auto storage = make_storage( "", make_table("user", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), - make_view("user_view", select(columns(&User::id, &User::name)))); + make_view("user_view", select(asterisk()))); storage.sync_schema(); @@ -47,7 +47,6 @@ TEST_CASE("sql view") { REQUIRE_THAT(users, UnorderedEquals({{1, "name"}})); } } -#if 0 SECTION("view with CTE") { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -56,8 +55,7 @@ TEST_CASE("sql view") { "", make_table("user", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), make_view("user_view", - with(users_cte().as(select(asterisk())), - select(columns(users_cte->*&User::id, users_cte->*&User::name))))); + with(users_cte().as(select(asterisk())), select(asterisk())))); storage.sync_schema(); @@ -77,6 +75,5 @@ TEST_CASE("sql view") { #endif #endif } -#endif } #endif From 733e1b70b69a725d3a0a3045fa57918ab8770e8b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Thu, 25 Sep 2025 23:31:06 +0300 Subject: [PATCH 08/49] Used the new facility to access the main select to reduce lines of code --- dev/select_constraints.h | 13 +++-- dev/storage.h | 85 +++++++++------------------- include/sqlite_orm/sqlite_orm.h | 98 ++++++++++++--------------------- 3 files changed, 68 insertions(+), 128 deletions(-) diff --git a/dev/select_constraints.h b/dev/select_constraints.h index f6495e8c8..6ff0b2b15 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -418,17 +418,20 @@ namespace sqlite_orm { /* * Access the main select expression of a with clause or the passed in select expression. */ - template = true> - constexpr decltype(auto) access_main_select(const T& select) { - if constexpr (is_with_clause_v) { + template = true> + constexpr decltype(auto) access_main_select(const Select& select) { + if constexpr (is_with_clause_v) { return select; } else { - static_assert(polyfill::always_false_v); + static_assert(polyfill::always_false_v) +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires (is_select_expression_v, + "SQL expression must be a select expression or a with-clause with a select expression"); +#endif + if constexpr (is_select_v) + requires (is_select_expression_v prepare(Select statement) { + if constexpr (is_select_v& statement) { using ExprDBOs = polyfill::remove_cvref_tdb_objects, statement.expression))>; // note: it is enough to only use the 'expression DBOs' at compile-time to determine the column results; // because we cannot select objects/structs from a CTE, passing the permanently defined DBOs are enough. - using ColResult = column_result_of_t; - return this->execute_select(statement); - } -#endif - - template - auto execute(const prepared_statement_t>& statement) { - using ColResult = column_result_of_t; + using ColResult = column_result_of_t>; return this->execute_select(statement); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 203e61388..e7ffd5716 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -9272,17 +9272,20 @@ namespace sqlite_orm { /* * Access the main select expression of a with clause or the passed in select expression. */ - template = true> - constexpr decltype(auto) access_main_select(const T& select) { - if constexpr (is_with_clause_v) { + template = true> + constexpr decltype(auto) access_main_select(const Select& select) { + if constexpr (is_with_clause_v) { return select; } else { - static_assert(polyfill::always_false_v); + static_assert(polyfill::always_false_v) +#ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED + requires (is_select_expression_v, + "SQL expression must be a select expression or a with-clause with a select expression"); +#endif + if constexpr (is_select_v) + requires (is_select_expression_v prepare(Select statement) { + if constexpr (is_select_v& statement) { using ExprDBOs = polyfill::remove_cvref_tdb_objects, statement.expression))>; // note: it is enough to only use the 'expression DBOs' at compile-time to determine the column results; // because we cannot select objects/structs from a CTE, passing the permanently defined DBOs are enough. - using ColResult = column_result_of_t; - return this->execute_select(statement); - } -#endif - - template - auto execute(const prepared_statement_t>& statement) { - using ColResult = column_result_of_t; + using ColResult = column_result_of_t>; return this->execute_select(statement); } From add9aeb5f5569cba1274516a06589df63bb99726 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 26 Sep 2025 02:01:24 +0300 Subject: [PATCH 09/49] Generalized preparing and executing 'raw' DML statements --- dev/prepared_statement.h | 42 +++++++++ dev/select_constraints.h | 4 +- dev/storage.h | 114 +---------------------- include/sqlite_orm/sqlite_orm.h | 160 ++++++++++---------------------- 4 files changed, 96 insertions(+), 224 deletions(-) diff --git a/dev/prepared_statement.h b/dev/prepared_statement.h index a3b687812..9cfd8477f 100644 --- a/dev/prepared_statement.h +++ b/dev/prepared_statement.h @@ -310,6 +310,48 @@ namespace sqlite_orm { template using is_insert_constraint = std::is_same; + + /** + * Specialize if a type is a DML statement expression. + */ + template + inline constexpr bool is_raw_dml_expression_v = false; + + template + using is_raw_dml_expression = polyfill::bool_constant>; + + template + inline constexpr bool is_raw_dml_expression_v< + DML, + std::enable_if_t< + polyfill:: + disjunction_v, is_replace_raw, is_update_all, is_remove_all>>> = + true; + + template + inline constexpr bool is_raw_dml_expression_v< + With, + std::enable_if_t, + polyfill::disjunction>, + is_replace_raw>, + is_update_all>, + is_remove_all>>>>> = + true; + + /* + * Access the main select expression of a with clause or the passed in select expression. + */ + template = true> + constexpr decltype(auto) access_main_dml(const DML& dml) { + if constexpr (is_with_clause_v) { + return (dml.expression); + } else { + return dml; + } + } + + template + using main_dml_t = polyfill::remove_cvref_t()))>; } } diff --git a/dev/select_constraints.h b/dev/select_constraints.h index 6ff0b2b15..4a7316ce2 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -422,10 +422,8 @@ namespace sqlite_orm { constexpr decltype(auto) access_main_select(const Select& select) { if constexpr (is_with_clause_v) { - return select; } else { - static_assert(polyfill::always_false_v prepare(Select statement) { @@ -1427,16 +1420,6 @@ namespace sqlite_orm { return this->prepare_impl(std::move(statement)); } - template - prepared_statement_t> prepare(replace_raw_t statement) { - return this->prepare_impl(std::move(statement)); - } - - template - prepared_statement_t> prepare(insert_raw_t statement) { - return this->prepare_impl(std::move(statement)); - } - #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED template prepared_statement_t> @@ -1445,16 +1428,6 @@ namespace sqlite_orm { } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED - template - prepared_statement_t> prepare(update_all_t statement) { - return this->prepare_impl(std::move(statement)); - } - - template - prepared_statement_t> prepare(remove_all_t statement) { - return this->prepare_impl(std::move(statement)); - } - template prepared_statement_t> prepare(get_t statement) { return this->prepare_impl(std::move(statement)); @@ -1530,49 +1503,8 @@ namespace sqlite_orm { return this->prepare_impl(std::move(statement)); } - template - void execute(const prepared_statement_t>& statement) { - sqlite3_stmt* stmt = reset_stmt(statement.stmt); - iterate_ast(statement.expression, conditional_binder{stmt}); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } - } - -#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) - template< - class... CTEs, - class E, - std::enable_if_t< - polyfill::disjunction_v, is_replace_raw, is_update_all, is_remove_all>, - bool> = true> - void execute(const prepared_statement_t>& statement) { - sqlite3_stmt* stmt = reset_stmt(statement.stmt); - iterate_ast(statement.expression, conditional_binder{stmt}); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } - } -#endif - - template - void execute(const prepared_statement_t>& statement) { + template, bool> = true> + void execute(const prepared_statement_t& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); std::string sql; @@ -1879,42 +1811,6 @@ namespace sqlite_orm { #endif } - template - void execute(const prepared_statement_t>& statement) { - sqlite3_stmt* stmt = reset_stmt(statement.stmt); - iterate_ast(statement.expression.conditions, conditional_binder{stmt}); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } - } - - template - void execute(const prepared_statement_t>& statement) { - sqlite3_stmt* stmt = reset_stmt(statement.stmt); - conditional_binder bindNode{stmt}; - iterate_ast(statement.expression.set, bindNode); - iterate_ast(statement.expression.conditions, bindNode); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } - } - template = true> auto execute(const prepared_statement_t) { return (select.expression); - } else if constexpr (is_select_v); + return select; } } @@ -15455,6 +15453,48 @@ namespace sqlite_orm { template using is_insert_constraint = std::is_same; + + /** + * Specialize if a type is a DML statement expression. + */ + template + inline constexpr bool is_raw_dml_expression_v = false; + + template + using is_raw_dml_expression = polyfill::bool_constant>; + + template + inline constexpr bool is_raw_dml_expression_v< + DML, + std::enable_if_t< + polyfill:: + disjunction_v, is_replace_raw, is_update_all, is_remove_all>>> = + true; + + template + inline constexpr bool is_raw_dml_expression_v< + With, + std::enable_if_t, + polyfill::disjunction>, + is_replace_raw>, + is_update_all>, + is_remove_all>>>>> = + true; + + /* + * Access the main select expression of a with clause or the passed in select expression. + */ + template = true> + constexpr decltype(auto) access_main_dml(const DML& dml) { + if constexpr (is_with_clause_v) { + return (dml.expression); + } else { + return dml; + } + } + + template + using main_dml_t = polyfill::remove_cvref_t()))>; } } @@ -24984,17 +25024,10 @@ namespace sqlite_orm { using storage_base::table_exists; // now that it is in storage_base make it into overload set -#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) - template< - class... CTEs, - class E, - std::enable_if_t< - polyfill::disjunction_v, is_replace_raw, is_update_all, is_remove_all>, - bool> = true> - prepared_statement_t> prepare(with_t sel) { - return this->prepare_impl>(std::move(sel)); + template, bool> = true> + prepared_statement_t prepare(DML statement) { + return this->prepare_impl(std::move(statement)); } -#endif template = true> prepared_statement_t& statement) { using ExprDBOs = polyfill::remove_cvref_tdb_objects, From 5b23b3cc9b2a737bd6b403233017aa3e83c8b505 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 26 Sep 2025 03:04:47 +0300 Subject: [PATCH 10/49] Streamlined executing and logging SQL statements --- dev/storage.h | 142 ++++------------- dev/util.h | 123 ++++++++++----- include/sqlite_orm/sqlite_orm.h | 265 ++++++++++++++------------------ 3 files changed, 226 insertions(+), 304 deletions(-) diff --git a/dev/storage.h b/dev/storage.h index 9bedad4fd..d702b6c55 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -1507,17 +1507,7 @@ namespace sqlite_orm { void execute(const prepared_statement_t& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + this->executor.perform_single_step(stmt); } /** @@ -1535,17 +1525,8 @@ namespace sqlite_orm { [&table = this->get_table(), &object = statement.expression.obj](auto& memberPointer) { return table.object_field_value(object, memberPointer); }); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + + this->executor.perform_single_step(stmt); return sqlite3_last_insert_rowid(sqlite3_db_handle(stmt)); } @@ -1583,17 +1564,8 @@ namespace sqlite_orm { const object_type& object = get_object(statement.expression); processObject(object); }; - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + + this->executor.perform_single_step(stmt); } /** @@ -1640,17 +1612,8 @@ namespace sqlite_orm { const object_type& object = get_object(statement.expression); processObject(object); } - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + + this->executor.perform_single_step(stmt); return sqlite3_last_insert_rowid(sqlite3_db_handle(stmt)); } @@ -1658,17 +1621,7 @@ namespace sqlite_orm { void execute(const prepared_statement_t>& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression.ids, conditional_binder{stmt}); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + this->executor.perform_single_step(stmt); } template @@ -1691,17 +1644,8 @@ namespace sqlite_orm { bindValue(polyfill::invoke(column.member_pointer, object)); } }); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + + this->executor.perform_single_step(stmt); } template @@ -1711,21 +1655,11 @@ namespace sqlite_orm { iterate_ast(statement.expression.ids, conditional_binder{stmt}); std::unique_ptr res; - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { + this->executor.perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { res = std::make_unique(); object_from_column_builder builder{*res, stmt}; table.for_each_column(builder); }); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } return res; } @@ -1737,20 +1671,10 @@ namespace sqlite_orm { iterate_ast(statement.expression.ids, conditional_binder{stmt}); std::optional res; - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { + this->executor.perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { object_from_column_builder builder{res.emplace(), stmt}; table.for_each_column(builder); }); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } return res; } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED @@ -1763,26 +1687,17 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED std::optional res; - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { + this->executor.perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { object_from_column_builder builder{res.emplace(), stmt}; table.for_each_column(builder); }); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } if (!res.has_value()) { throw std::system_error{orm_error_code::not_found}; } return std::move(res).value(); #else auto& table = this->get_table(); + std::string sql; if (this->executor.will_run_query || this->executor.did_run_query) { sql = statement.sql(); @@ -1790,24 +1705,27 @@ namespace sqlite_orm { if (this->executor.will_run_query) { this->executor.will_run_query(sql); } - const auto stepRes = sqlite3_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } - switch (stepRes) { - case SQLITE_ROW: { - T res; - object_from_column_builder builder{res, stmt}; - table.for_each_column(builder); - return res; - } break; + + switch (int rc = sqlite3_step(stmt)) { + case SQLITE_ROW: + break; case SQLITE_DONE: { throw std::system_error{orm_error_code::not_found}; - } break; + } default: { - throw_translated_sqlite_error(rc); + throw_translated_sqlite_error(stmt); } } + + T res; + object_from_column_builder builder{res, stmt}; + table.for_each_column(builder); + + if (this->executor.did_run_query) { + this->executor.did_run_query(sql); + } + + return res; #endif } diff --git a/dev/util.h b/dev/util.h index 7e727f352..0af9f7c48 100644 --- a/dev/util.h +++ b/dev/util.h @@ -5,9 +5,6 @@ #include // std::string #include // std::move #include // std::function -#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED -#include // std::string_view -#endif #endif #include "functional/gsl.h" @@ -59,6 +56,28 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { namespace sqlite_orm { namespace internal { + // Wrapper to reduce boiler-plate code + inline sqlite3_stmt* reset_stmt(sqlite3_stmt* stmt) { + sqlite3_reset(stmt); + return stmt; + } + + inline sqlite3_stmt* prepare_stmt(sqlite3* db, serialize_arg_type query) { + sqlite3_stmt* stmt; + const int rc = sqlite3_prepare_v2(db, query.data(), int(query.size()), &stmt, nullptr); + if (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { + throw_translated_sqlite_error(rc); + } + return stmt; + } + + template + void perform_single_step(sqlite3_stmt* stmt) { + const int rc = sqlite3_step(stmt); + if (rc != expected) SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { + throw_translated_sqlite_error(rc); + } + } template void perform_step(sqlite3_stmt* stmt, L&& lambda) { @@ -68,8 +87,26 @@ namespace sqlite_orm { } break; case SQLITE_DONE: return; - default: { - throw_translated_sqlite_error(stmt); + default: + SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { + throw_translated_sqlite_error(stmt); + } + } + } + + template + void perform_steps(sqlite3_stmt* stmt, L&& lambda) { + for (;;) { + switch (int rc = sqlite3_step(stmt)) { + case SQLITE_ROW: { + lambda(stmt); + } break; + case SQLITE_DONE: + return; + default: + SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { + throw_translated_sqlite_error(stmt); + } } } } @@ -82,10 +119,12 @@ namespace sqlite_orm { if (this->will_run_query) { this->will_run_query(sql); } + const int rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr); if (rc != SQLITE_OK) { throw_translated_sqlite_error(rc); } + if (this->did_run_query) { this->did_run_query(sql); } @@ -98,10 +137,12 @@ namespace sqlite_orm { if (this->will_run_query) { this->will_run_query(sql); } + const int rc = sqlite3_exec(db, sql, callback, user_data, nullptr); if (rc != SQLITE_OK) { throw_translated_sqlite_error(rc); } + if (this->did_run_query) { this->did_run_query(sql); } @@ -114,8 +155,8 @@ namespace sqlite_orm { return perform_exec(db, query.c_str(), callback, user_data); } - template - void perform_steps(sqlite3_stmt* stmt, L&& lambda) const { + template + void perform_single_step(sqlite3_stmt* stmt) const { orm_gsl::czstring sql = nullptr; if (this->will_run_query || this->did_run_query) { sql = sqlite3_sql(stmt); @@ -123,45 +164,47 @@ namespace sqlite_orm { if (this->will_run_query) { this->will_run_query(sql); } - for (;;) { - switch (int rc = sqlite3_step(stmt)) { - case SQLITE_ROW: { - lambda(stmt); - } break; - case SQLITE_DONE: - if (this->did_run_query) { - this->did_run_query(sql); - } - return; - default: { - throw_translated_sqlite_error(stmt); - } - } + + internal::perform_single_step(stmt); + + if (this->did_run_query) { + this->did_run_query(sql); } } - }; - // Wrapper to reduce boiler-plate code - inline sqlite3_stmt* reset_stmt(sqlite3_stmt* stmt) { - sqlite3_reset(stmt); - return stmt; - } + template + void perform_step(sqlite3_stmt* stmt, L&& lambda) const { + orm_gsl::czstring sql = nullptr; + if (this->will_run_query || this->did_run_query) { + sql = sqlite3_sql(stmt); + } + if (this->will_run_query) { + this->will_run_query(sql); + } - inline sqlite3_stmt* prepare_stmt(sqlite3* db, serialize_arg_type query) { - sqlite3_stmt* stmt; - const int rc = sqlite3_prepare_v2(db, query.data(), int(query.size()), &stmt, nullptr); - if (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { - throw_translated_sqlite_error(rc); + internal::perform_step(stmt, lambda); + + if (this->did_run_query) { + this->did_run_query(sql); + } } - return stmt; - } - template - void perform_step(sqlite3_stmt* stmt) { - const int rc = sqlite3_step(stmt); - if (rc != expected) { - throw_translated_sqlite_error(rc); + template + void perform_steps(sqlite3_stmt* stmt, L&& lambda) const { + orm_gsl::czstring sql = nullptr; + if (this->will_run_query || this->did_run_query) { + sql = sqlite3_sql(stmt); + } + if (this->will_run_query) { + this->will_run_query(sql); + } + + internal::perform_steps(stmt, lambda); + + if (this->did_run_query) { + this->did_run_query(sql); + } } - } + }; } } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index c9faf30c4..484b74b6e 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -14254,9 +14254,6 @@ namespace sqlite_orm { #include // std::string #include // std::move #include // std::function -#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED -#include // std::string_view -#endif #endif // #include "functional/gsl.h" @@ -14310,6 +14307,28 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { namespace sqlite_orm { namespace internal { + // Wrapper to reduce boiler-plate code + inline sqlite3_stmt* reset_stmt(sqlite3_stmt* stmt) { + sqlite3_reset(stmt); + return stmt; + } + + inline sqlite3_stmt* prepare_stmt(sqlite3* db, serialize_arg_type query) { + sqlite3_stmt* stmt; + const int rc = sqlite3_prepare_v2(db, query.data(), int(query.size()), &stmt, nullptr); + if (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { + throw_translated_sqlite_error(rc); + } + return stmt; + } + + template + void perform_single_step(sqlite3_stmt* stmt) { + const int rc = sqlite3_step(stmt); + if (rc != expected) SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { + throw_translated_sqlite_error(rc); + } + } template void perform_step(sqlite3_stmt* stmt, L&& lambda) { @@ -14319,8 +14338,26 @@ namespace sqlite_orm { } break; case SQLITE_DONE: return; - default: { - throw_translated_sqlite_error(stmt); + default: + SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { + throw_translated_sqlite_error(stmt); + } + } + } + + template + void perform_steps(sqlite3_stmt* stmt, L&& lambda) { + for (;;) { + switch (int rc = sqlite3_step(stmt)) { + case SQLITE_ROW: { + lambda(stmt); + } break; + case SQLITE_DONE: + return; + default: + SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { + throw_translated_sqlite_error(stmt); + } } } } @@ -14333,10 +14370,12 @@ namespace sqlite_orm { if (this->will_run_query) { this->will_run_query(sql); } + const int rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr); if (rc != SQLITE_OK) { throw_translated_sqlite_error(rc); } + if (this->did_run_query) { this->did_run_query(sql); } @@ -14349,10 +14388,12 @@ namespace sqlite_orm { if (this->will_run_query) { this->will_run_query(sql); } + const int rc = sqlite3_exec(db, sql, callback, user_data, nullptr); if (rc != SQLITE_OK) { throw_translated_sqlite_error(rc); } + if (this->did_run_query) { this->did_run_query(sql); } @@ -14365,8 +14406,8 @@ namespace sqlite_orm { return perform_exec(db, query.c_str(), callback, user_data); } - template - void perform_steps(sqlite3_stmt* stmt, L&& lambda) const { + template + void perform_single_step(sqlite3_stmt* stmt) const { orm_gsl::czstring sql = nullptr; if (this->will_run_query || this->did_run_query) { sql = sqlite3_sql(stmt); @@ -14374,46 +14415,48 @@ namespace sqlite_orm { if (this->will_run_query) { this->will_run_query(sql); } - for (;;) { - switch (int rc = sqlite3_step(stmt)) { - case SQLITE_ROW: { - lambda(stmt); - } break; - case SQLITE_DONE: - if (this->did_run_query) { - this->did_run_query(sql); - } - return; - default: { - throw_translated_sqlite_error(stmt); - } - } + + internal::perform_single_step(stmt); + + if (this->did_run_query) { + this->did_run_query(sql); } } - }; - // Wrapper to reduce boiler-plate code - inline sqlite3_stmt* reset_stmt(sqlite3_stmt* stmt) { - sqlite3_reset(stmt); - return stmt; - } + template + void perform_step(sqlite3_stmt* stmt, L&& lambda) const { + orm_gsl::czstring sql = nullptr; + if (this->will_run_query || this->did_run_query) { + sql = sqlite3_sql(stmt); + } + if (this->will_run_query) { + this->will_run_query(sql); + } - inline sqlite3_stmt* prepare_stmt(sqlite3* db, serialize_arg_type query) { - sqlite3_stmt* stmt; - const int rc = sqlite3_prepare_v2(db, query.data(), int(query.size()), &stmt, nullptr); - if (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { - throw_translated_sqlite_error(rc); + internal::perform_step(stmt, lambda); + + if (this->did_run_query) { + this->did_run_query(sql); + } } - return stmt; - } - template - void perform_step(sqlite3_stmt* stmt) { - const int rc = sqlite3_step(stmt); - if (rc != expected) { - throw_translated_sqlite_error(rc); + template + void perform_steps(sqlite3_stmt* stmt, L&& lambda) const { + orm_gsl::czstring sql = nullptr; + if (this->will_run_query || this->did_run_query) { + sql = sqlite3_sql(stmt); + } + if (this->will_run_query) { + this->will_run_query(sql); + } + + internal::perform_steps(stmt, lambda); + + if (this->did_run_query) { + this->did_run_query(sql); + } } - } + }; } } @@ -25134,17 +25177,7 @@ namespace sqlite_orm { void execute(const prepared_statement_t& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression, conditional_binder{stmt}); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + this->executor.perform_single_step(stmt); } /** @@ -25162,17 +25195,8 @@ namespace sqlite_orm { [&table = this->get_table(), &object = statement.expression.obj](auto& memberPointer) { return table.object_field_value(object, memberPointer); }); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + + this->executor.perform_single_step(stmt); return sqlite3_last_insert_rowid(sqlite3_db_handle(stmt)); } @@ -25210,17 +25234,8 @@ namespace sqlite_orm { const object_type& object = get_object(statement.expression); processObject(object); }; - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + + this->executor.perform_single_step(stmt); } /** @@ -25267,17 +25282,8 @@ namespace sqlite_orm { const object_type& object = get_object(statement.expression); processObject(object); } - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + + this->executor.perform_single_step(stmt); return sqlite3_last_insert_rowid(sqlite3_db_handle(stmt)); } @@ -25285,17 +25291,7 @@ namespace sqlite_orm { void execute(const prepared_statement_t>& statement) { sqlite3_stmt* stmt = reset_stmt(statement.stmt); iterate_ast(statement.expression.ids, conditional_binder{stmt}); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + this->executor.perform_single_step(stmt); } template @@ -25318,17 +25314,8 @@ namespace sqlite_orm { bindValue(polyfill::invoke(column.member_pointer, object)); } }); - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } + + this->executor.perform_single_step(stmt); } template @@ -25338,21 +25325,11 @@ namespace sqlite_orm { iterate_ast(statement.expression.ids, conditional_binder{stmt}); std::unique_ptr res; - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { + this->executor.perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { res = std::make_unique(); object_from_column_builder builder{*res, stmt}; table.for_each_column(builder); }); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } return res; } @@ -25364,20 +25341,10 @@ namespace sqlite_orm { iterate_ast(statement.expression.ids, conditional_binder{stmt}); std::optional res; - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { + this->executor.perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { object_from_column_builder builder{res.emplace(), stmt}; table.for_each_column(builder); }); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } return res; } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED @@ -25390,26 +25357,17 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED std::optional res; - std::string sql; - if (this->executor.will_run_query || this->executor.did_run_query) { - sql = statement.sql(); - } - if (this->executor.will_run_query) { - this->executor.will_run_query(sql); - } - perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { + this->executor.perform_step(stmt, [&table = this->get_table(), &res](sqlite3_stmt* stmt) { object_from_column_builder builder{res.emplace(), stmt}; table.for_each_column(builder); }); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } if (!res.has_value()) { throw std::system_error{orm_error_code::not_found}; } return std::move(res).value(); #else auto& table = this->get_table(); + std::string sql; if (this->executor.will_run_query || this->executor.did_run_query) { sql = statement.sql(); @@ -25417,24 +25375,27 @@ namespace sqlite_orm { if (this->executor.will_run_query) { this->executor.will_run_query(sql); } - const auto stepRes = sqlite3_step(stmt); - if (this->executor.did_run_query) { - this->executor.did_run_query(sql); - } - switch (stepRes) { - case SQLITE_ROW: { - T res; - object_from_column_builder builder{res, stmt}; - table.for_each_column(builder); - return res; - } break; + + switch (int rc = sqlite3_step(stmt)) { + case SQLITE_ROW: + break; case SQLITE_DONE: { throw std::system_error{orm_error_code::not_found}; - } break; + } default: { - throw_translated_sqlite_error(rc); + throw_translated_sqlite_error(stmt); } } + + T res; + object_from_column_builder builder{res, stmt}; + table.for_each_column(builder); + + if (this->executor.did_run_query) { + this->executor.did_run_query(sql); + } + + return res; #endif } From 996c34eb660439f34a9f2207509125bbf029fcff Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 26 Sep 2025 15:39:30 +0300 Subject: [PATCH 11/49] Generalized serializer context * Removed `serializer_context_builder`, which is now entirely replaced by `obtain_db_objects()`. * Renamed serializer context flag variables. --- dev/ast/set.h | 5 +- dev/column_names_getter.h | 4 +- dev/conditions.h | 5 +- dev/cte_column_names_collector.h | 4 +- dev/mapped_view.h | 2 +- dev/order_by_serializer.h | 2 +- dev/result_set_view.h | 2 +- dev/serializer_context.h | 18 +--- dev/serializing_util.h | 2 +- dev/statement_serializer.h | 38 ++++---- dev/storage.h | 4 +- include/sqlite_orm/sqlite_orm.h | 86 ++++++++----------- .../column_names.cpp | 6 +- .../schema/column.cpp | 4 +- .../statements/select.cpp | 4 +- .../table_constraints/primary_key.cpp | 2 +- 16 files changed, 78 insertions(+), 110 deletions(-) diff --git a/dev/ast/set.h b/dev/ast/set.h index aa9c20846..ed5aa3751 100644 --- a/dev/ast/set.h +++ b/dev/ast/set.h @@ -51,7 +51,7 @@ namespace sqlite_orm { template void push_back(assign_t assign) { auto newContext = this->context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; // note: we are only interested in the table name on the left-hand side of the assignment operator expression iterate_ast(assign.lhs, this->collector); std::stringstream ss; @@ -108,7 +108,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template internal::dynamic_set_t> dynamic_set(const S& storage) { - internal::serializer_context_builder builder(storage); - return builder(); + return {obtain_db_objects(storage)}; } } diff --git a/dev/column_names_getter.h b/dev/column_names_getter.h index 3b5dfc658..1200cc5dd 100644 --- a/dev/column_names_getter.h +++ b/dev/column_names_getter.h @@ -33,7 +33,7 @@ namespace sqlite_orm { if (definedOrder) { auto& table = pick_table>(context.db_objects); collectedExpressions.reserve(collectedExpressions.size() + table.template count_of()); - table.for_each_column([qualified = !context.skip_table_name, + table.for_each_column([qualified = !context.omit_table_name, &tableName = table.name, &collectedExpressions](const column_identifier& column) { if constexpr (is_alias::value) { @@ -50,7 +50,7 @@ namespace sqlite_orm { collectedExpressions.reserve(collectedExpressions.size() + 1); if constexpr (is_alias::value) { collectedExpressions.push_back(quote_identifier(alias_extractor::extract()) + ".*"); - } else if (!context.skip_table_name) { + } else if (!context.omit_table_name) { const basic_table& table = pick_table>(context.db_objects); collectedExpressions.push_back(quote_identifier(table.name) + ".*"); } else { diff --git a/dev/conditions.h b/dev/conditions.h index f565912d0..24ae7446c 100644 --- a/dev/conditions.h +++ b/dev/conditions.h @@ -493,7 +493,7 @@ namespace sqlite_orm { template void push_back(order_by_t orderBy) { auto newContext = this->context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; auto columnName = serialize(orderBy._expression, newContext); this->entries.emplace_back(std::move(columnName), std::move(orderBy._collate_argument), orderBy._order); } @@ -1149,8 +1149,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template internal::dynamic_order_by_t> dynamic_order_by(const S& storage) { - internal::serializer_context_builder builder(storage); - return builder(); + return {obtain_db_objects(storage)}; } /** diff --git a/dev/cte_column_names_collector.h b/dev/cte_column_names_collector.h index fc3c42d4a..b02fbf204 100644 --- a/dev/cte_column_names_collector.h +++ b/dev/cte_column_names_collector.h @@ -57,7 +57,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::vector operator()(const expression_type& t, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; std::string columnName = serialize(t, newContext); if (columnName.empty()) { throw std::system_error{orm_error_code::column_not_found}; @@ -137,7 +137,7 @@ namespace sqlite_orm { std::vector columnNames; columnNames.reserve(size_t(cols.count)); auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; iterate_tuple(cols.columns, [&columnNames, &newContext](auto& m) { using value_type = polyfill::remove_cvref_t; diff --git a/dev/mapped_view.h b/dev/mapped_view.h index aa72d1f66..57ffc8a92 100644 --- a/dev/mapped_view.h +++ b/dev/mapped_view.h @@ -42,7 +42,7 @@ namespace sqlite_orm { auto& dbObjects = obtain_db_objects(this->storage); context_t context{dbObjects}; - context.skip_table_name = false; + context.omit_table_name = false; context.replace_bindable_with_question = true; const std::string sql = serialize(this->expression, context); diff --git a/dev/order_by_serializer.h b/dev/order_by_serializer.h index 1782ca39a..f862c6939 100644 --- a/dev/order_by_serializer.h +++ b/dev/order_by_serializer.h @@ -45,7 +45,7 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; auto newContext = context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; ss << serialize(orderBy._expression, newContext); seralize_collate(ss, orderBy); diff --git a/dev/result_set_view.h b/dev/result_set_view.h index 2aa6b2377..53a016855 100644 --- a/dev/result_set_view.h +++ b/dev/result_set_view.h @@ -53,7 +53,7 @@ namespace sqlite_orm::internal { using context_t = serializer_context; context_t context{exprDBOs}; - context.skip_table_name = false; + context.omit_table_name = false; context.replace_bindable_with_question = true; const std::string sql = serialize(this->expression, context); diff --git a/dev/serializer_context.h b/dev/serializer_context.h index dfa6d3ad0..2fe1afde5 100644 --- a/dev/serializer_context.h +++ b/dev/serializer_context.h @@ -6,9 +6,9 @@ namespace sqlite_orm { struct serializer_context_base { bool replace_bindable_with_question = false; - bool skip_table_name = true; + bool omit_table_name = true; bool use_parentheses = true; - bool fts5_columns = false; + bool omit_column_type = false; }; template @@ -19,20 +19,6 @@ namespace sqlite_orm { serializer_context(const db_objects_type& dbObjects) : db_objects{dbObjects} {} }; - - template - struct serializer_context_builder { - using storage_type = S; - using db_objects_type = typename storage_type::db_objects_type; - - serializer_context_builder(const storage_type& storage_) : storage{storage_} {} - - serializer_context operator()() const { - return {obtain_db_objects(this->storage)}; - } - - const storage_type& storage; - }; } } diff --git a/dev/serializing_util.h b/dev/serializing_util.h index 219111d65..0c85932ae 100644 --- a/dev/serializing_util.h +++ b/dev/serializing_util.h @@ -433,7 +433,7 @@ namespace sqlite_orm { ss << ' ' << serialize(constraint, context); }); // add implicit null constraint - if (!context.fts5_columns) { + if (!context.omit_column_type) { constexpr bool hasExplicitNullableConstraint = mpl::invoke_t, check_if_has_type>, constraints_tuple>::value; diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index d70bedb06..f9714f925 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -182,7 +182,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) { auto subContext = context; - subContext.fts5_columns = true; + subContext.omit_column_type = true; std::stringstream ss; ss << "CREATE VIEW " << streaming_identifier(statement.name) << " (" << streaming_expressions_tuple(statement.elements, subContext) @@ -442,11 +442,11 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& c, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; - if (!context.skip_table_name) { + if (!context.omit_table_name) { ss << streaming_identifier(alias_extractor::extract()) << "."; } auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; ss << serialize(c.column, newContext); return ss.str(); } @@ -464,7 +464,7 @@ namespace sqlite_orm { std::stringstream ss; if (auto* columnName = find_column_name(context.db_objects, e)) { ss << streaming_identifier( - !context.skip_table_name ? lookup_table_name>(context.db_objects) : "", + !context.omit_table_name ? lookup_table_name>(context.db_objects) : "", *columnName, ""); } else { @@ -526,7 +526,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; - if (!context.skip_table_name) { + if (!context.omit_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } ss << static_cast(statement); @@ -542,7 +542,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; - if (!context.skip_table_name) { + if (!context.omit_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } ss << static_cast(statement); @@ -558,7 +558,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; - if (!context.skip_table_name) { + if (!context.omit_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } ss << static_cast(statement); @@ -1327,7 +1327,7 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; ss << streaming_identifier(column.name); - if (!context.fts5_columns) { + if (!context.omit_column_type) { ss << " " << type_printer>>().print(); } ss << streaming_column_constraints( @@ -1474,7 +1474,7 @@ namespace sqlite_orm { std::stringstream ss; ss << "SET "; auto leftContext = context; - leftContext.skip_table_name = true; + leftContext.omit_table_name = true; iterate_tuple(statement.assigns, [&ss, &context, &leftContext, first = true](auto& value) mutable { static constexpr std::array sep = {", ", ""}; ss << sep[std::exchange(first, false)] << serialize(value.lhs, leftContext) << ' ' @@ -1634,7 +1634,7 @@ namespace sqlite_orm { ss << ' '; if constexpr (is_columns::value) { auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; newContext.use_parentheses = true; ss << serialize(value, newContext); } else if constexpr (is_values::value || is_select::value) { @@ -1899,7 +1899,7 @@ namespace sqlite_orm { template SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& sel, Ctx context) SQLITE_ORM_OR_CONST_CALLOP { - context.skip_table_name = false; + context.omit_table_name = false; // subqueries should always use parentheses in column names auto subCtx = context; subCtx.use_parentheses = true; @@ -1983,7 +1983,7 @@ namespace sqlite_orm { std::stringstream ss; ss << "USING FTS5("; auto subContext = context; - subContext.fts5_columns = true; + subContext.omit_column_type = true; ss << streaming_expressions_tuple(statement.columns, subContext) << ")"; return ss.str(); } @@ -2073,7 +2073,7 @@ namespace sqlite_orm { std::stringstream ss; ss << "OLD."; auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; ss << serialize(statement.expression, newContext); return ss.str(); } @@ -2089,7 +2089,7 @@ namespace sqlite_orm { std::stringstream ss; ss << "NEW."; auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; ss << serialize(statement.expression, newContext); return ss.str(); } @@ -2332,7 +2332,7 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; auto newContext = context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; ss << static_cast(on) << " " << serialize(on.arg, newContext) << " "; return ss.str(); } @@ -2347,7 +2347,7 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; auto newContext = context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; ss << "GROUP BY " << streaming_expressions_tuple(statement.args, newContext) << " HAVING " << serialize(statement.expression, context); return ss.str(); @@ -2363,7 +2363,7 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; auto newContext = context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; ss << "GROUP BY " << streaming_expressions_tuple(statement.args, newContext); return ss.str(); } @@ -2381,7 +2381,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { auto newContext = context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; std::stringstream ss; ss << "LIMIT "; if constexpr (HO) { @@ -2423,7 +2423,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; return static_cast(statement) + " (" + serialize(statement.column, newContext) + ")"; } }; diff --git a/dev/storage.h b/dev/storage.h index d702b6c55..4abbe515e 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -1315,7 +1315,7 @@ namespace sqlite_orm { context_t context{exprDBOs}; context.replace_bindable_with_question = parametrized; // just like prepare_impl() - context.skip_table_name = false; + context.omit_table_name = false; return serialize(expression, context); } @@ -1333,7 +1333,7 @@ namespace sqlite_orm { using context_t = serializer_context>; context_t context{exprDBOs}; - context.skip_table_name = false; + context.omit_table_name = false; context.replace_bindable_with_question = true; auto conection = this->get_connection(); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 484b74b6e..2ad17b3bd 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -5025,9 +5025,9 @@ namespace sqlite_orm { struct serializer_context_base { bool replace_bindable_with_question = false; - bool skip_table_name = true; + bool omit_table_name = true; bool use_parentheses = true; - bool fts5_columns = false; + bool omit_column_type = false; }; template @@ -5038,20 +5038,6 @@ namespace sqlite_orm { serializer_context(const db_objects_type& dbObjects) : db_objects{dbObjects} {} }; - - template - struct serializer_context_builder { - using storage_type = S; - using db_objects_type = typename storage_type::db_objects_type; - - serializer_context_builder(const storage_type& storage_) : storage{storage_} {} - - serializer_context operator()() const { - return {obtain_db_objects(this->storage)}; - } - - const storage_type& storage; - }; } } @@ -5663,7 +5649,7 @@ namespace sqlite_orm { template void push_back(order_by_t orderBy) { auto newContext = this->context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; auto columnName = serialize(orderBy._expression, newContext); this->entries.emplace_back(std::move(columnName), std::move(orderBy._collate_argument), orderBy._order); } @@ -6319,8 +6305,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template internal::dynamic_order_by_t> dynamic_order_by(const S& storage) { - internal::serializer_context_builder builder(storage); - return builder(); + return {obtain_db_objects(storage)}; } /** @@ -15145,7 +15130,7 @@ namespace sqlite_orm { template void push_back(assign_t assign) { auto newContext = this->context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; // note: we are only interested in the table name on the left-hand side of the assignment operator expression iterate_ast(assign.lhs, this->collector); std::stringstream ss; @@ -15202,8 +15187,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template internal::dynamic_set_t> dynamic_set(const S& storage) { - internal::serializer_context_builder builder(storage); - return builder(); + return {obtain_db_objects(storage)}; } } @@ -16910,7 +16894,7 @@ namespace sqlite_orm { auto& dbObjects = obtain_db_objects(this->storage); context_t context{dbObjects}; - context.skip_table_name = false; + context.omit_table_name = false; context.replace_bindable_with_question = true; const std::string sql = serialize(this->expression, context); @@ -17088,7 +17072,7 @@ namespace sqlite_orm::internal { using context_t = serializer_context; context_t context{exprDBOs}; - context.skip_table_name = false; + context.omit_table_name = false; context.replace_bindable_with_question = true; const std::string sql = serialize(this->expression, context); @@ -17650,7 +17634,7 @@ namespace sqlite_orm { ss << ' ' << serialize(constraint, context); }); // add implicit null constraint - if (!context.fts5_columns) { + if (!context.omit_column_type) { constexpr bool hasExplicitNullableConstraint = mpl::invoke_t, check_if_has_type>, constraints_tuple>::value; @@ -20030,7 +20014,7 @@ namespace sqlite_orm { if (definedOrder) { auto& table = pick_table>(context.db_objects); collectedExpressions.reserve(collectedExpressions.size() + table.template count_of()); - table.for_each_column([qualified = !context.skip_table_name, + table.for_each_column([qualified = !context.omit_table_name, &tableName = table.name, &collectedExpressions](const column_identifier& column) { if constexpr (is_alias::value) { @@ -20047,7 +20031,7 @@ namespace sqlite_orm { collectedExpressions.reserve(collectedExpressions.size() + 1); if constexpr (is_alias::value) { collectedExpressions.push_back(quote_identifier(alias_extractor::extract()) + ".*"); - } else if (!context.skip_table_name) { + } else if (!context.omit_table_name) { const basic_table& table = pick_table>(context.db_objects); collectedExpressions.push_back(quote_identifier(table.name) + ".*"); } else { @@ -20192,7 +20176,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::vector operator()(const expression_type& t, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; std::string columnName = serialize(t, newContext); if (columnName.empty()) { throw std::system_error{orm_error_code::column_not_found}; @@ -20272,7 +20256,7 @@ namespace sqlite_orm { std::vector columnNames; columnNames.reserve(size_t(cols.count)); auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; iterate_tuple(cols.columns, [&columnNames, &newContext](auto& m) { using value_type = polyfill::remove_cvref_t; @@ -20407,7 +20391,7 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; auto newContext = context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; ss << serialize(orderBy._expression, newContext); seralize_collate(ss, orderBy); @@ -20873,7 +20857,7 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const Ctx& context) { auto subContext = context; - subContext.fts5_columns = true; + subContext.omit_column_type = true; std::stringstream ss; ss << "CREATE VIEW " << streaming_identifier(statement.name) << " (" << streaming_expressions_tuple(statement.elements, subContext) @@ -21133,11 +21117,11 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& c, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; - if (!context.skip_table_name) { + if (!context.omit_table_name) { ss << streaming_identifier(alias_extractor::extract()) << "."; } auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; ss << serialize(c.column, newContext); return ss.str(); } @@ -21155,7 +21139,7 @@ namespace sqlite_orm { std::stringstream ss; if (auto* columnName = find_column_name(context.db_objects, e)) { ss << streaming_identifier( - !context.skip_table_name ? lookup_table_name>(context.db_objects) : "", + !context.omit_table_name ? lookup_table_name>(context.db_objects) : "", *columnName, ""); } else { @@ -21217,7 +21201,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; - if (!context.skip_table_name) { + if (!context.omit_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } ss << static_cast(statement); @@ -21233,7 +21217,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; - if (!context.skip_table_name) { + if (!context.omit_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } ss << static_cast(statement); @@ -21249,7 +21233,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; - if (!context.skip_table_name) { + if (!context.omit_table_name) { ss << streaming_identifier(lookup_table_name(context.db_objects)) << "."; } ss << static_cast(statement); @@ -22018,7 +22002,7 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; ss << streaming_identifier(column.name); - if (!context.fts5_columns) { + if (!context.omit_column_type) { ss << " " << type_printer>>().print(); } ss << streaming_column_constraints( @@ -22165,7 +22149,7 @@ namespace sqlite_orm { std::stringstream ss; ss << "SET "; auto leftContext = context; - leftContext.skip_table_name = true; + leftContext.omit_table_name = true; iterate_tuple(statement.assigns, [&ss, &context, &leftContext, first = true](auto& value) mutable { static constexpr std::array sep = {", ", ""}; ss << sep[std::exchange(first, false)] << serialize(value.lhs, leftContext) << ' ' @@ -22325,7 +22309,7 @@ namespace sqlite_orm { ss << ' '; if constexpr (is_columns::value) { auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; newContext.use_parentheses = true; ss << serialize(value, newContext); } else if constexpr (is_values::value || is_select::value) { @@ -22590,7 +22574,7 @@ namespace sqlite_orm { template SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& sel, Ctx context) SQLITE_ORM_OR_CONST_CALLOP { - context.skip_table_name = false; + context.omit_table_name = false; // subqueries should always use parentheses in column names auto subCtx = context; subCtx.use_parentheses = true; @@ -22674,7 +22658,7 @@ namespace sqlite_orm { std::stringstream ss; ss << "USING FTS5("; auto subContext = context; - subContext.fts5_columns = true; + subContext.omit_column_type = true; ss << streaming_expressions_tuple(statement.columns, subContext) << ")"; return ss.str(); } @@ -22764,7 +22748,7 @@ namespace sqlite_orm { std::stringstream ss; ss << "OLD."; auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; ss << serialize(statement.expression, newContext); return ss.str(); } @@ -22780,7 +22764,7 @@ namespace sqlite_orm { std::stringstream ss; ss << "NEW."; auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; ss << serialize(statement.expression, newContext); return ss.str(); } @@ -23023,7 +23007,7 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; auto newContext = context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; ss << static_cast(on) << " " << serialize(on.arg, newContext) << " "; return ss.str(); } @@ -23038,7 +23022,7 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; auto newContext = context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; ss << "GROUP BY " << streaming_expressions_tuple(statement.args, newContext) << " HAVING " << serialize(statement.expression, context); return ss.str(); @@ -23054,7 +23038,7 @@ namespace sqlite_orm { const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { std::stringstream ss; auto newContext = context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; ss << "GROUP BY " << streaming_expressions_tuple(statement.args, newContext); return ss.str(); } @@ -23072,7 +23056,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { auto newContext = context; - newContext.skip_table_name = false; + newContext.omit_table_name = false; std::stringstream ss; ss << "LIMIT "; if constexpr (HO) { @@ -23114,7 +23098,7 @@ namespace sqlite_orm { SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, const Ctx& context) SQLITE_ORM_OR_CONST_CALLOP { auto newContext = context; - newContext.skip_table_name = true; + newContext.omit_table_name = true; return static_cast(statement) + " (" + serialize(statement.column, newContext) + ")"; } }; @@ -24985,7 +24969,7 @@ namespace sqlite_orm { context_t context{exprDBOs}; context.replace_bindable_with_question = parametrized; // just like prepare_impl() - context.skip_table_name = false; + context.omit_table_name = false; return serialize(expression, context); } @@ -25003,7 +24987,7 @@ namespace sqlite_orm { using context_t = serializer_context>; context_t context{exprDBOs}; - context.skip_table_name = false; + context.omit_table_name = false; context.replace_bindable_with_question = true; auto conection = this->get_connection(); diff --git a/tests/statement_serializer_tests/column_names.cpp b/tests/statement_serializer_tests/column_names.cpp index b1963cf82..31bbb9dc8 100644 --- a/tests/statement_serializer_tests/column_names.cpp +++ b/tests/statement_serializer_tests/column_names.cpp @@ -21,7 +21,7 @@ TEST_CASE("statement_serializer column names") { REQUIRE(value == R"("id")"); } SECTION("don't skip table name") { - context.skip_table_name = false; + context.omit_table_name = false; auto value = serialize(&User::id, context); REQUIRE(value == R"("users"."id")"); } @@ -159,7 +159,7 @@ TEST_CASE("statement_serializer column names") { SECTION("regular") { using context_t = internal::serializer_context; context_t context{dbObjects}; - context.skip_table_name = false; + context.omit_table_name = false; using als = alias_a; auto value = serialize(alias_column(&Object::id), context); REQUIRE(value == R"("a"."id")"); @@ -171,7 +171,7 @@ TEST_CASE("statement_serializer column names") { internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, 1_ctealias().as(select(1)))); using context_t = internal::serializer_context; context_t context{dbObjects2}; - context.skip_table_name = false; + context.omit_table_name = false; constexpr auto als = "a"_alias.for_<1_ctealias>(); auto value = serialize(als->*1_colalias, context); REQUIRE(value == R"("a"."1")"); diff --git a/tests/statement_serializer_tests/schema/column.cpp b/tests/statement_serializer_tests/schema/column.cpp index 0dec2393c..9fad196c6 100644 --- a/tests/statement_serializer_tests/schema/column.cpp +++ b/tests/statement_serializer_tests/schema/column.cpp @@ -14,7 +14,7 @@ TEST_CASE("statement_serializer column") { std::string value; std::string expected; SECTION("with types and constraints") { - context.fts5_columns = false; + context.omit_column_type = false; SECTION("id INTEGER (implicit) NOT NULL") { auto column = make_column("id", &User::id); value = serialize(column, context); @@ -82,7 +82,7 @@ TEST_CASE("statement_serializer column") { } } SECTION("without types and constraints") { - context.fts5_columns = true; + context.omit_column_type = true; SECTION("id INTEGER NOT NULL") { auto column = make_column("id", &User::id); value = serialize(column, context); diff --git a/tests/statement_serializer_tests/statements/select.cpp b/tests/statement_serializer_tests/statements/select.cpp index f21b125e8..1402562fb 100644 --- a/tests/statement_serializer_tests/statements/select.cpp +++ b/tests/statement_serializer_tests/statements/select.cpp @@ -192,7 +192,7 @@ TEST_CASE("statement_serializer select_t") { // issue #1106 SECTION("multi") { auto expression = columns(asterisk(), asterisk(true)); - context.skip_table_name = false; + context.omit_table_name = false; context.use_parentheses = false; stringValue = serialize(expression, context); expected = R"("users".*, "users"."id", "users"."name")"; @@ -228,7 +228,7 @@ TEST_CASE("statement_serializer select_t") { where(is_null(alias_column(&Employee::m_deptno)))); expression.highest_level = true; internal::serializer_context context{storage}; - context.skip_table_name = false; + context.omit_table_name = false; stringValue = serialize(expression, context); expected = R"(SELECT "d".* FROM "Dept" "d" LEFT JOIN "Emp" "e" ON "d"."deptno" = "e"."deptno" WHERE ("e"."deptno" IS NULL))"; diff --git a/tests/statement_serializer_tests/table_constraints/primary_key.cpp b/tests/statement_serializer_tests/table_constraints/primary_key.cpp index d843e3320..e55cb4d02 100644 --- a/tests/statement_serializer_tests/table_constraints/primary_key.cpp +++ b/tests/statement_serializer_tests/table_constraints/primary_key.cpp @@ -24,7 +24,7 @@ TEST_CASE("statement_serializer primary key table constraint") { auto dbObjects = db_objects_t{table1, table2}; using context_t = internal::serializer_context; context_t context{dbObjects}; - context.skip_table_name = false; + context.omit_table_name = false; SECTION("single column pk") { constexpr auto pk = primary_key(&User::id); From aebf0cf311804f89353ac1c5dd93b9d0be57bcad Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 26 Sep 2025 19:07:55 +0300 Subject: [PATCH 12/49] Restructured classes representing table database objects * Factored out common functionality into common base classes and mixins. * Renamed structures. * Split CTE table class into its own structure, effectively decoupling it from a table DBO. --- dev/alias.h | 6 +- dev/column_names_getter.h | 2 +- dev/cte_storage.h | 23 +- dev/schema/mapped_object.h | 148 ----- dev/schema/table.h | 246 +------- dev/schema/table_base.h | 148 +++++ dev/schema/triggers.h | 4 +- dev/schema/view.h | 7 +- dev/statement_serializer.h | 12 +- dev/storage.h | 4 +- dev/storage_impl.h | 4 +- dev/storage_lookup.h | 4 +- include/sqlite_orm/sqlite_orm.h | 549 ++++++------------ .../column_names.cpp | 3 +- .../select_constraints.cpp | 2 +- .../statements/insert_replace.cpp | 4 +- .../statements/remove_all.cpp | 2 +- .../statements/update_all.cpp | 2 +- tests/static_tests/column_expression_type.cpp | 6 +- tests/static_tests/column_result_t.cpp | 2 +- tests/static_tests/cte.cpp | 2 +- tests/table_name_collector.cpp | 2 +- 22 files changed, 397 insertions(+), 785 deletions(-) delete mode 100644 dev/schema/mapped_object.h create mode 100644 dev/schema/table_base.h diff --git a/dev/alias.h b/dev/alias.h index d5c9c689e..a5548da61 100644 --- a/dev/alias.h +++ b/dev/alias.h @@ -58,7 +58,7 @@ namespace sqlite_orm { is_operator_argument_v::value>> = true; - struct basic_table; + struct table_base; /* * Encapsulates extracting the alias identifier of a non-alias. @@ -77,7 +77,7 @@ namespace sqlite_orm { return {}; } - template + template static const std::string& as_qualifier(const X& table) { return table.name; } @@ -114,7 +114,7 @@ namespace sqlite_orm { // for regular table aliases -> alias identifier template = true> - static std::string as_qualifier(const basic_table&) { + static std::string as_qualifier(const table_base&) { return alias_extractor::extract(); } }; diff --git a/dev/column_names_getter.h b/dev/column_names_getter.h index 1200cc5dd..5fa1bc373 100644 --- a/dev/column_names_getter.h +++ b/dev/column_names_getter.h @@ -51,7 +51,7 @@ namespace sqlite_orm { if constexpr (is_alias::value) { collectedExpressions.push_back(quote_identifier(alias_extractor::extract()) + ".*"); } else if (!context.omit_table_name) { - const basic_table& table = pick_table>(context.db_objects); + const table_base& table = pick_table>(context.db_objects); collectedExpressions.push_back(quote_identifier(table.name) + ".*"); } else { collectedExpressions.emplace_back("*"); diff --git a/dev/cte_storage.h b/dev/cte_storage.h index b2ee153ce..8328bd7e1 100644 --- a/dev/cte_storage.h +++ b/dev/cte_storage.h @@ -13,7 +13,7 @@ #include "table_type_of.h" #include "column_result.h" #include "select_constraints.h" -#include "schema/table.h" +#include "schema/table_base.h" #include "alias.h" #include "cte_types.h" #include "cte_column_names_collector.h" @@ -64,6 +64,21 @@ namespace sqlite_orm { FinalColRefs, Result>::type; + template + struct cte_table : table_base, mapped_columns_mixin { + using base_type = mapped_columns_mixin; + using cte_mapper_type = Mapper; + using cte_moniker_type = typename cte_mapper_type::cte_moniker_type; + using object_type = cte_moniker_type; + using elements_type = typename base_type::elements_type; + }; + + template + cte_table make_cte_table(std::string name, Cs... args) { + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), std::make_tuple(std::forward(args)...)}); + } + // aliased column expressions, explicit or implicitly numbered template = true> auto make_cte_column(std::string name, const ColRef& /*finalColRef*/) { @@ -270,14 +285,14 @@ namespace sqlite_orm { std::vector columnNames, const ColRefs& finalColRefs, std::index_sequence) { - return make_table( + return make_cte_table( std::move(tableName), make_cte_column>(std::move(columnNames.at(CIs)), get(finalColRefs))...); } template - auto make_cte_table(const DBOs& dbObjects, const CTE& cte) { + auto make_cte_db_object(const DBOs& dbObjects, const CTE& cte) { using cte_type = CTE; auto subSelect = get_cte_driving_subselect(cte.subselect); @@ -315,7 +330,7 @@ namespace sqlite_orm { decltype(auto) make_recursive_cte_db_objects(const DBOs& dbObjects, const common_table_expressions& cte, std::index_sequence) { - auto tbl = make_cte_table(dbObjects, get(cte)); + auto tbl = make_cte_db_object(dbObjects, get(cte)); if constexpr (sizeof...(In) > 0) { return make_recursive_cte_db_objects( diff --git a/dev/schema/mapped_object.h b/dev/schema/mapped_object.h deleted file mode 100644 index 05a552786..000000000 --- a/dev/schema/mapped_object.h +++ /dev/null @@ -1,148 +0,0 @@ -#pragma once - -#ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::is_member_pointer -#include // std::string -#include // std::tuple_element -#include // std::move -#endif - -#include "../functional/cxx_type_traits_polyfill.h" -#include "../functional/cxx_functional_polyfill.h" -#include "../functional/mpl.h" -#include "../functional/index_sequence_util.h" -#include "../tuple_helper/tuple_filter.h" -#include "../tuple_helper/tuple_traits.h" -#include "../tuple_helper/tuple_iteration.h" -#include "../tuple_helper/tuple_transformer.h" -#include "../member_traits/member_traits.h" -#include "../field_of.h" -#include "../type_traits.h" -#include "../alias_traits.h" -#include "../constraints.h" -#include "column.h" - -namespace sqlite_orm { - - namespace internal { - - struct basic_table { - - /** - * Table name. - */ - std::string name; - }; - - /** - * Base for a mapped schema object aka table, view. - */ - template - struct mapped_object_t : basic_table { - using object_type = O; - using elements_type = std::tuple; - - elements_type elements; - - /* - * Returns the number of elements of the specified type. - */ - template class Trait> - static constexpr int count_of() { - using sequence_of = filter_tuple_sequence_t; - return int(sequence_of::size()); - } - - /* - * Returns the number of columns having the specified constraint trait. - */ - template class Trait> - static constexpr int count_of_columns_with() { - using filtered_index_sequence = col_index_sequence_with; - return int(filtered_index_sequence::size()); - } - - /* - * Returns the number of columns having the specified constraint trait. - */ - template class Trait> - static constexpr int count_of_columns_excluding() { - using excluded_col_index_sequence = col_index_sequence_excluding; - return int(excluded_col_index_sequence::size()); - } - - /** - * Function used to get field value from object by mapped member pointer/setter/getter. - * - * For a setter the corresponding getter has to be searched, - * so the method returns a pointer to the field as returned by the found getter. - * Otherwise the method invokes the member pointer and returns its result. - */ - template = true> - decltype(auto) object_field_value(const object_type& object, M memberPointer) const { - return polyfill::invoke(memberPointer, object); - } - - template = true> - const member_field_type_t* object_field_value(const object_type& object, M memberPointer) const { - using field_type = member_field_type_t; - const field_type* res = nullptr; - iterate_tuple(this->elements, - col_index_sequence_with_field_type{}, - call_as_template_base([&res, &memberPointer, &object](const auto& column) { - if (compare_fields(column.setter, memberPointer)) { - res = &polyfill::invoke(column.member_pointer, object); - } - })); - return res; - } - - /** - * Searches column name by class member pointer passed as the first argument. - * @return column name or empty string if nothing found. - */ - template = true> - const std::string* find_column_name(M memberPointer) const { - using field_type = member_field_type_t; - - const std::string* res = nullptr; - iterate_tuple(this->elements, - col_index_sequence_with_field_type{}, - [&res, memberPointer](auto& column) { - if (compare_fields(column.member_pointer, memberPointer) || - compare_fields(column.setter, memberPointer)) { - res = &column.name; - } - }); - return res; - } - - /** - * Call passed lambda with all defined columns. - * @param lambda Lambda called for each column. Function signature: `void(auto& column)` - */ - template - void for_each_column(L&& lambda) const { - iterate_tuple(this->elements, col_index_sequence_of{}, lambda); - } - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template class OpTraitFn, class L> - void for_each_column_excluding(L&& lambda) const { - iterate_tuple(this->elements, col_index_sequence_excluding{}, lambda); - } - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template = true> - void for_each_column_excluding(L&& lambda) const { - this->template for_each_column_excluding(lambda); - } - }; - } -} diff --git a/dev/schema/table.h b/dev/schema/table.h index a34a7db28..6a900764f 100644 --- a/dev/schema/table.h +++ b/dev/schema/table.h @@ -19,10 +19,9 @@ #include "../member_traits/member_traits.h" #include "../field_of.h" #include "../type_traits.h" -#include "../alias_traits.h" #include "../constraints.h" #include "../table_info.h" -#include "mapped_object.h" +#include "table_base.h" #include "index.h" #include "column.h" @@ -43,100 +42,22 @@ namespace sqlite_orm { check_if_is_template>, T>; -#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) - /** - * A subselect mapper's CTE moniker, void otherwise. - */ - template - using moniker_of_or_void_t = polyfill::detected_or_t; - - /** - * If O is a subselect_mapper then returns its nested type name O::cte_moniker_type, - * otherwise O itself is a regular object type to be mapped. - */ - template - using mapped_object_type_for_t = polyfill::detected_or_t; -#endif - /** * Table definition. */ template - struct table_t : basic_table { -#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) - // this typename is used in contexts where it is known that the 'table' holds a subselect_mapper - // instead of a regular object type - using cte_mapper_type = O; - using cte_moniker_type = moniker_of_or_void_t; - using object_type = mapped_object_type_for_t; -#else - using object_type = O; -#endif - using elements_type = std::tuple; + struct table_t : table_base, mapped_object_base { + using base_type = mapped_object_base; + using object_type = typename base_type::object_type; + using elements_type = typename base_type::elements_type; static constexpr bool is_without_rowid_v = WithoutRowId; - using is_without_rowid = polyfill::bool_constant; - elements_type elements; - table_t without_rowid() const { return {this->name, this->elements}; } - /* - * Returns the number of elements of the specified type. - */ - template class Trait> - static constexpr int count_of() { - using sequence_of = filter_tuple_sequence_t; - return int(sequence_of::size()); - } - - /* - * Returns the number of columns having the specified constraint trait. - */ - template class Trait> - static constexpr int count_of_columns_with() { - using filtered_index_sequence = col_index_sequence_with; - return int(filtered_index_sequence::size()); - } - - /* - * Returns the number of columns having the specified constraint trait. - */ - template class Trait> - static constexpr int count_of_columns_excluding() { - using excluded_col_index_sequence = col_index_sequence_excluding; - return int(excluded_col_index_sequence::size()); - } - - /** - * Function used to get field value from object by mapped member pointer/setter/getter. - * - * For a setter the corresponding getter has to be searched, - * so the method returns a pointer to the field as returned by the found getter. - * Otherwise the method invokes the member pointer and returns its result. - */ - template = true> - decltype(auto) object_field_value(const object_type& object, M memberPointer) const { - return polyfill::invoke(memberPointer, object); - } - - template = true> - const member_field_type_t* object_field_value(const object_type& object, M memberPointer) const { - using field_type = member_field_type_t; - const field_type* res = nullptr; - iterate_tuple(this->elements, - col_index_sequence_with_field_type{}, - call_as_template_base([&res, &memberPointer, &object](const auto& column) { - if (compare_fields(column.setter, memberPointer)) { - res = &polyfill::invoke(column.member_pointer, object); - } - })); - return res; - } - const basic_generated_always::storage_type* find_column_generated_storage_type([[maybe_unused]] const std::string& name) const { const basic_generated_always::storage_type* result = nullptr; @@ -211,26 +132,6 @@ namespace sqlite_orm { }); } - /** - * Searches column name by class member pointer passed as the first argument. - * @return column name or empty string if nothing found. - */ - template = true> - const std::string* find_column_name(M memberPointer) const { - using field_type = member_field_type_t; - - const std::string* res = nullptr; - iterate_tuple(this->elements, - col_index_sequence_with_field_type{}, - [&res, memberPointer](auto& column) { - if (compare_fields(column.member_pointer, memberPointer) || - compare_fields(column.setter, memberPointer)) { - res = &column.name; - } - }); - return res; - } - /** * Call passed lambda with all defined foreign keys. * @param lambda Lambda called for each column. Function signature: `void(auto& column)` @@ -251,33 +152,6 @@ namespace sqlite_orm { iterate_tuple(this->elements, filtered_index_sequence{}, lambda); } - /** - * Call passed lambda with all defined columns. - * @param lambda Lambda called for each column. Function signature: `void(auto& column)` - */ - template - void for_each_column(L&& lambda) const { - iterate_tuple(this->elements, col_index_sequence_of{}, lambda); - } - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template class OpTraitFn, class L> - void for_each_column_excluding(L&& lambda) const { - iterate_tuple(this->elements, col_index_sequence_excluding{}, lambda); - } - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template = true> - void for_each_column_excluding(L&& lambda) const { - this->template for_each_column_excluding(lambda); - } - std::vector get_table_info() const; }; @@ -288,107 +162,25 @@ namespace sqlite_orm { struct is_table> : std::true_type {}; template - struct virtual_table_t : basic_table { - using module_details_type = M; - using object_type = typename module_details_type::object_type; - using elements_type = typename module_details_type::columns_type; + struct virtual_table : table_base, M { + using module_type = M; + using object_type = typename module_type::object_type; + using elements_type = typename module_type::elements_type; static constexpr bool is_without_rowid_v = false; using is_without_rowid = polyfill::bool_constant; - module_details_type module_details; - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template class OpTraitFn, class L> - void for_each_column_excluding(L&& lambda) const { - this->module_details.template for_each_column_excluding(lambda); - } - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template = true> - void for_each_column_excluding(L&& lambda) const { - this->module_details.template for_each_column_excluding(lambda); - } - - /** - * Call passed lambda with all defined columns. - * @param lambda Lambda called for each column. Function signature: `void(auto& column)` - */ - template - void for_each_column(L&& lambda) const { - this->module_details.for_each_column(lambda); - } - - template = true> - const std::string* find_column_name(MP memberPointer) const { - return this->module_details.find_column_name(memberPointer); + const module_type& module() const { + return *this; } }; - template - struct is_virtual_table : std::false_type {}; - - template - struct is_virtual_table> : std::true_type {}; - #if SQLITE_VERSION_NUMBER >= 3009000 template - struct using_fts5_t { + struct fts5_module : mapped_columns_mixin { + using base_type = mapped_columns_mixin; using object_type = T; - using columns_type = std::tuple; - - columns_type columns; - - using_fts5_t(columns_type columns) : columns(std::move(columns)) {} - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template class OpTraitFn, class L> - void for_each_column_excluding(L&& lambda) const { - iterate_tuple(this->columns, col_index_sequence_excluding{}, lambda); - } - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template = true> - void for_each_column_excluding(L&& lambda) const { - this->template for_each_column_excluding(lambda); - } - - /** - * Call passed lambda with all defined columns. - * @param lambda Lambda called for each column. Function signature: `void(auto& column)` - */ - template - void for_each_column(L&& lambda) const { - iterate_tuple(this->columns, col_index_sequence_of{}, lambda); - } - - template = true> - const std::string* find_column_name(M memberPointer) const { - using field_type = member_field_type_t; - - const std::string* res = nullptr; - iterate_tuple(this->columns, - col_index_sequence_with_field_type{}, - [&res, memberPointer](auto& column) { - if (compare_fields(column.member_pointer, memberPointer) || - compare_fields(column.setter, memberPointer)) { - res = &column.name; - } - }); - return res; - } + using elements_type = typename base_type::elements_type; }; #endif @@ -413,7 +205,7 @@ namespace sqlite_orm { } template - bool exists_in_composite_primary_key(const virtual_table_t& /*virtualTable*/, + bool exists_in_composite_primary_key(const virtual_table& /*virtualTable*/, const column_field& /*column*/) { return false; } @@ -423,7 +215,7 @@ namespace sqlite_orm { SQLITE_ORM_EXPORT namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3009000 template>::object_type> - internal::using_fts5_t using_fts5(Cs... columns) { + internal::fts5_module using_fts5(Cs... columns) { static_assert(polyfill::conjunction_v...>, "Incorrect table elements or constraints"); @@ -431,7 +223,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { } template - internal::using_fts5_t using_fts5(Cs... columns) { + internal::fts5_module using_fts5(Cs... columns) { static_assert(polyfill::conjunction_v...>, "Incorrect table elements or constraints"); @@ -480,7 +272,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #endif template - internal::virtual_table_t make_virtual_table(std::string name, M module_details) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(module_details)}); + internal::virtual_table make_virtual_table(std::string name, M module) { + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(module)}); } } diff --git a/dev/schema/table_base.h b/dev/schema/table_base.h new file mode 100644 index 000000000..9d9f9f488 --- /dev/null +++ b/dev/schema/table_base.h @@ -0,0 +1,148 @@ +#pragma once + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#include // std::is_member_pointer +#include // std::string +#include // std::tuple +#endif + +#include "../functional/cxx_functional_polyfill.h" +#include "../functional/mpl.h" +#include "../tuple_helper/tuple_filter.h" +#include "../tuple_helper/tuple_iteration.h" +#include "../member_traits/member_traits.h" +#include "../type_traits.h" +#include "../field_of.h" +#include "../constraints.h" +#include "column.h" + +namespace sqlite_orm::internal { + + struct table_base { + + /** + * Table name. + */ + std::string name; + }; + + /** + * Mixin for fields of any mapped schema object, i.e. table or view, or any virtual or temporary table. + */ + template + struct mapped_columns_mixin { + using elements_type = std::tuple; + + elements_type elements; + + /* + * Returns the number of elements of the specified type. + */ + template class Trait> + static constexpr int count_of() { + using sequence_of = filter_tuple_sequence_t; + return int(sequence_of::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_with() { + using col_index_sequence = col_index_sequence_with; + return int(col_index_sequence::size()); + } + + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_excluding() { + using excluded_col_index_sequence = col_index_sequence_excluding; + return int(excluded_col_index_sequence::size()); + } + + /** + * Call passed lambda with all defined columns. + * @param lambda Lambda called for each column. Function signature: `void(auto& column)` + */ + template + void for_each_column(L&& lambda) const { + iterate_tuple(this->elements, col_index_sequence_of{}, lambda); + } + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template class OpTraitFn, class L> + void for_each_column_excluding(L&& lambda) const { + iterate_tuple(this->elements, col_index_sequence_excluding{}, lambda); + } + + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template = true> + void for_each_column_excluding(L&& lambda) const { + this->template for_each_column_excluding(lambda); + } + + /** + * Searches column name by class member pointer passed as the first argument. + * @return column name or empty string if nothing found. + */ + template = true> + const std::string* find_column_name(M memberPointer) const { + using field_type = member_field_type_t; + + const std::string* res = nullptr; + iterate_tuple(this->elements, + col_index_sequence_with_field_type{}, + [&res, memberPointer](auto& column) { + if (compare_fields(column.member_pointer, memberPointer) || + compare_fields(column.setter, memberPointer)) { + res = &column.name; + } + }); + return res; + } + }; + + /** + * Base for a mapped schema object aka table, view. + */ + template + struct mapped_object_base : mapped_columns_mixin { + using base_type = mapped_columns_mixin; + using object_type = O; + using elements_type = typename base_type::elements_type; + + /** + * Function used to get field value from object by mapped member pointer/setter/getter. + * + * For a setter the corresponding getter has to be searched, + * so the method returns a pointer to the field as returned by the found getter. + * Otherwise the method invokes the member pointer and returns its result. + */ + template = true> + decltype(auto) object_field_value(const object_type& object, M memberPointer) const { + return polyfill::invoke(memberPointer, object); + } + + template = true> + const member_field_type_t* object_field_value(const object_type& object, M memberPointer) const { + using field_type = member_field_type_t; + const field_type* res = nullptr; + iterate_tuple(this->elements, + col_index_sequence_with_field_type{}, + call_as_template_base([&res, &memberPointer, &object](const auto& column) { + if (compare_fields(column.setter, memberPointer)) { + res = &polyfill::invoke(column.member_pointer, object); + } + })); + return res; + } + }; +} diff --git a/dev/schema/triggers.h b/dev/schema/triggers.h index 2e6ecebd9..0ceb4738d 100644 --- a/dev/schema/triggers.h +++ b/dev/schema/triggers.h @@ -46,7 +46,7 @@ namespace sqlite_orm { } }; - struct base_trigger { + struct trigger_base { /** * Name of the trigger */ @@ -59,7 +59,7 @@ namespace sqlite_orm { * S is the list of trigger statments */ template - struct trigger_t : base_trigger { + struct trigger_t : trigger_base { using object_type = void; using elements_type = typename partial_trigger_t::statements_type; diff --git a/dev/schema/view.h b/dev/schema/view.h index e09b3d85d..7dc5f0f3f 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -12,10 +12,11 @@ #endif #endif +#include "../functional/cxx_type_traits_polyfill.h" #include "../column_pointer.h" #include "../select_constraints.h" #include "column.h" -#include "mapped_object.h" +#include "table_base.h" #ifdef SQLITE_ORM_WITH_VIEW #if __cpp_impl_reflection >= 202500L @@ -61,8 +62,8 @@ namespace sqlite_orm::internal { * View definition, mapping an aggregate object type to a corresponding select statement. */ template - struct view_t : mapped_object_t { - using base_type = mapped_object_t; + struct view_t : table_base, mapped_object_base { + using base_type = mapped_object_base; using object_type = typename base_type::object_type; using elements_type = typename base_type::elements_type; using select_type = Select; diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index f9714f925..a1f87b276 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -1974,8 +1974,8 @@ namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3009000 template - struct statement_serializer, void> { - using statement_type = using_fts5_t; + struct statement_serializer, void> { + using statement_type = fts5_module; template SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, @@ -1984,15 +1984,15 @@ namespace sqlite_orm { ss << "USING FTS5("; auto subContext = context; subContext.omit_column_type = true; - ss << streaming_expressions_tuple(statement.columns, subContext) << ")"; + ss << streaming_expressions_tuple(statement.elements, subContext) << ")"; return ss.str(); } }; #endif template - struct statement_serializer, void> { - using statement_type = virtual_table_t; + struct statement_serializer, void> { + using statement_type = virtual_table; template SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, @@ -2000,7 +2000,7 @@ namespace sqlite_orm { std::stringstream ss; ss << "CREATE VIRTUAL TABLE IF NOT EXISTS "; ss << streaming_identifier(statement.name) << ' '; - ss << serialize(statement.module_details, context); + ss << serialize(statement.module(), context); return ss.str(); } }; diff --git a/dev/storage.h b/dev/storage.h index 4abbe515e..64908d5c8 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -1112,7 +1112,7 @@ namespace sqlite_orm { protected: template - sync_schema_result schema_status(const virtual_table_t&, sqlite3*, bool, bool*) { + sync_schema_result schema_status(const virtual_table&, sqlite3*, bool, bool*) { return sync_schema_result::already_in_sync; } @@ -1221,7 +1221,7 @@ namespace sqlite_orm { } template - sync_schema_result sync_dbo(const virtual_table_t& virtualTable, sqlite3* db, bool) { + sync_schema_result sync_dbo(const virtual_table& virtualTable, sqlite3* db, bool) { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; diff --git a/dev/storage_impl.h b/dev/storage_impl.h index 9a007d76e..487520511 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -70,7 +70,7 @@ namespace sqlite_orm { * Materialize column pointer: * 3. by moniker and alias_holder<>. * - * internal note: there's an overload for `find_column_name()` that avoids going through `table_t<>::find_column_name()` + * internal note: there's an overload for `find_column_name()` that avoids going through `cte_table<>::find_column_name()` */ template = true> constexpr decltype(auto) materialize_column_pointer(const DBOs&, @@ -117,7 +117,7 @@ namespace sqlite_orm { static_assert(colalias_index::value < std::tuple_size_v, "No such column mapped into the CTE."); - // note: we could "materialize" the alias to an `aliased_field<>::*` and use the regular `table_t<>::find_column_name()` mechanism; + // note: we could "materialize" the alias to an `aliased_field<>::*` and use the regular `cte_table<>::find_column_name()` mechanism; // however we have the column index already. // lookup column in table_t<>'s elements constexpr size_t ColIdx = index_sequence_value_at(column_index_sequence{}); diff --git a/dev/storage_lookup.h b/dev/storage_lookup.h index 6b9fa48bd..2ee079824 100644 --- a/dev/storage_lookup.h +++ b/dev/storage_lookup.h @@ -18,9 +18,9 @@ namespace sqlite_orm { template using db_objects_tuple = std::tuple; - struct basic_table; + struct table_base; struct index_base; - struct base_trigger; + struct trigger_base; template struct is_storage : std::false_type {}; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 2ad17b3bd..b277b73e8 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -2562,7 +2562,7 @@ namespace sqlite_orm { is_operator_argument_v::value>> = true; - struct basic_table; + struct table_base; /* * Encapsulates extracting the alias identifier of a non-alias. @@ -2581,7 +2581,7 @@ namespace sqlite_orm { return {}; } - template + template static const std::string& as_qualifier(const X& table) { return table.name; } @@ -2618,7 +2618,7 @@ namespace sqlite_orm { // for regular table aliases -> alias identifier template = true> - static std::string as_qualifier(const basic_table&) { + static std::string as_qualifier(const table_base&) { return alias_extractor::extract(); } }; @@ -10867,9 +10867,9 @@ namespace sqlite_orm { template using db_objects_tuple = std::tuple; - struct basic_table; + struct table_base; struct index_base; - struct base_trigger; + struct trigger_base; template struct is_storage : std::false_type {}; @@ -12283,172 +12283,165 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "../type_traits.h" -// #include "../alias_traits.h" - // #include "../constraints.h" // #include "../table_info.h" -// #include "mapped_object.h" +// #include "table_base.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include // std::is_member_pointer #include // std::string -#include // std::tuple_element -#include // std::move +#include // std::tuple #endif -// #include "../functional/cxx_type_traits_polyfill.h" - // #include "../functional/cxx_functional_polyfill.h" // #include "../functional/mpl.h" -// #include "../functional/index_sequence_util.h" - // #include "../tuple_helper/tuple_filter.h" -// #include "../tuple_helper/tuple_traits.h" - // #include "../tuple_helper/tuple_iteration.h" -// #include "../tuple_helper/tuple_transformer.h" - // #include "../member_traits/member_traits.h" -// #include "../field_of.h" - // #include "../type_traits.h" -// #include "../alias_traits.h" +// #include "../field_of.h" // #include "../constraints.h" // #include "column.h" -namespace sqlite_orm { - - namespace internal { - - struct basic_table { +namespace sqlite_orm::internal { - /** - * Table name. - */ - std::string name; - }; + struct table_base { /** - * Base for a mapped schema object aka table, view. + * Table name. */ - template - struct mapped_object_t : basic_table { - using object_type = O; - using elements_type = std::tuple; + std::string name; + }; - elements_type elements; + /** + * Mixin for fields of any mapped schema object, i.e. table or view, or any virtual or temporary table. + */ + template + struct mapped_columns_mixin { + using elements_type = std::tuple; - /* - * Returns the number of elements of the specified type. - */ - template class Trait> - static constexpr int count_of() { - using sequence_of = filter_tuple_sequence_t; - return int(sequence_of::size()); - } + elements_type elements; - /* - * Returns the number of columns having the specified constraint trait. - */ - template class Trait> - static constexpr int count_of_columns_with() { - using filtered_index_sequence = col_index_sequence_with; - return int(filtered_index_sequence::size()); - } + /* + * Returns the number of elements of the specified type. + */ + template class Trait> + static constexpr int count_of() { + using sequence_of = filter_tuple_sequence_t; + return int(sequence_of::size()); + } - /* - * Returns the number of columns having the specified constraint trait. - */ - template class Trait> - static constexpr int count_of_columns_excluding() { - using excluded_col_index_sequence = col_index_sequence_excluding; - return int(excluded_col_index_sequence::size()); - } + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_with() { + using col_index_sequence = col_index_sequence_with; + return int(col_index_sequence::size()); + } - /** - * Function used to get field value from object by mapped member pointer/setter/getter. - * - * For a setter the corresponding getter has to be searched, - * so the method returns a pointer to the field as returned by the found getter. - * Otherwise the method invokes the member pointer and returns its result. - */ - template = true> - decltype(auto) object_field_value(const object_type& object, M memberPointer) const { - return polyfill::invoke(memberPointer, object); - } + /* + * Returns the number of columns having the specified constraint trait. + */ + template class Trait> + static constexpr int count_of_columns_excluding() { + using excluded_col_index_sequence = col_index_sequence_excluding; + return int(excluded_col_index_sequence::size()); + } - template = true> - const member_field_type_t* object_field_value(const object_type& object, M memberPointer) const { - using field_type = member_field_type_t; - const field_type* res = nullptr; - iterate_tuple(this->elements, - col_index_sequence_with_field_type{}, - call_as_template_base([&res, &memberPointer, &object](const auto& column) { - if (compare_fields(column.setter, memberPointer)) { - res = &polyfill::invoke(column.member_pointer, object); - } - })); - return res; - } + /** + * Call passed lambda with all defined columns. + * @param lambda Lambda called for each column. Function signature: `void(auto& column)` + */ + template + void for_each_column(L&& lambda) const { + iterate_tuple(this->elements, col_index_sequence_of{}, lambda); + } - /** - * Searches column name by class member pointer passed as the first argument. - * @return column name or empty string if nothing found. - */ - template = true> - const std::string* find_column_name(M memberPointer) const { - using field_type = member_field_type_t; + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template class OpTraitFn, class L> + void for_each_column_excluding(L&& lambda) const { + iterate_tuple(this->elements, col_index_sequence_excluding{}, lambda); + } - const std::string* res = nullptr; - iterate_tuple(this->elements, - col_index_sequence_with_field_type{}, - [&res, memberPointer](auto& column) { - if (compare_fields(column.member_pointer, memberPointer) || - compare_fields(column.setter, memberPointer)) { - res = &column.name; - } - }); - return res; - } + /** + * Call passed lambda with columns not having the specified constraint trait `OpTrait`. + * @param lambda Lambda called for each column. + */ + template = true> + void for_each_column_excluding(L&& lambda) const { + this->template for_each_column_excluding(lambda); + } - /** - * Call passed lambda with all defined columns. - * @param lambda Lambda called for each column. Function signature: `void(auto& column)` + /** + * Searches column name by class member pointer passed as the first argument. + * @return column name or empty string if nothing found. */ - template - void for_each_column(L&& lambda) const { - iterate_tuple(this->elements, col_index_sequence_of{}, lambda); - } + template = true> + const std::string* find_column_name(M memberPointer) const { + using field_type = member_field_type_t; + + const std::string* res = nullptr; + iterate_tuple(this->elements, + col_index_sequence_with_field_type{}, + [&res, memberPointer](auto& column) { + if (compare_fields(column.member_pointer, memberPointer) || + compare_fields(column.setter, memberPointer)) { + res = &column.name; + } + }); + return res; + } + }; - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template class OpTraitFn, class L> - void for_each_column_excluding(L&& lambda) const { - iterate_tuple(this->elements, col_index_sequence_excluding{}, lambda); - } + /** + * Base for a mapped schema object aka table, view. + */ + template + struct mapped_object_base : mapped_columns_mixin { + using base_type = mapped_columns_mixin; + using object_type = O; + using elements_type = typename base_type::elements_type; - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template = true> - void for_each_column_excluding(L&& lambda) const { - this->template for_each_column_excluding(lambda); - } - }; - } + /** + * Function used to get field value from object by mapped member pointer/setter/getter. + * + * For a setter the corresponding getter has to be searched, + * so the method returns a pointer to the field as returned by the found getter. + * Otherwise the method invokes the member pointer and returns its result. + */ + template = true> + decltype(auto) object_field_value(const object_type& object, M memberPointer) const { + return polyfill::invoke(memberPointer, object); + } + + template = true> + const member_field_type_t* object_field_value(const object_type& object, M memberPointer) const { + using field_type = member_field_type_t; + const field_type* res = nullptr; + iterate_tuple(this->elements, + col_index_sequence_with_field_type{}, + call_as_template_base([&res, &memberPointer, &object](const auto& column) { + if (compare_fields(column.setter, memberPointer)) { + res = &polyfill::invoke(column.member_pointer, object); + } + })); + return res; + } + }; } // #include "index.h" @@ -12604,100 +12597,22 @@ namespace sqlite_orm { check_if_is_template>, T>; -#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) - /** - * A subselect mapper's CTE moniker, void otherwise. - */ - template - using moniker_of_or_void_t = polyfill::detected_or_t; - - /** - * If O is a subselect_mapper then returns its nested type name O::cte_moniker_type, - * otherwise O itself is a regular object type to be mapped. - */ - template - using mapped_object_type_for_t = polyfill::detected_or_t; -#endif - /** * Table definition. */ template - struct table_t : basic_table { -#if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) - // this typename is used in contexts where it is known that the 'table' holds a subselect_mapper - // instead of a regular object type - using cte_mapper_type = O; - using cte_moniker_type = moniker_of_or_void_t; - using object_type = mapped_object_type_for_t; -#else - using object_type = O; -#endif - using elements_type = std::tuple; + struct table_t : table_base, mapped_object_base { + using base_type = mapped_object_base; + using object_type = typename base_type::object_type; + using elements_type = typename base_type::elements_type; static constexpr bool is_without_rowid_v = WithoutRowId; - using is_without_rowid = polyfill::bool_constant; - elements_type elements; - table_t without_rowid() const { return {this->name, this->elements}; } - /* - * Returns the number of elements of the specified type. - */ - template class Trait> - static constexpr int count_of() { - using sequence_of = filter_tuple_sequence_t; - return int(sequence_of::size()); - } - - /* - * Returns the number of columns having the specified constraint trait. - */ - template class Trait> - static constexpr int count_of_columns_with() { - using filtered_index_sequence = col_index_sequence_with; - return int(filtered_index_sequence::size()); - } - - /* - * Returns the number of columns having the specified constraint trait. - */ - template class Trait> - static constexpr int count_of_columns_excluding() { - using excluded_col_index_sequence = col_index_sequence_excluding; - return int(excluded_col_index_sequence::size()); - } - - /** - * Function used to get field value from object by mapped member pointer/setter/getter. - * - * For a setter the corresponding getter has to be searched, - * so the method returns a pointer to the field as returned by the found getter. - * Otherwise the method invokes the member pointer and returns its result. - */ - template = true> - decltype(auto) object_field_value(const object_type& object, M memberPointer) const { - return polyfill::invoke(memberPointer, object); - } - - template = true> - const member_field_type_t* object_field_value(const object_type& object, M memberPointer) const { - using field_type = member_field_type_t; - const field_type* res = nullptr; - iterate_tuple(this->elements, - col_index_sequence_with_field_type{}, - call_as_template_base([&res, &memberPointer, &object](const auto& column) { - if (compare_fields(column.setter, memberPointer)) { - res = &polyfill::invoke(column.member_pointer, object); - } - })); - return res; - } - const basic_generated_always::storage_type* find_column_generated_storage_type([[maybe_unused]] const std::string& name) const { const basic_generated_always::storage_type* result = nullptr; @@ -12772,26 +12687,6 @@ namespace sqlite_orm { }); } - /** - * Searches column name by class member pointer passed as the first argument. - * @return column name or empty string if nothing found. - */ - template = true> - const std::string* find_column_name(M memberPointer) const { - using field_type = member_field_type_t; - - const std::string* res = nullptr; - iterate_tuple(this->elements, - col_index_sequence_with_field_type{}, - [&res, memberPointer](auto& column) { - if (compare_fields(column.member_pointer, memberPointer) || - compare_fields(column.setter, memberPointer)) { - res = &column.name; - } - }); - return res; - } - /** * Call passed lambda with all defined foreign keys. * @param lambda Lambda called for each column. Function signature: `void(auto& column)` @@ -12812,33 +12707,6 @@ namespace sqlite_orm { iterate_tuple(this->elements, filtered_index_sequence{}, lambda); } - /** - * Call passed lambda with all defined columns. - * @param lambda Lambda called for each column. Function signature: `void(auto& column)` - */ - template - void for_each_column(L&& lambda) const { - iterate_tuple(this->elements, col_index_sequence_of{}, lambda); - } - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template class OpTraitFn, class L> - void for_each_column_excluding(L&& lambda) const { - iterate_tuple(this->elements, col_index_sequence_excluding{}, lambda); - } - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template = true> - void for_each_column_excluding(L&& lambda) const { - this->template for_each_column_excluding(lambda); - } - std::vector get_table_info() const; }; @@ -12849,107 +12717,25 @@ namespace sqlite_orm { struct is_table> : std::true_type {}; template - struct virtual_table_t : basic_table { - using module_details_type = M; - using object_type = typename module_details_type::object_type; - using elements_type = typename module_details_type::columns_type; + struct virtual_table : table_base, M { + using module_type = M; + using object_type = typename module_type::object_type; + using elements_type = typename module_type::elements_type; static constexpr bool is_without_rowid_v = false; using is_without_rowid = polyfill::bool_constant; - module_details_type module_details; - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template class OpTraitFn, class L> - void for_each_column_excluding(L&& lambda) const { - this->module_details.template for_each_column_excluding(lambda); - } - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template = true> - void for_each_column_excluding(L&& lambda) const { - this->module_details.template for_each_column_excluding(lambda); - } - - /** - * Call passed lambda with all defined columns. - * @param lambda Lambda called for each column. Function signature: `void(auto& column)` - */ - template - void for_each_column(L&& lambda) const { - this->module_details.for_each_column(lambda); - } - - template = true> - const std::string* find_column_name(MP memberPointer) const { - return this->module_details.find_column_name(memberPointer); + const module_type& module() const { + return *this; } }; - template - struct is_virtual_table : std::false_type {}; - - template - struct is_virtual_table> : std::true_type {}; - #if SQLITE_VERSION_NUMBER >= 3009000 template - struct using_fts5_t { + struct fts5_module : mapped_columns_mixin { + using base_type = mapped_columns_mixin; using object_type = T; - using columns_type = std::tuple; - - columns_type columns; - - using_fts5_t(columns_type columns) : columns(std::move(columns)) {} - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template class OpTraitFn, class L> - void for_each_column_excluding(L&& lambda) const { - iterate_tuple(this->columns, col_index_sequence_excluding{}, lambda); - } - - /** - * Call passed lambda with columns not having the specified constraint trait `OpTrait`. - * @param lambda Lambda called for each column. - */ - template = true> - void for_each_column_excluding(L&& lambda) const { - this->template for_each_column_excluding(lambda); - } - - /** - * Call passed lambda with all defined columns. - * @param lambda Lambda called for each column. Function signature: `void(auto& column)` - */ - template - void for_each_column(L&& lambda) const { - iterate_tuple(this->columns, col_index_sequence_of{}, lambda); - } - - template = true> - const std::string* find_column_name(M memberPointer) const { - using field_type = member_field_type_t; - - const std::string* res = nullptr; - iterate_tuple(this->columns, - col_index_sequence_with_field_type{}, - [&res, memberPointer](auto& column) { - if (compare_fields(column.member_pointer, memberPointer) || - compare_fields(column.setter, memberPointer)) { - res = &column.name; - } - }); - return res; - } + using elements_type = typename base_type::elements_type; }; #endif @@ -12974,7 +12760,7 @@ namespace sqlite_orm { } template - bool exists_in_composite_primary_key(const virtual_table_t& /*virtualTable*/, + bool exists_in_composite_primary_key(const virtual_table& /*virtualTable*/, const column_field& /*column*/) { return false; } @@ -12984,7 +12770,7 @@ namespace sqlite_orm { SQLITE_ORM_EXPORT namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3009000 template>::object_type> - internal::using_fts5_t using_fts5(Cs... columns) { + internal::fts5_module using_fts5(Cs... columns) { static_assert(polyfill::conjunction_v...>, "Incorrect table elements or constraints"); @@ -12992,7 +12778,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { } template - internal::using_fts5_t using_fts5(Cs... columns) { + internal::fts5_module using_fts5(Cs... columns) { static_assert(polyfill::conjunction_v...>, "Incorrect table elements or constraints"); @@ -13041,8 +12827,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #endif template - internal::virtual_table_t make_virtual_table(std::string name, M module_details) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(module_details)}); + internal::virtual_table make_virtual_table(std::string name, M module) { + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(module)}); } } @@ -13060,13 +12846,15 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #endif #endif +// #include "../functional/cxx_type_traits_polyfill.h" + // #include "../column_pointer.h" // #include "../select_constraints.h" // #include "column.h" -// #include "mapped_object.h" +// #include "table_base.h" #ifdef SQLITE_ORM_WITH_VIEW #if __cpp_impl_reflection >= 202500L @@ -13112,8 +12900,8 @@ namespace sqlite_orm::internal { * View definition, mapping an aggregate object type to a corresponding select statement. */ template - struct view_t : mapped_object_t { - using base_type = mapped_object_t; + struct view_t : table_base, mapped_object_base { + using base_type = mapped_object_base; using object_type = typename base_type::object_type; using elements_type = typename base_type::elements_type; using select_type = Select; @@ -13310,7 +13098,7 @@ namespace sqlite_orm { * Materialize column pointer: * 3. by moniker and alias_holder<>. * - * internal note: there's an overload for `find_column_name()` that avoids going through `table_t<>::find_column_name()` + * internal note: there's an overload for `find_column_name()` that avoids going through `cte_table<>::find_column_name()` */ template = true> constexpr decltype(auto) materialize_column_pointer(const DBOs&, @@ -13357,7 +13145,7 @@ namespace sqlite_orm { static_assert(colalias_index::value < std::tuple_size_v, "No such column mapped into the CTE."); - // note: we could "materialize" the alias to an `aliased_field<>::*` and use the regular `table_t<>::find_column_name()` mechanism; + // note: we could "materialize" the alias to an `aliased_field<>::*` and use the regular `cte_table<>::find_column_name()` mechanism; // however we have the column index already. // lookup column in table_t<>'s elements constexpr size_t ColIdx = index_sequence_value_at(column_index_sequence{}); @@ -20032,7 +19820,7 @@ namespace sqlite_orm { if constexpr (is_alias::value) { collectedExpressions.push_back(quote_identifier(alias_extractor::extract()) + ".*"); } else if (!context.omit_table_name) { - const basic_table& table = pick_table>(context.db_objects); + const table_base& table = pick_table>(context.db_objects); collectedExpressions.push_back(quote_identifier(table.name) + ".*"); } else { collectedExpressions.emplace_back("*"); @@ -20489,7 +20277,7 @@ namespace sqlite_orm { } }; - struct base_trigger { + struct trigger_base { /** * Name of the trigger */ @@ -20502,7 +20290,7 @@ namespace sqlite_orm { * S is the list of trigger statments */ template - struct trigger_t : base_trigger { + struct trigger_t : trigger_base { using object_type = void; using elements_type = typename partial_trigger_t::statements_type; @@ -22649,8 +22437,8 @@ namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3009000 template - struct statement_serializer, void> { - using statement_type = using_fts5_t; + struct statement_serializer, void> { + using statement_type = fts5_module; template SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, @@ -22659,15 +22447,15 @@ namespace sqlite_orm { ss << "USING FTS5("; auto subContext = context; subContext.omit_column_type = true; - ss << streaming_expressions_tuple(statement.columns, subContext) << ")"; + ss << streaming_expressions_tuple(statement.elements, subContext) << ")"; return ss.str(); } }; #endif template - struct statement_serializer, void> { - using statement_type = virtual_table_t; + struct statement_serializer, void> { + using statement_type = virtual_table; template SQLITE_ORM_STATIC_CALLOP std::string operator()(const statement_type& statement, @@ -22675,7 +22463,7 @@ namespace sqlite_orm { std::stringstream ss; ss << "CREATE VIRTUAL TABLE IF NOT EXISTS "; ss << streaming_identifier(statement.name) << ' '; - ss << serialize(statement.module_details, context); + ss << serialize(statement.module(), context); return ss.str(); } }; @@ -23202,7 +22990,7 @@ namespace sqlite_orm { // #include "select_constraints.h" -// #include "schema/table.h" +// #include "schema/table_base.h" // #include "alias.h" @@ -23372,6 +23160,21 @@ namespace sqlite_orm { FinalColRefs, Result>::type; + template + struct cte_table : table_base, mapped_columns_mixin { + using base_type = mapped_columns_mixin; + using cte_mapper_type = Mapper; + using cte_moniker_type = typename cte_mapper_type::cte_moniker_type; + using object_type = cte_moniker_type; + using elements_type = typename base_type::elements_type; + }; + + template + cte_table make_cte_table(std::string name, Cs... args) { + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( + return {std::move(name), std::make_tuple(std::forward(args)...)}); + } + // aliased column expressions, explicit or implicitly numbered template = true> auto make_cte_column(std::string name, const ColRef& /*finalColRef*/) { @@ -23578,14 +23381,14 @@ namespace sqlite_orm { std::vector columnNames, const ColRefs& finalColRefs, std::index_sequence) { - return make_table( + return make_cte_table( std::move(tableName), make_cte_column>(std::move(columnNames.at(CIs)), get(finalColRefs))...); } template - auto make_cte_table(const DBOs& dbObjects, const CTE& cte) { + auto make_cte_db_object(const DBOs& dbObjects, const CTE& cte) { using cte_type = CTE; auto subSelect = get_cte_driving_subselect(cte.subselect); @@ -23623,7 +23426,7 @@ namespace sqlite_orm { decltype(auto) make_recursive_cte_db_objects(const DBOs& dbObjects, const common_table_expressions& cte, std::index_sequence) { - auto tbl = make_cte_table(dbObjects, get(cte)); + auto tbl = make_cte_db_object(dbObjects, get(cte)); if constexpr (sizeof...(In) > 0) { return make_recursive_cte_db_objects( @@ -24766,7 +24569,7 @@ namespace sqlite_orm { protected: template - sync_schema_result schema_status(const virtual_table_t&, sqlite3*, bool, bool*) { + sync_schema_result schema_status(const virtual_table&, sqlite3*, bool, bool*) { return sync_schema_result::already_in_sync; } @@ -24875,7 +24678,7 @@ namespace sqlite_orm { } template - sync_schema_result sync_dbo(const virtual_table_t& virtualTable, sqlite3* db, bool) { + sync_schema_result sync_dbo(const virtual_table& virtualTable, sqlite3* db, bool) { using context_t = serializer_context; const auto res = sync_schema_result::already_in_sync; diff --git a/tests/statement_serializer_tests/column_names.cpp b/tests/statement_serializer_tests/column_names.cpp index 31bbb9dc8..65381044f 100644 --- a/tests/statement_serializer_tests/column_names.cpp +++ b/tests/statement_serializer_tests/column_names.cpp @@ -168,7 +168,8 @@ TEST_CASE("statement_serializer column names") { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES SECTION("cte") { auto dbObjects2 = - internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, 1_ctealias().as(select(1)))); + internal::db_objects_cat(dbObjects, + internal::make_cte_db_object(dbObjects, 1_ctealias().as(select(1)))); using context_t = internal::serializer_context; context_t context{dbObjects2}; context.omit_table_name = false; diff --git a/tests/statement_serializer_tests/select_constraints.cpp b/tests/statement_serializer_tests/select_constraints.cpp index b0d05c119..0ece294f8 100644 --- a/tests/statement_serializer_tests/select_constraints.cpp +++ b/tests/statement_serializer_tests/select_constraints.cpp @@ -96,7 +96,7 @@ TEST_CASE("statement_serializer select constraints") { SECTION("from CTE") { using cte_1 = decltype(1_ctealias); auto dbObjects2 = - internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, cte().as(select(1)))); + internal::db_objects_cat(dbObjects, internal::make_cte_db_object(dbObjects, cte().as(select(1)))); using context_t = internal::serializer_context; context_t context{dbObjects2}; SECTION("without alias 1") { diff --git a/tests/statement_serializer_tests/statements/insert_replace.cpp b/tests/statement_serializer_tests/statements/insert_replace.cpp index 3a5b16f14..5ab7735f6 100644 --- a/tests/statement_serializer_tests/statements/insert_replace.cpp +++ b/tests/statement_serializer_tests/statements/insert_replace.cpp @@ -76,7 +76,7 @@ TEST_CASE("statement_serializer insert/replace") { constexpr orm_cte_moniker auto data = "data"_cte; constexpr auto cteExpression = cte().as(select(asterisk())); auto dbObjects2 = - internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, cteExpression)); + internal::db_objects_cat(dbObjects, internal::make_cte_db_object(dbObjects, cteExpression)); using context_t = internal::serializer_context; context_t context2{dbObjects2}; @@ -369,7 +369,7 @@ TEST_CASE("statement_serializer insert/replace") { constexpr orm_cte_moniker auto data = "data"_cte; constexpr auto cteExpression = cte().as(select(asterisk())); auto dbObjects2 = - internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, cteExpression)); + internal::db_objects_cat(dbObjects, internal::make_cte_db_object(dbObjects, cteExpression)); using context_t = internal::serializer_context; context_t context2{dbObjects2}; diff --git a/tests/statement_serializer_tests/statements/remove_all.cpp b/tests/statement_serializer_tests/statements/remove_all.cpp index 44c62a9d5..cee0685e8 100644 --- a/tests/statement_serializer_tests/statements/remove_all.cpp +++ b/tests/statement_serializer_tests/statements/remove_all.cpp @@ -38,7 +38,7 @@ TEST_CASE("statement_serializer remove_all") { SECTION("With clause") { constexpr orm_cte_moniker auto data = "data"_cte; constexpr auto cteExpression = cte().as(select(1)); - auto dbObjects2 = internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, cteExpression)); + auto dbObjects2 = internal::db_objects_cat(dbObjects, internal::make_cte_db_object(dbObjects, cteExpression)); using context_t = internal::serializer_context; context_t context2{dbObjects2}; diff --git a/tests/statement_serializer_tests/statements/update_all.cpp b/tests/statement_serializer_tests/statements/update_all.cpp index 160e1c944..b21e1d1d5 100644 --- a/tests/statement_serializer_tests/statements/update_all.cpp +++ b/tests/statement_serializer_tests/statements/update_all.cpp @@ -65,7 +65,7 @@ TEST_CASE("statement_serializer update_all") { SECTION("With clause") { constexpr orm_cte_moniker auto data = "data"_cte; constexpr auto cteExpression = cte().as(select(&Customer::phone, where(c(&Customer::id) == 1))); - auto dbObjects2 = internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, cteExpression)); + auto dbObjects2 = internal::db_objects_cat(dbObjects, internal::make_cte_db_object(dbObjects, cteExpression)); using context_t = internal::serializer_context; context_t context2{dbObjects2}; diff --git a/tests/static_tests/column_expression_type.cpp b/tests/static_tests/column_expression_type.cpp index 47c233089..607841335 100644 --- a/tests/static_tests/column_expression_type.cpp +++ b/tests/static_tests/column_expression_type.cpp @@ -56,9 +56,9 @@ TEST_CASE("column_expression_of_t") { #endif #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) using cte_1 = decltype(1_ctealias); - auto dbObjects2 = - internal::db_objects_cat(dbObjects, - internal::make_cte_table(dbObjects, cte().as(select(columns(&Org::id, 1))))); + auto dbObjects2 = internal::db_objects_cat( + dbObjects, + internal::make_cte_db_object(dbObjects, cte().as(select(columns(&Org::id, 1))))); using db_objects2_t = decltype(dbObjects2); runTest>(column(&Org::id)); runTest>>>(column(1_colalias)); diff --git a/tests/static_tests/column_result_t.cpp b/tests/static_tests/column_result_t.cpp index 035f80385..6f6b2fa5d 100644 --- a/tests/static_tests/column_result_t.cpp +++ b/tests/static_tests/column_result_t.cpp @@ -148,7 +148,7 @@ TEST_CASE("column_result_of_t") { // note: even though used with the CTE, &User::id doesn't need to be mapped into the CTE to make column results work; // this is because the result type is taken from the member pointer just because we can't look it up in the storage definition auto dbObjects2 = - internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, cte().as(select(1)))); + internal::db_objects_cat(dbObjects, internal::make_cte_db_object(dbObjects, cte().as(select(1)))); using db_objects2_t = decltype(dbObjects2); runTest(column(&User::id)); runTest(column(1_colalias)); diff --git a/tests/static_tests/cte.cpp b/tests/static_tests/cte.cpp index b069ce4fb..4566d2c52 100644 --- a/tests/static_tests/cte.cpp +++ b/tests/static_tests/cte.cpp @@ -89,7 +89,7 @@ TEST_CASE("CTE storage") { auto idx1 = make_unique_index("idx1_org", &Org::id); auto idx2 = make_index("idx2_org", &Org::id); auto dbObjects = tuple{idx1, idx2, table}; - auto cteTable = internal::make_cte_table(dbObjects, 1_ctealias().as(select(1))); + auto cteTable = internal::make_cte_db_object(dbObjects, 1_ctealias().as(select(1))); auto dbObjects2 = internal::db_objects_cat(dbObjects, cteTable); // note: deliberately make indexes resulting in the same index_t<> type, such that we know `db_objects_cat()` is working properly diff --git a/tests/table_name_collector.cpp b/tests/table_name_collector.cpp index 1af91184a..a704eb381 100644 --- a/tests/table_name_collector.cpp +++ b/tests/table_name_collector.cpp @@ -73,7 +73,7 @@ TEST_CASE("table name collector") { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) SECTION("from CTE") { auto dbObjects2 = - internal::db_objects_cat(dbObjects, internal::make_cte_table(dbObjects, 1_ctealias().as(select(1)))); + internal::db_objects_cat(dbObjects, internal::make_cte_db_object(dbObjects, 1_ctealias().as(select(1)))); using context_t = internal::serializer_context; context_t context{dbObjects2}; auto collector = internal::make_table_name_collector(context.db_objects); From 1c363c1f628f903b23192ec44cc60b473651551b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 26 Sep 2025 19:10:46 +0300 Subject: [PATCH 13/49] Pull in Boost PFR if available --- .clang-format | 1 + dev/functional/config.h | 4 ++++ dev/schema/view.h | 8 ++++++++ include/sqlite_orm/sqlite_orm.h | 12 ++++++++++++ 4 files changed, 25 insertions(+) diff --git a/.clang-format b/.clang-format index 558c0d0ad..dcb7a7761 100644 --- a/.clang-format +++ b/.clang-format @@ -124,6 +124,7 @@ StatementMacros: - _Pragma - Q_UNUSED - QT_REQUIRE_VERSION +WhitespaceSensitiveMacros: [SQLITE_ORM_HAS_INCLUDE] TabWidth: 4 UseTab: Never LineEnding: LF diff --git a/dev/functional/config.h b/dev/functional/config.h index e0a3be45f..0057e9f0f 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -13,6 +13,10 @@ #include #endif +#if SQLITE_ORM_HAS_INCLUDE() +#define SQLITE_ORM_HAS_BOOST_PFR +#endif + #if __cpp_lib_constexpr_functional >= 201907L #define SQLITE_ORM_CONSTEXPR_CPP20 constexpr #else diff --git a/dev/schema/view.h b/dev/schema/view.h index 7dc5f0f3f..d25dd07fc 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -12,6 +12,14 @@ #endif #endif +#ifdef SQLITE_ORM_WITH_VIEW +#if __cpp_impl_reflection < 202500L +#ifdef SQLITE_ORM_HAS_BOOST_PFR +#include +#endif +#endif +#endif + #include "../functional/cxx_type_traits_polyfill.h" #include "../column_pointer.h" #include "../select_constraints.h" diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index b277b73e8..6707b1bd1 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -244,6 +244,10 @@ using std::nullptr_t; #include #endif +#if SQLITE_ORM_HAS_INCLUDE() +#define SQLITE_ORM_HAS_BOOST_PFR +#endif + #if __cpp_lib_constexpr_functional >= 201907L #define SQLITE_ORM_CONSTEXPR_CPP20 constexpr #else @@ -12846,6 +12850,14 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #endif #endif +#ifdef SQLITE_ORM_WITH_VIEW +#if __cpp_impl_reflection < 202500L +#ifdef SQLITE_ORM_HAS_BOOST_PFR +#include +#endif +#endif +#endif + // #include "../functional/cxx_type_traits_polyfill.h" // #include "../column_pointer.h" From af22d4aa2160509e80a27d276682f106663bbbf1 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 3 Oct 2025 17:25:58 +0300 Subject: [PATCH 14/49] Include SQLite3 configuration in funcational/config.h It is better to include configuration header files of third-party libraries (including SQLite) in the library's configuration header than in the umbrella header. --- dev/functional/config.h | 2 ++ include/sqlite_orm/sqlite_orm.h | 8 +++++--- not_single_header_include/sqlite_orm/sqlite_orm.h | 2 -- third_party/amalgamate/config.json | 1 - 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dev/functional/config.h b/dev/functional/config.h index 0057e9f0f..335c659a5 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -2,6 +2,8 @@ #include "cxx_universal.h" #include "platform_definitions.h" +// pull in SQLite3 configuration early, such that version and feature macros are globally available in sqlite_orm +#include "sqlite3_config.h" #ifdef BUILD_SQLITE_ORM_MODULE #define SQLITE_ORM_EXPORT export diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 63b218cad..e142c4b68 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -8,9 +8,6 @@ __pragma(push_macro("max")) #endif // defined(_MSC_VER) #pragma once -#include -#pragma once - // #include "cxx_universal.h" /* @@ -238,6 +235,11 @@ using std::nullptr_t; #error "Unknown target platform detected" #endif +// pull in SQLite3 configuration early, such that version and feature macros are globally available in sqlite_orm +// #include "sqlite3_config.h" + +#include + #ifdef BUILD_SQLITE_ORM_MODULE #define SQLITE_ORM_EXPORT export #else diff --git a/not_single_header_include/sqlite_orm/sqlite_orm.h b/not_single_header_include/sqlite_orm/sqlite_orm.h index 8a36c4b2d..eb96d66e9 100644 --- a/not_single_header_include/sqlite_orm/sqlite_orm.h +++ b/not_single_header_include/sqlite_orm/sqlite_orm.h @@ -1,7 +1,5 @@ #pragma once #include "../../dev/functional/start_macros.h" -// pull in the SQLite3 configuration early, such that version and feature macros are globally available in sqlite_orm -#include "../../dev/functional/sqlite3_config.h" // though each header is required to include everything it needs // we include the configuration and all underlying c++ core features in order to make it universally available #include "../../dev/functional/config.h" diff --git a/third_party/amalgamate/config.json b/third_party/amalgamate/config.json index e370d0a72..a264733d7 100755 --- a/third_party/amalgamate/config.json +++ b/third_party/amalgamate/config.json @@ -3,7 +3,6 @@ "target": "include/sqlite_orm/sqlite_orm.h", "sources": [ "dev/functional/start_macros.h", - "dev/functional/sqlite3_config.h", "dev/functional/config.h", "dev/storage.h", "dev/interface_definitions.h", From 5ee55ebf969e0cffbd099279b5bd5a7d305de02d Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 3 Oct 2025 17:28:37 +0300 Subject: [PATCH 15/49] Automatically include boost-pfr if available --- appveyor.yml | 6 +++--- dev/functional/config.h | 5 +---- dev/functional/pfr_config.h | 9 +++++++++ include/sqlite_orm/sqlite_orm.h | 14 ++++++++++---- 4 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 dev/functional/pfr_config.h diff --git a/appveyor.yml b/appveyor.yml index 39677eda9..fc7b9e4f8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -113,7 +113,7 @@ for: C:\Tools\vcpkg\bootstrap-vcpkg.bat -disableMetrics C:\Tools\vcpkg\vcpkg integrate install set VCPKG_DEFAULT_TRIPLET=%platform%-windows - vcpkg install sqlite3[core,dbstat,math,json1,fts5,rtree,soundex] + vcpkg install sqlite3[core,dbstat,math,json1,fts5,rtree,soundex] boost-pfr rem The Visual Studio 2017 build worker image comes with CMake 3.16 only, and sqlite_orm will build the Catch2 dependency from source if not "%appveyor_build_worker_image%"=="Visual Studio 2017" (vcpkg install catch2) before_build: @@ -146,7 +146,7 @@ for: popd $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets - vcpkg install sqlite3[core,dbstat,math,json1,fts5,rtree,soundex] catch2 --overlay-triplets=vcpkg/triplets + vcpkg install sqlite3[core,dbstat,math,json1,fts5,rtree,soundex] catch2 boost-pfr --overlay-triplets=vcpkg/triplets before_build: - |- mkdir compile @@ -173,7 +173,7 @@ for: git clone --depth 1 --branch 2025.08.27 https://github.com/microsoft/vcpkg.git $HOME/vcpkg $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets - vcpkg install sqlite3[core,dbstat,math,json1,fts5,rtree,soundex] catch2 --overlay-triplets=vcpkg/triplets + vcpkg install sqlite3[core,dbstat,math,json1,fts5,rtree,soundex] catch2 boost-pfr --overlay-triplets=vcpkg/triplets before_build: - |- mkdir compile diff --git a/dev/functional/config.h b/dev/functional/config.h index 335c659a5..2b9475791 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -4,6 +4,7 @@ #include "platform_definitions.h" // pull in SQLite3 configuration early, such that version and feature macros are globally available in sqlite_orm #include "sqlite3_config.h" +#include "pfr_config.h" #ifdef BUILD_SQLITE_ORM_MODULE #define SQLITE_ORM_EXPORT export @@ -15,10 +16,6 @@ #include #endif -#if SQLITE_ORM_HAS_INCLUDE() -#define SQLITE_ORM_HAS_BOOST_PFR -#endif - #if __cpp_lib_constexpr_functional >= 201907L #define SQLITE_ORM_CONSTEXPR_CPP20 constexpr #else diff --git a/dev/functional/pfr_config.h b/dev/functional/pfr_config.h new file mode 100644 index 000000000..10a098a46 --- /dev/null +++ b/dev/functional/pfr_config.h @@ -0,0 +1,9 @@ +#pragma once + +#if SQLITE_ORM_HAS_INCLUDE() +#define SQLITE_ORM_HAS_BOOST_PFR +#endif + +#ifdef SQLITE_ORM_HAS_BOOST_PFR +#include +#endif diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index e142c4b68..78895cb1c 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -240,6 +240,16 @@ using std::nullptr_t; #include +// #include "pfr_config.h" + +#if SQLITE_ORM_HAS_INCLUDE() +#define SQLITE_ORM_HAS_BOOST_PFR +#endif + +#ifdef SQLITE_ORM_HAS_BOOST_PFR +#include +#endif + #ifdef BUILD_SQLITE_ORM_MODULE #define SQLITE_ORM_EXPORT export #else @@ -250,10 +260,6 @@ using std::nullptr_t; #include #endif -#if SQLITE_ORM_HAS_INCLUDE() -#define SQLITE_ORM_HAS_BOOST_PFR -#endif - #if __cpp_lib_constexpr_functional >= 201907L #define SQLITE_ORM_CONSTEXPR_CPP20 constexpr #else From 0fcfe6a1e4e0c1ef2246fc479273e2b29c859345 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 3 Oct 2025 19:22:47 +0300 Subject: [PATCH 16/49] Direct database operations for --- dev/storage.h | 11 ++-- dev/storage_base.h | 85 +++++++++++++++++++++++++---- include/sqlite_orm/sqlite_orm.h | 96 +++++++++++++++++++++++++++------ tests/logger_tests.cpp | 33 ++++++++++-- tests/storage_tests.cpp | 29 ++++++++++ 5 files changed, 219 insertions(+), 35 deletions(-) diff --git a/dev/storage.h b/dev/storage.h index 40ba669c5..7f4eadfb4 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -1128,8 +1128,8 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_VIEW - template - sync_schema_result schema_status(const view_t&, sqlite3*, bool, bool*) { + template = true> + sync_schema_result schema_status(const View&, sqlite3*, bool, bool*) { return sync_schema_result::already_in_sync; } #endif @@ -1254,8 +1254,8 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_VIEW - template - sync_schema_result sync_dbo(const view_t& view, sqlite3* db, bool) { + template = true> + sync_schema_result sync_dbo(const View& view, sqlite3* db, bool) { const auto& exprDBOs = db_objects_for_expression(this->db_objects, view.select); using context_t = serializer_context>; @@ -1395,7 +1395,8 @@ namespace sqlite_orm { return result; } - using storage_base::table_exists; // now that it is in storage_base make it into overload set + using storage_base::table_exists; + using storage_base::view_exists; template, bool> = true> prepared_statement_t prepare(DML statement) { diff --git a/dev/storage_base.h b/dev/storage_base.h index 6a6db4257..ac6462def 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -110,15 +110,6 @@ namespace sqlite_orm { this->drop_trigger_internal(triggerName, true); } - /** - * `VACUUM` query. - * More info: https://www.sqlite.org/lang_vacuum.html - */ - void vacuum() { - auto connection = this->get_connection(); - this->executor.perform_void_exec(connection.get(), "VACUUM"); - } - /** * Drops table with given name. * Calls `DROP TABLE tableName`. @@ -139,6 +130,26 @@ namespace sqlite_orm { this->drop_table_internal(connection.get(), tableName, true); } + /** + * Drops the view with the specified name. + * Calls `DROP VIEW "viewName"`. + * More info: https://www.sqlite.org/lang_droptable.html + */ + void drop_view(const std::string& tableName) { + auto connection = this->get_connection(); + this->drop_view_internal(connection.get(), tableName, false); + } + + /** + * Drops the view with the specified name if it exists. + * Calls `DROP VIEW IF EXISTS "viewName"`. + * More info: https://www.sqlite.org/lang_droptable.html + */ + void drop_view_if_exists(const std::string& tableName) { + auto connection = this->get_connection(); + this->drop_view_internal(connection.get(), tableName, true); + } + /** * Rename table named `from` to `to`. */ @@ -147,6 +158,15 @@ namespace sqlite_orm { this->rename_table(connection.get(), from, to); } + /** + * `VACUUM` query. + * More info: https://www.sqlite.org/lang_vacuum.html + */ + void vacuum() { + auto connection = this->get_connection(); + this->executor.perform_void_exec(connection.get(), "VACUUM"); + } + protected: void rename_table(sqlite3* db, const std::string& oldName, const std::string& newName) const { std::string sql; @@ -190,6 +210,36 @@ namespace sqlite_orm { return result; } + /** + * Directly checks the actual database whether the specified view exists, bypassing the library's 'storage' mapping. + * @return true if view with the specified name exists in the database, false otherwise. + */ + bool view_exists(const std::string& tableName) { + auto connection = this->get_connection(); + return this->view_exists(connection.get(), tableName); + } + + bool view_exists(sqlite3* db, const std::string& tableName) const { + bool result = false; + std::string sql; + { + std::stringstream ss; + ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << quote_string_literal("view") + << " AND name = " << quote_string_literal(tableName) << std::flush; + sql = ss.str(); + } + this->executor.perform_exec( + db, + sql, + [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { + auto& res = *(bool*)userData; + res = !!atoi(argv[0]); + return 0; + }, + &result); + return result; + } + void add_generated_cols(std::vector& columnsToAdd, const std::vector& storageTableInfo) { // iterate through storage columns @@ -768,7 +818,8 @@ namespace sqlite_orm { auto connection = this->get_connection(); data_t objectNames; std::stringstream ss; - ss << "SELECT name FROM sqlite_master WHERE type=" << quote_string_literal(std::string(type)); + ss << "SELECT name FROM sqlite_master WHERE type=" << quote_string_literal(std::string(type)) + << std::flush; this->executor.perform_exec( connection.get(), ss.str(), @@ -1016,6 +1067,20 @@ namespace sqlite_orm { this->executor.perform_void_exec(db, sql.c_str()); } + void drop_view_internal(sqlite3* db, const std::string& viewName, bool ifExists) { + std::string sql; + { + std::stringstream ss; + ss << "DROP VIEW"; + if (ifExists) { + ss << " IF EXISTS"; + } + ss << ' ' << streaming_identifier(viewName) << std::flush; + sql = ss.str(); + } + this->executor.perform_void_exec(db, sql.c_str()); + } + void drop_index_internal(const std::string& indexName, bool ifExists) { std::string sql; { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 78895cb1c..961497b3e 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -18588,15 +18588,6 @@ namespace sqlite_orm { this->drop_trigger_internal(triggerName, true); } - /** - * `VACUUM` query. - * More info: https://www.sqlite.org/lang_vacuum.html - */ - void vacuum() { - auto connection = this->get_connection(); - this->executor.perform_void_exec(connection.get(), "VACUUM"); - } - /** * Drops table with given name. * Calls `DROP TABLE tableName`. @@ -18617,6 +18608,26 @@ namespace sqlite_orm { this->drop_table_internal(connection.get(), tableName, true); } + /** + * Drops the view with the specified name. + * Calls `DROP VIEW "viewName"`. + * More info: https://www.sqlite.org/lang_droptable.html + */ + void drop_view(const std::string& tableName) { + auto connection = this->get_connection(); + this->drop_view_internal(connection.get(), tableName, false); + } + + /** + * Drops the view with the specified name if it exists. + * Calls `DROP VIEW IF EXISTS "viewName"`. + * More info: https://www.sqlite.org/lang_droptable.html + */ + void drop_view_if_exists(const std::string& tableName) { + auto connection = this->get_connection(); + this->drop_view_internal(connection.get(), tableName, true); + } + /** * Rename table named `from` to `to`. */ @@ -18625,6 +18636,15 @@ namespace sqlite_orm { this->rename_table(connection.get(), from, to); } + /** + * `VACUUM` query. + * More info: https://www.sqlite.org/lang_vacuum.html + */ + void vacuum() { + auto connection = this->get_connection(); + this->executor.perform_void_exec(connection.get(), "VACUUM"); + } + protected: void rename_table(sqlite3* db, const std::string& oldName, const std::string& newName) const { std::string sql; @@ -18668,6 +18688,36 @@ namespace sqlite_orm { return result; } + /** + * Directly checks the actual database whether the specified view exists, bypassing the library's 'storage' mapping. + * @return true if view with the specified name exists in the database, false otherwise. + */ + bool view_exists(const std::string& tableName) { + auto connection = this->get_connection(); + return this->view_exists(connection.get(), tableName); + } + + bool view_exists(sqlite3* db, const std::string& tableName) const { + bool result = false; + std::string sql; + { + std::stringstream ss; + ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << quote_string_literal("view") + << " AND name = " << quote_string_literal(tableName) << std::flush; + sql = ss.str(); + } + this->executor.perform_exec( + db, + sql, + [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { + auto& res = *(bool*)userData; + res = !!atoi(argv[0]); + return 0; + }, + &result); + return result; + } + void add_generated_cols(std::vector& columnsToAdd, const std::vector& storageTableInfo) { // iterate through storage columns @@ -19246,7 +19296,8 @@ namespace sqlite_orm { auto connection = this->get_connection(); data_t objectNames; std::stringstream ss; - ss << "SELECT name FROM sqlite_master WHERE type=" << quote_string_literal(std::string(type)); + ss << "SELECT name FROM sqlite_master WHERE type=" << quote_string_literal(std::string(type)) + << std::flush; this->executor.perform_exec( connection.get(), ss.str(), @@ -19494,6 +19545,20 @@ namespace sqlite_orm { this->executor.perform_void_exec(db, sql.c_str()); } + void drop_view_internal(sqlite3* db, const std::string& viewName, bool ifExists) { + std::string sql; + { + std::stringstream ss; + ss << "DROP VIEW"; + if (ifExists) { + ss << " IF EXISTS"; + } + ss << ' ' << streaming_identifier(viewName) << std::flush; + sql = ss.str(); + } + this->executor.perform_void_exec(db, sql.c_str()); + } + void drop_index_internal(const std::string& indexName, bool ifExists) { std::string sql; { @@ -24821,8 +24886,8 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_VIEW - template - sync_schema_result schema_status(const view_t&, sqlite3*, bool, bool*) { + template = true> + sync_schema_result schema_status(const View&, sqlite3*, bool, bool*) { return sync_schema_result::already_in_sync; } #endif @@ -24947,8 +25012,8 @@ namespace sqlite_orm { } #ifdef SQLITE_ORM_WITH_VIEW - template - sync_schema_result sync_dbo(const view_t& view, sqlite3* db, bool) { + template = true> + sync_schema_result sync_dbo(const View& view, sqlite3* db, bool) { const auto& exprDBOs = db_objects_for_expression(this->db_objects, view.select); using context_t = serializer_context>; @@ -25088,7 +25153,8 @@ namespace sqlite_orm { return result; } - using storage_base::table_exists; // now that it is in storage_base make it into overload set + using storage_base::table_exists; + using storage_base::view_exists; template, bool> = true> prepared_statement_t prepare(DML statement) { diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 1fd5cf9b4..0cd6e416f 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -23,6 +23,12 @@ struct DidLogsCollector { std::vector DidLogsCollector::logs; +#ifdef SQLITE_ORM_WITH_VIEW +struct UserViewLoggerTests { + int id = 0; + std::string name; +}; +#endif TEST_CASE("logger") { using Logs = std::vector; using Callback = std::function; @@ -62,7 +68,7 @@ TEST_CASE("logger") { } }; - auto requireLogsAreEmpty = [] { + constexpr auto requireLogsAreEmpty = [] { REQUIRE(WillLogsCollector::logs.empty()); REQUIRE(DidLogsCollector::logs.empty()); }; @@ -89,6 +95,9 @@ TEST_CASE("logger") { make_table("visits_log", make_column("id", &VisitLog::id, primary_key()), make_column("message", &VisitLog::message)), +#ifdef SQLITE_ORM_WITH_VIEW + make_view("users_view", select(asterisk())), +#endif will_run_query(willRunQuery), did_run_query(didRunQuery)); storage.sync_schema(); @@ -150,10 +159,6 @@ TEST_CASE("logger") { storage.drop_trigger_if_exists(value); pushExpected(expected); } - SECTION("vacuum") { - storage.vacuum(); - pushExpected("VACUUM"); - } SECTION("drop_table") { const auto [value, expected] = GENERATE(table({ {"users", R"(DROP TABLE "users")"}, @@ -170,6 +175,24 @@ TEST_CASE("logger") { storage.drop_table_if_exists(value); pushExpected(expected); } +#ifdef SQLITE_ORM_WITH_VIEW + SECTION("drop_view") { + storage.drop_view("users_view"); + pushExpected(R"(DROP VIEW "users_view")"); + } + SECTION("drop_view_if_exists") { + const auto [value, expected] = GENERATE(table({ + {"users_view", R"(DROP VIEW IF EXISTS "users_view")"}, + {"xyz_view", R"(DROP VIEW IF EXISTS "xyz_view")"}, + })); + storage.drop_view_if_exists(value); + pushExpected(expected); + } +#endif + SECTION("vacuum") { + storage.vacuum(); + pushExpected("VACUUM"); + } SECTION("changes") { std::ignore = storage.changes(); } diff --git a/tests/storage_tests.cpp b/tests/storage_tests.cpp index c3844ab53..27adff4b1 100644 --- a/tests/storage_tests.cpp +++ b/tests/storage_tests.cpp @@ -155,6 +155,35 @@ TEST_CASE("drop table") { REQUIRE_NOTHROW(storage.drop_table_if_exists(visitsTableName)); } +#ifdef SQLITE_ORM_WITH_VIEW +struct UserViewDropViewTests { + int id = 0; + std::string name; +}; +TEST_CASE("drop view") { + struct User { + int id = 0; + std::string name; + }; + const std::string usersViewName = "users_view"; + + auto storage = + make_storage({}, + make_table("users", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), + make_view(usersViewName, select(asterisk()))); + REQUIRE_FALSE(storage.view_exists(usersViewName)); + + storage.sync_schema(); + REQUIRE(storage.view_exists(usersViewName)); + + storage.drop_view(usersViewName); + REQUIRE_FALSE(storage.view_exists(usersViewName)); + + REQUIRE_THROWS(storage.drop_view(usersViewName)); + REQUIRE_NOTHROW(storage.drop_view_if_exists(usersViewName)); +} +#endif + TEST_CASE("drop index") { struct User { int id = 0; From 157910767eab064cc63d1719c9b4fc9760b70f3b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 3 Oct 2025 21:09:21 +0300 Subject: [PATCH 17/49] Renamed `view_t` -> `query_view` --- dev/schema/view.h | 16 +++++++-------- dev/statement_serializer.h | 4 ++-- include/sqlite_orm/sqlite_orm.h | 20 +++++++++---------- .../schema/view.cpp | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/dev/schema/view.h b/dev/schema/view.h index d53d88829..e038463e1 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -70,7 +70,7 @@ namespace sqlite_orm::internal { * View definition, mapping an aggregate object type to a corresponding select statement. */ template - struct view_t : table_identifier, table_definition { + struct query_view : table_identifier, table_definition { using definition_base_type = table_definition; using object_type = O; using elements_type = typename definition_base_type::elements_type; @@ -80,7 +80,7 @@ namespace sqlite_orm::internal { }; template - inline constexpr bool is_view_v = polyfill::is_specialization_of_v; + inline constexpr bool is_view_v = polyfill::is_specialization_of_v; #else template inline constexpr bool is_view_v = false; @@ -164,12 +164,12 @@ namespace sqlite_orm::internal { // object's member types as a tuple using TS = pfrs::tuple::type>...>; - using view_type = - view_t(std::string(pfr::get_name()), - column_pointer())>{ - pfr::get_relative_address()}))...>; + using view_type = query_view( + std::string(pfr::get_name()), + column_pointer())>{ + pfr::get_relative_address()}))...>; SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return view_type{ std::move(name), diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index c929b0945..41f978016 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -176,8 +176,8 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_VIEW template - struct statement_serializer, void> { - using statement_type = view_t; + struct statement_serializer, void> { + using statement_type = query_view; template std::string operator()(const statement_type& statement, const Ctx& context) { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 961497b3e..ed5e419e1 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -12962,7 +12962,7 @@ namespace sqlite_orm::internal { * View definition, mapping an aggregate object type to a corresponding select statement. */ template - struct view_t : table_identifier, table_definition { + struct query_view : table_identifier, table_definition { using definition_base_type = table_definition; using object_type = O; using elements_type = typename definition_base_type::elements_type; @@ -12972,7 +12972,7 @@ namespace sqlite_orm::internal { }; template - inline constexpr bool is_view_v = polyfill::is_specialization_of_v; + inline constexpr bool is_view_v = polyfill::is_specialization_of_v; #else template inline constexpr bool is_view_v = false; @@ -13056,12 +13056,12 @@ namespace sqlite_orm::internal { // object's member types as a tuple using TS = pfrs::tuple::type>...>; - using view_type = - view_t(std::string(pfr::get_name()), - column_pointer())>{ - pfr::get_relative_address()}))...>; + using view_type = query_view( + std::string(pfr::get_name()), + column_pointer())>{ + pfr::get_relative_address()}))...>; SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return view_type{ std::move(name), @@ -20926,8 +20926,8 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_VIEW template - struct statement_serializer, void> { - using statement_type = view_t; + struct statement_serializer, void> { + using statement_type = query_view; template std::string operator()(const statement_type& statement, const Ctx& context) { diff --git a/tests/statement_serializer_tests/schema/view.cpp b/tests/statement_serializer_tests/schema/view.cpp index 96c04c08e..6a87220d5 100644 --- a/tests/statement_serializer_tests/schema/view.cpp +++ b/tests/statement_serializer_tests/schema/view.cpp @@ -10,7 +10,7 @@ struct UserViewSerializerTests { std::string name; }; -TEST_CASE("statement_serializer view_t") { +TEST_CASE("statement_serializer query_view") { struct User { int id = 0; std::string name; From 63dbea950bb60cd905d08b68bebe98cfc287510f Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 3 Oct 2025 22:03:09 +0300 Subject: [PATCH 18/49] Disable boost-pfr when testing the sqlite_orm C++ module --- dev/functional/pfr_config.h | 2 +- include/sqlite_orm/sqlite_orm.h | 2 +- tests/CMakeLists.txt | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dev/functional/pfr_config.h b/dev/functional/pfr_config.h index 10a098a46..6548a2722 100644 --- a/dev/functional/pfr_config.h +++ b/dev/functional/pfr_config.h @@ -4,6 +4,6 @@ #define SQLITE_ORM_HAS_BOOST_PFR #endif -#ifdef SQLITE_ORM_HAS_BOOST_PFR +#if defined(SQLITE_ORM_HAS_BOOST_PFR) && (!defined(BOOST_PFR_ENABLED) || (BOOST_PFR_ENABLED == 1)) #include #endif diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index ed5e419e1..26e2ee680 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -246,7 +246,7 @@ using std::nullptr_t; #define SQLITE_ORM_HAS_BOOST_PFR #endif -#ifdef SQLITE_ORM_HAS_BOOST_PFR +#if defined(SQLITE_ORM_HAS_BOOST_PFR) && (!defined(BOOST_PFR_ENABLED) || (BOOST_PFR_ENABLED == 1)) #include #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 294c42a2b..e7a136cd6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,6 +32,8 @@ if(CMAKE_CXX_STANDARD GREATER_EQUAL 23 AND CMAKE_VERSION VERSION_GREATER_EQUAL 3 message(STATUS "SQLITE_ORM_OMITS_CODECVT is enabled") target_compile_definitions(module_tests PRIVATE SQLITE_ORM_OMITS_CODECVT=1) endif() + # Explicitly disable boost-pfr for now until it supports C++ named modules with msvc + target_compile_definitions(module_tests PRIVATE BOOST_PFR_ENABLED=0) target_precompile_headers(module_tests PRIVATE ) From f7cf5d6ba54b3400e3b9f24da9fd7609769432df Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 4 Oct 2025 11:47:52 +0300 Subject: [PATCH 19/49] Used a custom implementation of `offsetof` clang/gcc use an intrinsic compiler builtin [`__builtin_offsetof`], which doesn't work the way we need. --- dev/schema/view.h | 6 ++++-- include/sqlite_orm/sqlite_orm.h | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dev/schema/view.h b/dev/schema/view.h index e038463e1..3a764827a 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -5,7 +5,6 @@ #include // std::remove_cvref #include // std::forward, std::move, std::index_sequence, std::make_index_sequence #include // std::byte -#include // offsetof #if __cpp_impl_reflection >= 202500L #include #endif @@ -44,7 +43,10 @@ namespace boost::pfr { static_assert(N < Tpl::size_v); using nth_type = decltype(get_nth_base(std::declval())); using field_type = decltype(nth_type::value); - return (field_type*)(std::byte*)offsetof(Tpl, nth_type::value); + + return (field_type*)(std::byte*) + // offsetof - the official one cannot be used because of some implementations using the compiler intrinsic builtin + ((::size_t)&reinterpret_cast((((Tpl*)0)->nth_type::value))); } } } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 26e2ee680..7ee2cb21d 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -12893,7 +12893,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #include // std::remove_cvref #include // std::forward, std::move, std::index_sequence, std::make_index_sequence #include // std::byte -#include // offsetof #if __cpp_impl_reflection >= 202500L #include #endif @@ -12936,7 +12935,10 @@ namespace boost::pfr { static_assert(N < Tpl::size_v); using nth_type = decltype(get_nth_base(std::declval())); using field_type = decltype(nth_type::value); - return (field_type*)(std::byte*)offsetof(Tpl, nth_type::value); + + return (field_type*)(std::byte*) + // offsetof - the official one cannot be used because of some implementations using the compiler intrinsic builtin + ((::size_t)&reinterpret_cast((((Tpl*)0)->nth_type::value))); } } } From 415b462740559e60ea30b2795e0ab164d780d472 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 23 Nov 2025 08:58:32 +0200 Subject: [PATCH 20/49] Removed duplicate C++ feature test --- dev/functional/cxx_core_features.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dev/functional/cxx_core_features.h b/dev/functional/cxx_core_features.h index 1647fbcc7..850abef81 100644 --- a/dev/functional/cxx_core_features.h +++ b/dev/functional/cxx_core_features.h @@ -43,10 +43,6 @@ #define SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED #endif -#if __cpp_nontype_template_args >= 201911L -#define SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED -#endif - #if __cpp_explicit_this_parameter >= 202110L #define SQLITE_ORM_DEDUCING_THIS_SUPPORTED #endif From c88062619cf33e379e3983e8cc4ff783bedc3adb Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 23 Nov 2025 08:59:17 +0200 Subject: [PATCH 21/49] Used `std::size_t` for PFR related function --- dev/schema/view.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/schema/view.h b/dev/schema/view.h index 3a764827a..11df78079 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -46,7 +46,7 @@ namespace boost::pfr { return (field_type*)(std::byte*) // offsetof - the official one cannot be used because of some implementations using the compiler intrinsic builtin - ((::size_t)&reinterpret_cast((((Tpl*)0)->nth_type::value))); + ((std::size_t)&reinterpret_cast((((Tpl*)0)->nth_type::value))); } } } From 139bda0bbbb9fbce5db6847443cc44309b3c2b72 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 23 Nov 2025 20:27:40 +0200 Subject: [PATCH 22/49] Creation of `query_view` with C++26 reflection syntax --- .clang-format-ignore | 2 + dev/functional/cxx_core_features.h | 4 ++ dev/functional/meta_util.h | 40 +++++++++++++ dev/schema/view.h | 44 +++++++++++--- include/sqlite_orm/sqlite_orm.h | 94 ++++++++++++++++++++++++++---- 5 files changed, 165 insertions(+), 19 deletions(-) create mode 100644 .clang-format-ignore create mode 100644 dev/functional/meta_util.h diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 000000000..e37bf2876 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,2 @@ +# exclude until clang-format understands C++ reflection syntax +dev/functional/meta_util.h diff --git a/dev/functional/cxx_core_features.h b/dev/functional/cxx_core_features.h index 850abef81..d43ca8e71 100644 --- a/dev/functional/cxx_core_features.h +++ b/dev/functional/cxx_core_features.h @@ -63,6 +63,10 @@ #define SQLITE_ORM_CONTRACTS_SUPPORTED #endif +#if __cpp_impl_reflection >= 202506L +#define SQLITE_ORM_REFLECTION_SUPPORTED +#endif + #if __cplusplus >= 202002L #define SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED #define SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED diff --git a/dev/functional/meta_util.h b/dev/functional/meta_util.h new file mode 100644 index 000000000..0821d1727 --- /dev/null +++ b/dev/functional/meta_util.h @@ -0,0 +1,40 @@ +#pragma once + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED +#include // std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of +#include // std::tuple, std::get +#include // std::index_sequence, std::make_index_sequence +#endif +#endif + +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED +namespace sqlite_orm::internal { + template + consteval auto extract_member_names() { + constexpr auto ctx = std::meta::access_context::current(); + constexpr size_t N = nonstatic_data_members_of(^^T, ctx).size(); + auto members = nonstatic_data_members_of(^^T, ctx); + + return [&members](std::index_sequence) consteval { + return std::tuple{std::meta::identifier_of(members[I])...}; + }(std::make_index_sequence{}); + } + + template + consteval auto extract_member_pointers() { + constexpr auto ctx = std::meta::access_context::current(); + constexpr size_t N = nonstatic_data_members_of(^^T, ctx).size(); + + return [&ctx](std::index_sequence) consteval { + return std::tuple{&[:nonstatic_data_members_of(^^T, ctx)[I]:]...}; + }(std::make_index_sequence{}); + } + + template + consteval auto count_members() { + constexpr auto ctx = std::meta::access_context::current(); + return nonstatic_data_members_of(^^T, ctx).size(); + } +} +#endif diff --git a/dev/schema/view.h b/dev/schema/view.h index 11df78079..241847140 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -5,14 +5,11 @@ #include // std::remove_cvref #include // std::forward, std::move, std::index_sequence, std::make_index_sequence #include // std::byte -#if __cpp_impl_reflection >= 202500L -#include -#endif #endif #endif #ifdef SQLITE_ORM_WITH_VIEW -#if __cpp_impl_reflection < 202500L +#if __cpp_impl_reflection < 202506L #ifdef SQLITE_ORM_HAS_BOOST_PFR #include #endif @@ -20,13 +17,14 @@ #endif #include "../functional/cxx_type_traits_polyfill.h" +#include "../functional/meta_util.h" #include "../column_pointer.h" #include "../select_constraints.h" #include "column.h" #include "table_base.h" #ifdef SQLITE_ORM_WITH_VIEW -#if __cpp_impl_reflection >= 202500L +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED #elif BOOST_PFR_ENABLED == 1 namespace boost::pfr { namespace detail { @@ -89,11 +87,43 @@ namespace sqlite_orm::internal { #endif template - struct is_view : polyfill::bool_constant> {}; + using is_view = polyfill::bool_constant>; } #ifdef SQLITE_ORM_WITH_VIEW -#if __cpp_impl_reflection >= 202500L +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED +namespace sqlite_orm::internal { + template + auto make_view(std::string name, std::index_sequence, Select select) { + constexpr auto memberNames = extract_member_names(); + constexpr auto memberPointers = extract_member_pointers(); + + using view_type = + query_view(memberNames)), std::get(memberPointers)))...>; + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return view_type{ + std::move(name), + std::tuple{make_column(std::string(std::get(memberNames)), std::get(memberPointers))...}, + std::move(select)}); + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + template + requires (internal::is_select_expression_v) { + select.highest_level = true; + } + return internal::make_view(std::move(name), + std::make_index_sequence()>{}, + std::move(select)); + } +} #elif BOOST_PFR_ENABLED == 1 namespace sqlite_orm::internal { /** diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index bc8737628..d7b88c5b8 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -104,10 +104,6 @@ using std::nullptr_t; #define SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED #endif -#if __cpp_nontype_template_args >= 201911L -#define SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED -#endif - #if __cpp_explicit_this_parameter >= 202110L #define SQLITE_ORM_DEDUCING_THIS_SUPPORTED #endif @@ -128,6 +124,10 @@ using std::nullptr_t; #define SQLITE_ORM_CONTRACTS_SUPPORTED #endif +#if __cpp_impl_reflection >= 202506L +#define SQLITE_ORM_REFLECTION_SUPPORTED +#endif + #if __cplusplus >= 202002L #define SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED #define SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED @@ -13177,14 +13177,11 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #include // std::remove_cvref #include // std::forward, std::move, std::index_sequence, std::make_index_sequence #include // std::byte -#if __cpp_impl_reflection >= 202500L -#include -#endif #endif #endif #ifdef SQLITE_ORM_WITH_VIEW -#if __cpp_impl_reflection < 202500L +#if __cpp_impl_reflection < 202506L #ifdef SQLITE_ORM_HAS_BOOST_PFR #include #endif @@ -13193,6 +13190,47 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "../functional/cxx_type_traits_polyfill.h" +// #include "../functional/meta_util.h" + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED +#include // std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of +#include // std::tuple, std::get +#include // std::index_sequence, std::make_index_sequence +#endif +#endif + +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED +namespace sqlite_orm::internal { + template + consteval auto extract_member_names() { + constexpr auto ctx = std::meta::access_context::current(); + constexpr size_t N = nonstatic_data_members_of(^^T, ctx).size(); + auto members = nonstatic_data_members_of(^^T, ctx); + + return [&members](std::index_sequence) consteval { + return std::tuple{std::meta::identifier_of(members[I])...}; + }(std::make_index_sequence{}); + } + + template + consteval auto extract_member_pointers() { + constexpr auto ctx = std::meta::access_context::current(); + constexpr size_t N = nonstatic_data_members_of(^^T, ctx).size(); + + return [&ctx](std::index_sequence) consteval { + return std::tuple{&[:nonstatic_data_members_of(^^T, ctx)[I]:]...}; + }(std::make_index_sequence{}); + } + + template + consteval auto count_members() { + constexpr auto ctx = std::meta::access_context::current(); + return nonstatic_data_members_of(^^T, ctx).size(); + } +} +#endif + // #include "../column_pointer.h" // #include "../select_constraints.h" @@ -13202,7 +13240,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "table_base.h" #ifdef SQLITE_ORM_WITH_VIEW -#if __cpp_impl_reflection >= 202500L +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED #elif BOOST_PFR_ENABLED == 1 namespace boost::pfr { namespace detail { @@ -13222,7 +13260,7 @@ namespace boost::pfr { return (field_type*)(std::byte*) // offsetof - the official one cannot be used because of some implementations using the compiler intrinsic builtin - ((::size_t)&reinterpret_cast((((Tpl*)0)->nth_type::value))); + ((std::size_t)&reinterpret_cast((((Tpl*)0)->nth_type::value))); } } } @@ -13265,11 +13303,43 @@ namespace sqlite_orm::internal { #endif template - struct is_view : polyfill::bool_constant> {}; + using is_view = polyfill::bool_constant>; } #ifdef SQLITE_ORM_WITH_VIEW -#if __cpp_impl_reflection >= 202500L +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED +namespace sqlite_orm::internal { + template + auto make_view(std::string name, std::index_sequence, Select select) { + constexpr auto memberNames = extract_member_names(); + constexpr auto memberPointers = extract_member_pointers(); + + using view_type = + query_view(memberNames)), std::get(memberPointers)))...>; + + SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return view_type{ + std::move(name), + std::tuple{make_column(std::string(std::get(memberNames)), std::get(memberPointers))...}, + std::move(select)}); + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + template + requires (internal::is_select_expression_v) { + select.highest_level = true; + } + return internal::make_view(std::move(name), + std::make_index_sequence()>{}, + std::move(select)); + } +} #elif BOOST_PFR_ENABLED == 1 namespace sqlite_orm::internal { /** From bbfec0bc61eeaac9117c16aea873e5d2d5467401 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 23 Nov 2025 23:49:34 +0200 Subject: [PATCH 23/49] `stream_identifier()` with an index sequence needs to be available --- dev/serializing_util.h | 12 ++++++------ include/sqlite_orm/sqlite_orm.h | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dev/serializing_util.h b/dev/serializing_util.h index 080519ca3..dbc4d8ee4 100644 --- a/dev/serializing_util.h +++ b/dev/serializing_util.h @@ -81,6 +81,12 @@ namespace sqlite_orm { return stream_identifier(ss, "", identifier, ""); } + template + void stream_identifier(std::ostream& ss, const Tpl& tpl, std::index_sequence) { + static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3, ""); + return stream_identifier(ss, std::get(tpl)...); + } + #ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED template requires polyfill::is_detected_v> @@ -90,12 +96,6 @@ namespace sqlite_orm { return stream_identifier(ss, elements...); } #else - template - void stream_identifier(std::ostream& ss, const Tpl& tpl, std::index_sequence) { - static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3, ""); - return stream_identifier(ss, std::get(tpl)...); - } - template>::value, bool> = true> void stream_identifier(std::ostream& ss, const Tpl& tpl) { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index d7b88c5b8..5522fd450 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -17779,6 +17779,12 @@ namespace sqlite_orm { return stream_identifier(ss, "", identifier, ""); } + template + void stream_identifier(std::ostream& ss, const Tpl& tpl, std::index_sequence) { + static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3, ""); + return stream_identifier(ss, std::get(tpl)...); + } + #ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED template requires polyfill::is_detected_v> @@ -17788,12 +17794,6 @@ namespace sqlite_orm { return stream_identifier(ss, elements...); } #else - template - void stream_identifier(std::ostream& ss, const Tpl& tpl, std::index_sequence) { - static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3, ""); - return stream_identifier(ss, std::get(tpl)...); - } - template>::value, bool> = true> void stream_identifier(std::ostream& ss, const Tpl& tpl) { From b3373e4af169fd7dc5e1878ea4bdfccc9b3a4eb9 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 23 Nov 2025 23:56:05 +0200 Subject: [PATCH 24/49] Used the single-argument version of `static_assert` --- dev/alias.h | 2 +- dev/functional/cxx_check_prerequisites.h | 2 +- dev/serializing_util.h | 2 +- dev/statement_serializer.h | 2 +- include/sqlite_orm/sqlite_orm.h | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dev/alias.h b/dev/alias.h index e43d7d382..57f86787b 100644 --- a/dev/alias.h +++ b/dev/alias.h @@ -379,7 +379,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template internal::alias_holder get() { - static_assert(internal::is_column_alias_v, ""); + static_assert(internal::is_column_alias_v); return {}; } diff --git a/dev/functional/cxx_check_prerequisites.h b/dev/functional/cxx_check_prerequisites.h index 20ed286f5..224364d8d 100644 --- a/dev/functional/cxx_check_prerequisites.h +++ b/dev/functional/cxx_check_prerequisites.h @@ -11,7 +11,7 @@ #endif #if (!defined(__has_include)) || \ - ((__cpp_noexcept_function_type < 201510L) || \ + ((__cpp_static_assert < 201411L) || (__cpp_noexcept_function_type < 201510L) || \ (__cpp_fold_expressions < 201603L || __cpp_constexpr < 201603L || __cpp_aggregate_bases < 201603L || \ __cpp_range_based_for < 201603L) || \ (__cpp_if_constexpr < 201606L || __cpp_inline_variables < 201606L || __cpp_structured_bindings < 201606L) || \ diff --git a/dev/serializing_util.h b/dev/serializing_util.h index dbc4d8ee4..9990b8ac8 100644 --- a/dev/serializing_util.h +++ b/dev/serializing_util.h @@ -83,7 +83,7 @@ namespace sqlite_orm { template void stream_identifier(std::ostream& ss, const Tpl& tpl, std::index_sequence) { - static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3, ""); + static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3); return stream_identifier(ss, std::get(tpl)...); } diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index d69c6ade0..632a2eeea 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -102,7 +102,7 @@ namespace sqlite_orm { , bool> = true> static std::string do_serialize(const X& c) { - static_assert(std::is_same::value, ""); + static_assert(std::is_same::value); // implementation detail: utilizing field_printer return field_printer{}(c); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 5522fd450..308442d63 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -51,7 +51,7 @@ using std::nullptr_t; #endif #if (!defined(__has_include)) || \ - ((__cpp_noexcept_function_type < 201510L) || \ + ((__cpp_static_assert < 201411L) || (__cpp_noexcept_function_type < 201510L) || \ (__cpp_fold_expressions < 201603L || __cpp_constexpr < 201603L || __cpp_aggregate_bases < 201603L || \ __cpp_range_based_for < 201603L) || \ (__cpp_if_constexpr < 201606L || __cpp_inline_variables < 201606L || __cpp_structured_bindings < 201606L) || \ @@ -3001,7 +3001,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { */ template internal::alias_holder get() { - static_assert(internal::is_column_alias_v, ""); + static_assert(internal::is_column_alias_v); return {}; } @@ -17781,7 +17781,7 @@ namespace sqlite_orm { template void stream_identifier(std::ostream& ss, const Tpl& tpl, std::index_sequence) { - static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3, ""); + static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3); return stream_identifier(ss, std::get(tpl)...); } @@ -21559,7 +21559,7 @@ namespace sqlite_orm { , bool> = true> static std::string do_serialize(const X& c) { - static_assert(std::is_same::value, ""); + static_assert(std::is_same::value); // implementation detail: utilizing field_printer return field_printer{}(c); From 273bab0a9b44f63aedd5ab5bac181f5065ced1ac Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 24 Nov 2025 00:22:22 +0200 Subject: [PATCH 25/49] Accommodated for Clang on Windows, which cannot use constexpr DLL exports --- dev/functional/cxx_compiler_quirks.h | 5 ++++- dev/prepared_statement.h | 10 ++++++++++ dev/statement_finalizer.h | 14 ++++++++++++++ include/sqlite_orm/sqlite_orm.h | 29 +++++++++++++++++++++++++++- tests/xdestroy_handling.cpp | 6 ++++++ 5 files changed, 62 insertions(+), 2 deletions(-) diff --git a/dev/functional/cxx_compiler_quirks.h b/dev/functional/cxx_compiler_quirks.h index 14e0b05db..c6043bbf9 100644 --- a/dev/functional/cxx_compiler_quirks.h +++ b/dev/functional/cxx_compiler_quirks.h @@ -15,7 +15,6 @@ SQLITE_ORM_DO_PRAGMA(clang diagnostic ignored warnoption) \ __VA_ARGS__ \ SQLITE_ORM_DO_PRAGMA(clang diagnostic pop) - #else #define SQLITE_ORM_CLANG_SUPPRESS(warnoption, ...) __VA_ARGS__ #endif @@ -30,6 +29,10 @@ #define SQLITE_ORM_MSVC_SUPPRESS(warcode, ...) __VA_ARGS__ #endif +#if defined(_MSC_VER) && defined(__clang__) +#define SQLITE_ORM_CLANG_ON_WIN +#endif + // clang has the bad habit of diagnosing missing brace-init-lists when constructing aggregates with base classes. // This is a false positive, since the C++ standard is quite clear that braces for nested or base objects may be omitted, // see https://en.cppreference.com/w/cpp/language/aggregate_initialization: diff --git a/dev/prepared_statement.h b/dev/prepared_statement.h index d5a6bc987..7bbed6cf8 100644 --- a/dev/prepared_statement.h +++ b/dev/prepared_statement.h @@ -47,7 +47,17 @@ namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3014000 std::string expanded_sql() const { // note: must check return value due to SQLITE_OMIT_TRACE +#ifndef SQLITE_ORM_CLANG_ON_WIN using char_ptr = std::unique_ptr>; +#else + struct sqlite3_memory_deleter { + SQLITE_ORM_STATIC_CALLOP void operator()(void* mem) SQLITE_ORM_OR_CONST_CALLOP noexcept { + sqlite3_free(mem); + } + }; + using char_ptr = std::unique_ptr; +#endif + if (char_ptr sql{sqlite3_expanded_sql(this->stmt)}) { return sql.get(); } else { diff --git a/dev/statement_finalizer.h b/dev/statement_finalizer.h index 1de4575ff..73b67d295 100644 --- a/dev/statement_finalizer.h +++ b/dev/statement_finalizer.h @@ -6,11 +6,25 @@ #include // std::integral_constant #endif +#ifdef SQLITE_ORM_CLANG_ON_WIN +namespace sqlite_orm::internal { + struct statement_deleter { + SQLITE_ORM_STATIC_CALLOP void operator()(sqlite3_stmt* stmt) SQLITE_ORM_OR_CONST_CALLOP noexcept { + sqlite3_finalize(stmt); + } + }; +} +#endif + SQLITE_ORM_EXPORT namespace sqlite_orm { +#ifndef SQLITE_ORM_CLANG_ON_WIN /** * Guard class which finalizes `sqlite3_stmt` in dtor */ using statement_finalizer = std::unique_ptr>; +#else + using statement_finalizer = std::unique_ptr; +#endif } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 308442d63..782c1998a 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -150,7 +150,6 @@ using std::nullptr_t; SQLITE_ORM_DO_PRAGMA(clang diagnostic ignored warnoption) \ __VA_ARGS__ \ SQLITE_ORM_DO_PRAGMA(clang diagnostic pop) - #else #define SQLITE_ORM_CLANG_SUPPRESS(warnoption, ...) __VA_ARGS__ #endif @@ -165,6 +164,10 @@ using std::nullptr_t; #define SQLITE_ORM_MSVC_SUPPRESS(warcode, ...) __VA_ARGS__ #endif +#if defined(_MSC_VER) && defined(__clang__) +#define SQLITE_ORM_CLANG_ON_WIN +#endif + // clang has the bad habit of diagnosing missing brace-init-lists when constructing aggregates with base classes. // This is a false positive, since the C++ standard is quite clear that braces for nested or base objects may be omitted, // see https://en.cppreference.com/w/cpp/language/aggregate_initialization: @@ -14322,13 +14325,27 @@ namespace sqlite_orm { #include // std::integral_constant #endif +#ifdef SQLITE_ORM_CLANG_ON_WIN +namespace sqlite_orm::internal { + struct statement_deleter { + SQLITE_ORM_STATIC_CALLOP void operator()(sqlite3_stmt* stmt) SQLITE_ORM_OR_CONST_CALLOP noexcept { + sqlite3_finalize(stmt); + } + }; +} +#endif + SQLITE_ORM_EXPORT namespace sqlite_orm { +#ifndef SQLITE_ORM_CLANG_ON_WIN /** * Guard class which finalizes `sqlite3_stmt` in dtor */ using statement_finalizer = std::unique_ptr>; +#else + using statement_finalizer = std::unique_ptr; +#endif } // #include "error_code.h" @@ -15643,7 +15660,17 @@ namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3014000 std::string expanded_sql() const { // note: must check return value due to SQLITE_OMIT_TRACE +#ifndef SQLITE_ORM_CLANG_ON_WIN using char_ptr = std::unique_ptr>; +#else + struct sqlite3_memory_deleter { + SQLITE_ORM_STATIC_CALLOP void operator()(void* mem) SQLITE_ORM_OR_CONST_CALLOP noexcept { + sqlite3_free(mem); + } + }; + using char_ptr = std::unique_ptr; +#endif + if (char_ptr sql{sqlite3_expanded_sql(this->stmt)}) { return sql.get(); } else { diff --git a/tests/xdestroy_handling.cpp b/tests/xdestroy_handling.cpp index 93503abf3..68dbe5a9d 100644 --- a/tests/xdestroy_handling.cpp +++ b/tests/xdestroy_handling.cpp @@ -23,8 +23,10 @@ template inline constexpr delete_default_t delete_default_f{}; #endif +#ifndef SQLITE_ORM_CLANG_ON_WIN using free_t = std::integral_constant; inline constexpr free_t free_f{}; +#endif TEST_CASE("obtain_xdestroy_for") { @@ -62,6 +64,7 @@ TEST_CASE("obtain_xdestroy_for") { STATIC_REQUIRE(xDestroy1 == nullptr); REQUIRE(xDestroy1 == nullptr); +#ifndef SQLITE_ORM_CLANG_ON_WIN // free(int*) constexpr xdestroy_fn_t xDestroy2 = obtain_xdestroy_for(free, int_nullptr); STATIC_REQUIRE(xDestroy2 == &free); @@ -71,6 +74,7 @@ TEST_CASE("obtain_xdestroy_for") { constexpr xdestroy_fn_t xDestroy3 = obtain_xdestroy_for(free_f, int_nullptr); STATIC_REQUIRE(xDestroy3 == &free); REQUIRE(xDestroy3 == &free); +#endif #if __cpp_constexpr >= 201603L // constexpr lambda // [](void* p){} @@ -116,6 +120,7 @@ TEST_CASE("obtain_xdestroy_for") { #endif #if __cpp_constexpr >= 201907L // Trivial default initialization in constexpr functions +#ifndef SQLITE_ORM_CLANG_ON_WIN // xdestroy_holder{ free }(int*) constexpr xdestroy_fn_t xDestroy8 = obtain_xdestroy_for(xdestroy_holder{free}, int_nullptr); STATIC_REQUIRE(xDestroy8 == &free); @@ -130,6 +135,7 @@ TEST_CASE("obtain_xdestroy_for") { constexpr xdestroy_fn_t xDestroy10 = obtain_xdestroy_for(xdestroy_holder{nullptr}, const_int_nullptr); STATIC_REQUIRE(xDestroy10 == nullptr); REQUIRE(xDestroy10 == nullptr); +#endif #endif // expressions that do not work From af5a8c36418f09604ef0e82a58533533b6fc2d41 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Mon, 24 Nov 2025 20:59:37 +0200 Subject: [PATCH 26/49] Merge branch 'clang-related' into experimental/sql-view --- .clang-format | 2 +- .github/workflows/clang-format-lint.yaml | 4 +- CMakeLists.txt | 2 +- dev/alias.h | 6 +- dev/cte_column_names_collector.h | 2 +- dev/cte_moniker.h | 4 +- dev/cte_storage.h | 3 +- dev/function.h | 2 +- dev/functional/cxx_compiler_quirks.h | 14 +- dev/functional/start_macros.h | 15 +- dev/pointer_value.h | 2 +- dev/prepared_statement.h | 2 +- dev/schema/column.h | 12 +- dev/schema/index.h | 9 +- dev/schema/table.h | 6 +- dev/schema/triggers.h | 3 +- dev/schema/view.h | 7 +- dev/schema/virtual_table.h | 4 +- dev/serializing_util.h | 2 +- dev/statement_binder.h | 9 +- dev/statement_finalizer.h | 4 +- dev/storage.h | 2 +- dev/table_reference.h | 6 +- dev/tuple_helper/tuple_transformer.h | 4 +- dev/util.h | 4 +- dev/vtabs/dbstat.h | 2 +- dev/vtabs/fts5.h | 6 +- dev/vtabs/generate_series.h | 2 +- dev/vtabs/rtree.h | 4 +- examples/rtree.cpp | 3 +- include/sqlite_orm/sqlite_orm.h | 136 +++++++++--------- tests/CMakeLists.txt | 11 +- .../static_tests/functional/tuple_traits.cpp | 1 - tests/static_tests/virtual_tables.cpp | 1 - tests/user_defined_functions.cpp | 4 +- tests/xdestroy_handling.cpp | 6 +- 36 files changed, 149 insertions(+), 157 deletions(-) diff --git a/.clang-format b/.clang-format index dcb7a7761..2c2d646a1 100644 --- a/.clang-format +++ b/.clang-format @@ -1,5 +1,6 @@ --- Language: Cpp +Standard: Latest AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false @@ -117,7 +118,6 @@ SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp11 AttributeMacros: [SQLITE_ORM_CPP_LIKELY, SQLITE_ORM_CPP_UNLIKELY, SQLITE_ORM_EXPORT] StatementMacros: - __pragma diff --git a/.github/workflows/clang-format-lint.yaml b/.github/workflows/clang-format-lint.yaml index 65f060047..dce93991e 100644 --- a/.github/workflows/clang-format-lint.yaml +++ b/.github/workflows/clang-format-lint.yaml @@ -9,6 +9,6 @@ jobs: steps: - uses: actions/checkout@v1 - name: clang-format lint - uses: d-griet/clang-format-lint-action@99a106be2f3f1a92d9783ea7c744fde62d8ce1fa + uses: DoozyX/clang-format-lint-action@8b8bbdd8669eec54682e720ebe2fb19d9387fdb0 with: - clangFormatVersion: 19 + clangFormatVersion: 20 diff --git a/CMakeLists.txt b/CMakeLists.txt index e091b46f3..9124ea2c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_VERBOSE_MAKEFILE ON) -message(STATUS "Configuring ${PROJECT_NAME} ${sqlite_orm_VERSION}") +message(STATUS "Configuring ${PROJECT_NAME} ${sqlite_orm_VERSION} for ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") diff --git a/dev/alias.h b/dev/alias.h index 57f86787b..007f9c5c0 100644 --- a/dev/alias.h +++ b/dev/alias.h @@ -469,7 +469,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * constexpr orm_table_alias auto z_alias = "z"_alias.for_(); */ template - [[nodiscard]] consteval auto operator"" _alias() { + [[nodiscard]] consteval auto operator""_alias() { return internal::explode_into( std::make_index_sequence{}); } @@ -479,7 +479,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * E.g. "a"_col, "b"_col */ template - [[nodiscard]] consteval auto operator"" _col() { + [[nodiscard]] consteval auto operator""_col() { return internal::explode_into(std::make_index_sequence{}); } } @@ -492,7 +492,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * E.g. 1_colalias, 2_colalias */ template - [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator"" _colalias() { + [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator""_colalias() { // numeric identifiers are used for automatically assigning implicit aliases to unaliased column expressions, // which start at "1". static_assert(std::array{Chars...}[0] > '0'); diff --git a/dev/cte_column_names_collector.h b/dev/cte_column_names_collector.h index b02fbf204..106d547bf 100644 --- a/dev/cte_column_names_collector.h +++ b/dev/cte_column_names_collector.h @@ -204,7 +204,7 @@ namespace sqlite_orm { // 3. fill in blanks with numerical column identifiers { #ifdef SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED - for (size_t n = 1; std::string & name: columnNames) { + for (size_t n = 1; std::string& name: columnNames) { if (name.empty()) { name = std::to_string(n); } diff --git a/dev/cte_moniker.h b/dev/cte_moniker.h index e723b02d7..a53417cf6 100644 --- a/dev/cte_moniker.h +++ b/dev/cte_moniker.h @@ -76,7 +76,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * E.g. 1_ctealias, 2_ctealias */ template - [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator"" _ctealias() { + [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator""_ctealias() { return internal::cte_moniker{}; } @@ -86,7 +86,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * E.g. "1"_cte, "2"_cte */ template - [[nodiscard]] consteval auto operator"" _cte() { + [[nodiscard]] consteval auto operator""_cte() { return internal::explode_into(std::make_index_sequence{}); } #endif diff --git a/dev/cte_storage.h b/dev/cte_storage.h index 139c1e5ae..06f8bfd94 100644 --- a/dev/cte_storage.h +++ b/dev/cte_storage.h @@ -75,8 +75,7 @@ namespace sqlite_orm { template cte_table make_cte_table(std::string name, Cs... args) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), std::make_tuple(std::forward(args)...)}); + return {std::move(name), std::make_tuple(std::forward(args)...)}; } // aliased column expressions, explicit or implicitly numbered diff --git a/dev/function.h b/dev/function.h index a7660179c..542a53483 100644 --- a/dev/function.h +++ b/dev/function.h @@ -580,7 +580,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * auto rows = storage.select(equal_to_int_3_f(1, 1)); */ template - [[nodiscard]] consteval auto operator"" _scalar() { + [[nodiscard]] consteval auto operator""_scalar() { return builder; } } diff --git a/dev/functional/cxx_compiler_quirks.h b/dev/functional/cxx_compiler_quirks.h index c6043bbf9..52466e3a3 100644 --- a/dev/functional/cxx_compiler_quirks.h +++ b/dev/functional/cxx_compiler_quirks.h @@ -26,23 +26,13 @@ #if defined(_MSC_VER) && !defined(__clang__) #define SQLITE_ORM_MSVC_SUPPRESS(warncode, ...) SQLITE_ORM_DO_PRAGMA(warning(suppress : warncode)) #else -#define SQLITE_ORM_MSVC_SUPPRESS(warcode, ...) __VA_ARGS__ +#define SQLITE_ORM_MSVC_SUPPRESS(warncode, ...) __VA_ARGS__ #endif #if defined(_MSC_VER) && defined(__clang__) -#define SQLITE_ORM_CLANG_ON_WIN +#define SQLITE_ORM_CLANG_MSVC #endif -// clang has the bad habit of diagnosing missing brace-init-lists when constructing aggregates with base classes. -// This is a false positive, since the C++ standard is quite clear that braces for nested or base objects may be omitted, -// see https://en.cppreference.com/w/cpp/language/aggregate_initialization: -// "The braces around the nested initializer lists may be elided (omitted), -// in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, -// and the subsequent initializer clauses are used to initialize the following members of the object." -// In this sense clang should only warn about missing field initializers. -// Because we know what we are doing, we suppress the diagnostic message -#define SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(...) SQLITE_ORM_CLANG_SUPPRESS("-Wmissing-braces", __VA_ARGS__) - // msvc has the bad habit of diagnosing overalignment of types with an explicit alignment specifier. #define SQLITE_ORM_MSVC_SUPPRESS_OVERALIGNMENT(...) SQLITE_ORM_MSVC_SUPPRESS(4324, __VA_ARGS__) diff --git a/dev/functional/start_macros.h b/dev/functional/start_macros.h index 7ec4f6430..329281ab1 100644 --- a/dev/functional/start_macros.h +++ b/dev/functional/start_macros.h @@ -1,11 +1,24 @@ #pragma once -// Clang has the annoying habit of warning about future C++ features that it claims to support through a feature macro. #ifdef __clang__ #pragma clang diagnostic push +// Clang has the annoying habit of warning about future C++ features that it claims to support through a feature macro. #pragma clang diagnostic ignored "-Wc++20-extensions" #pragma clang diagnostic ignored "-Wc++23-extensions" #pragma clang diagnostic ignored "-Wc++26-extensions" + +// clang has the bad habit of diagnosing missing brace-init-lists when constructing aggregates with base classes. +// This is a false positive, since the C++ standard is quite clear that braces for nested or base objects may be omitted, +// see https://en.cppreference.com/w/cpp/language/aggregate_initialization: +// "The braces around the nested initializer lists may be elided (omitted), +// in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, +// and the subsequent initializer clauses are used to initialize the following members of the object." +// In this sense clang should only warn about missing field initializers. +// Because we know what we are doing, we suppress the diagnostic message +#pragma clang diagnostic ignored "-Wmissing-braces" + +// Unused lambda captures are common in generic code that involves `if contexpr` or `this`-capture. +#pragma clang diagnostic ignored "-Wunused-lambda-capture" #endif #if defined(_MSC_VER) diff --git a/dev/pointer_value.h b/dev/pointer_value.h index 4727f4ae1..d5cfa3d20 100644 --- a/dev/pointer_value.h +++ b/dev/pointer_value.h @@ -32,7 +32,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES inline namespace literals { template - [[nodiscard]] consteval auto operator"" _pointer_type() { + [[nodiscard]] consteval auto operator""_pointer_type() { return internal::explode_into(std::make_index_sequence{}); } } diff --git a/dev/prepared_statement.h b/dev/prepared_statement.h index 7bbed6cf8..e2909bfc7 100644 --- a/dev/prepared_statement.h +++ b/dev/prepared_statement.h @@ -47,7 +47,7 @@ namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3014000 std::string expanded_sql() const { // note: must check return value due to SQLITE_OMIT_TRACE -#ifndef SQLITE_ORM_CLANG_ON_WIN +#ifndef SQLITE_ORM_CLANG_MSVC using char_ptr = std::unique_ptr>; #else struct sqlite3_memory_deleter { diff --git a/dev/schema/column.h b/dev/schema/column.h index 35e059499..62e0dad68 100644 --- a/dev/schema/column.h +++ b/dev/schema/column.h @@ -175,8 +175,7 @@ namespace sqlite_orm { // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}); + return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}; } #endif } @@ -193,8 +192,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}); + return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}; } /** @@ -212,8 +210,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}); + return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}; } /** @@ -231,7 +228,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}); + return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}; } } diff --git a/dev/schema/index.h b/dev/schema/index.h index b6bc92710..00f17b587 100644 --- a/dev/schema/index.h +++ b/dev/schema/index.h @@ -37,8 +37,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { using cols_tuple = std::tuple; static_assert(internal::count_tuple::value <= 1, "amount of where arguments can be 0 or 1"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), false, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}); + return {std::move(name), false, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}; } template @@ -48,8 +47,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { using cols_tuple = std::tuple; static_assert(internal::count_tuple::value <= 1, "amount of where arguments can be 0 or 1"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), false, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}); + return {std::move(name), false, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}; } template @@ -59,7 +57,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { using cols_tuple = std::tuple; static_assert(internal::count_tuple::value <= 1, "amount of where arguments can be 0 or 1"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), true, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}); + return {std::move(name), true, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}; } } diff --git a/dev/schema/table.h b/dev/schema/table.h index c6b7bb6cc..9d97e3962 100644 --- a/dev/schema/table.h +++ b/dev/schema/table.h @@ -131,8 +131,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { static_assert(polyfill::conjunction_v...>, "Incorrect table elements or constraints"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), std::make_tuple(std::forward(definition)...)}); + return {std::move(name), std::make_tuple(std::forward(definition)...)}; } /** @@ -145,8 +144,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { static_assert(polyfill::conjunction_v...>, "Incorrect table elements or constraints"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), std::make_tuple(std::forward(definition)...)}); + return {std::move(name), std::make_tuple(std::forward(definition)...)}; } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES diff --git a/dev/schema/triggers.h b/dev/schema/triggers.h index 0ceb4738d..0bf79e814 100644 --- a/dev/schema/triggers.h +++ b/dev/schema/triggers.h @@ -265,8 +265,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template internal::trigger_t make_trigger(std::string name, const internal::partial_trigger_t& part) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), std::move(part.base), std::move(part.statements)}); + return {std::move(name), std::move(part.base), std::move(part.statements)}; } inline internal::trigger_timing_t before() { diff --git a/dev/schema/view.h b/dev/schema/view.h index 241847140..d5c6b214d 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -137,8 +137,7 @@ namespace sqlite_orm::internal { // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), relativeField, {}, std::tuple{std::move(constraints)...}}); + return {std::move(name), relativeField, {}, std::tuple{std::move(constraints)...}}; } /* @@ -203,12 +202,12 @@ namespace sqlite_orm::internal { column_pointer())>{ pfr::get_relative_address()}))...>; - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return view_type{ + return view_type{ std::move(name), std::tuple{internal::make_column<>(std::string(pfr::get_name()), column_pointer())>{ pfr::get_relative_address()})...}, - std::move(select)}); + std::move(select)}; } } diff --git a/dev/schema/virtual_table.h b/dev/schema/virtual_table.h index dbf6dbf43..8a7a57f4d 100644 --- a/dev/schema/virtual_table.h +++ b/dev/schema/virtual_table.h @@ -113,7 +113,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template internal::virtual_table make_virtual_table(std::string name, internal::virtual_table_description description) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(description)}); + return {std::move(name), std::move(description)}; } /** @@ -124,7 +124,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template internal::virtual_table make_virtual_table(std::string name, internal::virtual_table_definition definition) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(definition)}); + return {std::move(name), std::move(definition)}; } /** diff --git a/dev/serializing_util.h b/dev/serializing_util.h index 9990b8ac8..52c9459c4 100644 --- a/dev/serializing_util.h +++ b/dev/serializing_util.h @@ -87,7 +87,7 @@ namespace sqlite_orm { return stream_identifier(ss, std::get(tpl)...); } -#ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED +#if defined(SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED) && defined(SQLITE_ORM_CONCEPTS_SUPPORTED) template requires polyfill::is_detected_v> void stream_identifier(std::ostream& ss, const Tpl& tpl) { diff --git a/dev/statement_binder.h b/dev/statement_binder.h index 0ea57f03a..51189b7c3 100644 --- a/dev/statement_binder.h +++ b/dev/statement_binder.h @@ -298,7 +298,7 @@ namespace sqlite_orm { template = true> void operator()(const T& t) { - int rc = statement_binder{}.bind(this->stmt, ++this->nthSqlParameter, t); + const int rc = statement_binder{}.bind(this->stmt, ++this->nthSqlParameter, t); if (SQLITE_OK != rc) SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { throw_translated_sqlite_error(rc); } @@ -330,10 +330,11 @@ namespace sqlite_orm { explicit tuple_value_binder(sqlite3_stmt* stmt) : stmt{stmt} {} #ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED - void operator()(const auto& tpl, auto project) const { + template + void operator()(const Tpl& tpl, Projection project) const { int nthSqlParameter = 0; auto& [... elements] = tpl; - (this->bind(std::invoke(project, elements), ++nthSqlParameter), ...); + (this->bind(polyfill::invoke(project, elements), ++nthSqlParameter), ...); } #else template @@ -354,7 +355,7 @@ namespace sqlite_orm { template void bind(const T& t, int nthSqlParameter) const { - int rc = statement_binder{}.bind(this->stmt, nthSqlParameter, t); + const int rc = statement_binder{}.bind(this->stmt, nthSqlParameter, t); if (SQLITE_OK != rc) SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { throw_translated_sqlite_error(rc); } diff --git a/dev/statement_finalizer.h b/dev/statement_finalizer.h index 73b67d295..9f9d92576 100644 --- a/dev/statement_finalizer.h +++ b/dev/statement_finalizer.h @@ -6,7 +6,7 @@ #include // std::integral_constant #endif -#ifdef SQLITE_ORM_CLANG_ON_WIN +#ifdef SQLITE_ORM_CLANG_MSVC namespace sqlite_orm::internal { struct statement_deleter { SQLITE_ORM_STATIC_CALLOP void operator()(sqlite3_stmt* stmt) SQLITE_ORM_OR_CONST_CALLOP noexcept { @@ -18,7 +18,7 @@ namespace sqlite_orm::internal { SQLITE_ORM_EXPORT namespace sqlite_orm { -#ifndef SQLITE_ORM_CLANG_ON_WIN +#ifndef SQLITE_ORM_CLANG_MSVC /** * Guard class which finalizes `sqlite3_stmt` in dtor */ diff --git a/dev/storage.h b/dev/storage.h index 3b60b5fe9..6403b30e7 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -1703,7 +1703,7 @@ namespace sqlite_orm { this->executor.will_run_query(sql); } - switch (int rc = sqlite3_step(stmt)) { + switch (/*int rc =*/sqlite3_step(stmt)) { case SQLITE_ROW: break; case SQLITE_DONE: { diff --git a/dev/table_reference.h b/dev/table_reference.h index 7819d1ffa..728df4905 100644 --- a/dev/table_reference.h +++ b/dev/table_reference.h @@ -38,7 +38,8 @@ namespace sqlite_orm::internal { * Make a table-valued function call. */ template - constexpr table_valued_expression operator()(Args... arguments) const { + constexpr SQLITE_ORM_STATIC_CALLOP table_valued_expression + operator()(Args... arguments) SQLITE_ORM_OR_CONST_CALLOP { return {{ {std::move(arguments)}... }}; } #else @@ -46,7 +47,8 @@ namespace sqlite_orm::internal { * Make a table-valued function call. */ template - constexpr table_valued_expression operator()(Args... arguments) const { + constexpr SQLITE_ORM_STATIC_CALLOP table_valued_expression + operator()(Args... arguments) SQLITE_ORM_OR_CONST_CALLOP { return {{{std::move(arguments)}...}}; } #endif diff --git a/dev/tuple_helper/tuple_transformer.h b/dev/tuple_helper/tuple_transformer.h index 8a715758f..f3d6d0edb 100644 --- a/dev/tuple_helper/tuple_transformer.h +++ b/dev/tuple_helper/tuple_transformer.h @@ -100,7 +100,7 @@ namespace sqlite_orm { return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; } -#ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED +#if defined(SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED) && __cpp_lib_forward_like >= 202207L /* * Like `std::make_from_tuple()`, but using a projection on the tuple elements. */ @@ -127,7 +127,7 @@ namespace sqlite_orm { return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; } -#ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED +#if defined(SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED) && __cpp_lib_forward_like >= 202207L /* * Similar to `create_from_tuple()`, but the result type is specified as a class template. */ diff --git a/dev/util.h b/dev/util.h index 1b88f5892..8d408f6ef 100644 --- a/dev/util.h +++ b/dev/util.h @@ -81,7 +81,7 @@ namespace sqlite_orm { template void perform_step(sqlite3_stmt* stmt, L&& lambda) { - switch (int rc = sqlite3_step(stmt)) { + switch (/*int rc =*/sqlite3_step(stmt)) { case SQLITE_ROW: { lambda(stmt); } break; @@ -97,7 +97,7 @@ namespace sqlite_orm { template void perform_steps(sqlite3_stmt* stmt, L&& lambda) { for (;;) { - switch (int rc = sqlite3_step(stmt)) { + switch (/*int rc =*/sqlite3_step(stmt)) { case SQLITE_ROW: { lambda(stmt); } break; diff --git a/dev/vtabs/dbstat.h b/dev/vtabs/dbstat.h index bcc9b0b5d..6e35a2f30 100644 --- a/dev/vtabs/dbstat.h +++ b/dev/vtabs/dbstat.h @@ -44,7 +44,7 @@ namespace sqlite_orm::internal { template inline virtual_table_definition make_dbstat_definition(Cs... columns) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(columns)...)}}); + return {{std::make_tuple(std::move(columns)...)}}; } } diff --git a/dev/vtabs/fts5.h b/dev/vtabs/fts5.h index 1857c939f..190ac96c9 100644 --- a/dev/vtabs/fts5.h +++ b/dev/vtabs/fts5.h @@ -38,7 +38,7 @@ namespace sqlite_orm::internal { template inline virtual_table_definition make_fts5_definition(Cs... definition) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(definition)...)}}); + return {{std::make_tuple(std::move(definition)...)}}; } } @@ -121,7 +121,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { static_assert((internal::is_fts5_table_element_or_constraint_v && ...), "Incorrect table elements or constraints"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(definition)...)}); + return {std::make_tuple(std::forward(definition)...)}; } template @@ -137,7 +137,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { std::get(definition.elements).name = tableName; #endif - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(tableName), std::move(definition)}); + return {std::move(tableName), std::move(definition)}; } } #endif diff --git a/dev/vtabs/generate_series.h b/dev/vtabs/generate_series.h index b8d4f0407..cfddc3aa9 100644 --- a/dev/vtabs/generate_series.h +++ b/dev/vtabs/generate_series.h @@ -40,7 +40,7 @@ namespace sqlite_orm::internal { template inline virtual_table_definition make_generate_series_definition(Cs... columns) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(columns)...)}}); + return {{std::make_tuple(std::move(columns)...)}}; } } diff --git a/dev/vtabs/rtree.h b/dev/vtabs/rtree.h index 010936608..52f4d5b60 100644 --- a/dev/vtabs/rtree.h +++ b/dev/vtabs/rtree.h @@ -70,7 +70,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::virtual_table_definition using_rtree(Cs... definition) { internal::validate_rtree_definition(); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(definition)...)}); + return {std::make_tuple(std::forward(definition)...)}; } /** * Factory function for a RTREE_I32 virtual table definition. @@ -79,7 +79,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::virtual_table_definition using_rtree_i32(Cs... definition) { internal::validate_rtree_definition(); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(definition)...)}); + return {std::make_tuple(std::forward(definition)...)}; } } #endif diff --git a/examples/rtree.cpp b/examples/rtree.cpp index 5a60a3075..b0eb60a2e 100644 --- a/examples/rtree.cpp +++ b/examples/rtree.cpp @@ -6,8 +6,9 @@ #include +// note: clang currently has problems to use constexpr variables in lambdas #if defined(SQLITE_ENABLE_RTREE) && SQLITE_VERSION_NUMBER >= 3024000 && defined(SQLITE_ORM_CPP20_RANGES_SUPPORTED) && \ - defined(SQLITE_ORM_WITH_CPP20_ALIASES) + defined(SQLITE_ORM_WITH_CPP20_ALIASES) && !defined(__clang__) #define ENABLE_THIS_EXAMPLE #endif diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 782c1998a..f3dd92ba9 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -1,11 +1,24 @@ #pragma once -// Clang has the annoying habit of warning about future C++ features that it claims to support through a feature macro. #ifdef __clang__ #pragma clang diagnostic push +// Clang has the annoying habit of warning about future C++ features that it claims to support through a feature macro. #pragma clang diagnostic ignored "-Wc++20-extensions" #pragma clang diagnostic ignored "-Wc++23-extensions" #pragma clang diagnostic ignored "-Wc++26-extensions" + +// clang has the bad habit of diagnosing missing brace-init-lists when constructing aggregates with base classes. +// This is a false positive, since the C++ standard is quite clear that braces for nested or base objects may be omitted, +// see https://en.cppreference.com/w/cpp/language/aggregate_initialization: +// "The braces around the nested initializer lists may be elided (omitted), +// in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, +// and the subsequent initializer clauses are used to initialize the following members of the object." +// In this sense clang should only warn about missing field initializers. +// Because we know what we are doing, we suppress the diagnostic message +#pragma clang diagnostic ignored "-Wmissing-braces" + +// Unused lambda captures are common in generic code that involves `if contexpr` or `this`-capture. +#pragma clang diagnostic ignored "-Wunused-lambda-capture" #endif #if defined(_MSC_VER) @@ -161,23 +174,13 @@ using std::nullptr_t; #if defined(_MSC_VER) && !defined(__clang__) #define SQLITE_ORM_MSVC_SUPPRESS(warncode, ...) SQLITE_ORM_DO_PRAGMA(warning(suppress : warncode)) #else -#define SQLITE_ORM_MSVC_SUPPRESS(warcode, ...) __VA_ARGS__ +#define SQLITE_ORM_MSVC_SUPPRESS(warncode, ...) __VA_ARGS__ #endif #if defined(_MSC_VER) && defined(__clang__) -#define SQLITE_ORM_CLANG_ON_WIN +#define SQLITE_ORM_CLANG_MSVC #endif -// clang has the bad habit of diagnosing missing brace-init-lists when constructing aggregates with base classes. -// This is a false positive, since the C++ standard is quite clear that braces for nested or base objects may be omitted, -// see https://en.cppreference.com/w/cpp/language/aggregate_initialization: -// "The braces around the nested initializer lists may be elided (omitted), -// in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, -// and the subsequent initializer clauses are used to initialize the following members of the object." -// In this sense clang should only warn about missing field initializers. -// Because we know what we are doing, we suppress the diagnostic message -#define SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(...) SQLITE_ORM_CLANG_SUPPRESS("-Wmissing-braces", __VA_ARGS__) - // msvc has the bad habit of diagnosing overalignment of types with an explicit alignment specifier. #define SQLITE_ORM_MSVC_SUPPRESS_OVERALIGNMENT(...) SQLITE_ORM_MSVC_SUPPRESS(4324, __VA_ARGS__) @@ -1729,7 +1732,7 @@ namespace sqlite_orm { return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; } -#ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED +#if defined(SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED) && __cpp_lib_forward_like >= 202207L /* * Like `std::make_from_tuple()`, but using a projection on the tuple elements. */ @@ -1756,7 +1759,7 @@ namespace sqlite_orm { return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; } -#ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED +#if defined(SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED) && __cpp_lib_forward_like >= 202207L /* * Similar to `create_from_tuple()`, but the result type is specified as a class template. */ @@ -2454,7 +2457,8 @@ namespace sqlite_orm::internal { * Make a table-valued function call. */ template - constexpr table_valued_expression operator()(Args... arguments) const { + constexpr SQLITE_ORM_STATIC_CALLOP table_valued_expression + operator()(Args... arguments) SQLITE_ORM_OR_CONST_CALLOP { return {{ {std::move(arguments)}... }}; } #else @@ -2462,7 +2466,8 @@ namespace sqlite_orm::internal { * Make a table-valued function call. */ template - constexpr table_valued_expression operator()(Args... arguments) const { + constexpr SQLITE_ORM_STATIC_CALLOP table_valued_expression + operator()(Args... arguments) SQLITE_ORM_OR_CONST_CALLOP { return {{ {std::move(arguments)}... }}; } #endif @@ -3094,7 +3099,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * constexpr orm_table_alias auto z_alias = "z"_alias.for_(); */ template - [[nodiscard]] consteval auto operator"" _alias() { + [[nodiscard]] consteval auto operator""_alias() { return internal::explode_into( std::make_index_sequence{}); } @@ -3104,7 +3109,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * E.g. "a"_col, "b"_col */ template - [[nodiscard]] consteval auto operator"" _col() { + [[nodiscard]] consteval auto operator""_col() { return internal::explode_into(std::make_index_sequence{}); } } @@ -3117,7 +3122,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * E.g. 1_colalias, 2_colalias */ template - [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator"" _colalias() { + [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator""_colalias() { // numeric identifiers are used for automatically assigning implicit aliases to unaliased column expressions, // which start at "1". static_assert(std::array{Chars...}[0] > '0'); @@ -8919,7 +8924,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * E.g. 1_ctealias, 2_ctealias */ template - [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator"" _ctealias() { + [[nodiscard]] SQLITE_ORM_CONSTEVAL auto operator""_ctealias() { return internal::cte_moniker{}; } @@ -8929,7 +8934,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * E.g. "1"_cte, "2"_cte */ template - [[nodiscard]] consteval auto operator"" _cte() { + [[nodiscard]] consteval auto operator""_cte() { return internal::explode_into(std::make_index_sequence{}); } #endif @@ -9161,8 +9166,7 @@ namespace sqlite_orm { // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}); + return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}; } #endif } @@ -9179,8 +9183,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}); + return {std::move(name), memberPointer, {}, std::tuple{std::move(constraints)...}}; } /** @@ -9198,8 +9201,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}); + return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}; } /** @@ -9217,8 +9219,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}); + return {std::move(name), getter, setter, std::tuple{std::move(constraints)...}}; } } @@ -10378,7 +10379,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES inline namespace literals { template - [[nodiscard]] consteval auto operator"" _pointer_type() { + [[nodiscard]] consteval auto operator""_pointer_type() { return internal::explode_into(std::make_index_sequence{}); } } @@ -10921,7 +10922,7 @@ namespace sqlite_orm { template = true> void operator()(const T& t) { - int rc = statement_binder{}.bind(this->stmt, ++this->nthSqlParameter, t); + const int rc = statement_binder{}.bind(this->stmt, ++this->nthSqlParameter, t); if (SQLITE_OK != rc) SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { throw_translated_sqlite_error(rc); } @@ -10953,10 +10954,11 @@ namespace sqlite_orm { explicit tuple_value_binder(sqlite3_stmt* stmt) : stmt{stmt} {} #ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED - void operator()(const auto& tpl, auto project) const { + template + void operator()(const Tpl& tpl, Projection project) const { int nthSqlParameter = 0; auto& [... elements] = tpl; - (this->bind(std::invoke(project, elements), ++nthSqlParameter), ...); + (this->bind(polyfill::invoke(project, elements), ++nthSqlParameter), ...); } #else template @@ -10977,7 +10979,7 @@ namespace sqlite_orm { template void bind(const T& t, int nthSqlParameter) const { - int rc = statement_binder{}.bind(this->stmt, nthSqlParameter, t); + const int rc = statement_binder{}.bind(this->stmt, nthSqlParameter, t); if (SQLITE_OK != rc) SQLITE_ORM_CPP_UNLIKELY /*possible but unexpected*/ { throw_translated_sqlite_error(rc); } @@ -12082,7 +12084,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * auto rows = storage.select(equal_to_int_3_f(1, 1)); */ template - [[nodiscard]] consteval auto operator"" _scalar() { + [[nodiscard]] consteval auto operator""_scalar() { return builder; } } @@ -13005,8 +13007,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { using cols_tuple = std::tuple; static_assert(internal::count_tuple::value <= 1, "amount of where arguments can be 0 or 1"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), false, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}); + return {std::move(name), false, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}; } template @@ -13016,8 +13017,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { using cols_tuple = std::tuple; static_assert(internal::count_tuple::value <= 1, "amount of where arguments can be 0 or 1"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), false, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}); + return {std::move(name), false, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}; } template @@ -13027,8 +13027,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { using cols_tuple = std::tuple; static_assert(internal::count_tuple::value <= 1, "amount of where arguments can be 0 or 1"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), true, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}); + return {std::move(name), true, std::make_tuple(internal::make_indexed_column(std::move(cols))...)}; } } @@ -13142,8 +13141,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { static_assert(polyfill::conjunction_v...>, "Incorrect table elements or constraints"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), std::make_tuple(std::forward(definition)...)}); + return {std::move(name), std::make_tuple(std::forward(definition)...)}; } /** @@ -13156,8 +13154,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { static_assert(polyfill::conjunction_v...>, "Incorrect table elements or constraints"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), std::make_tuple(std::forward(definition)...)}); + return {std::move(name), std::make_tuple(std::forward(definition)...)}; } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES @@ -13356,8 +13353,7 @@ namespace sqlite_orm::internal { // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, // as this will lead to UB with Clang on MinGW! - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), relativeField, {}, std::tuple{std::move(constraints)...}}); + return {std::move(name), relativeField, {}, std::tuple{std::move(constraints)...}}; } /* @@ -13422,12 +13418,12 @@ namespace sqlite_orm::internal { column_pointer())>{ pfr::get_relative_address()}))...>; - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return view_type{ + return view_type{ std::move(name), std::tuple{internal::make_column<>(std::string(pfr::get_name()), column_pointer())>{ pfr::get_relative_address()})...}, - std::move(select)}); + std::move(select)}; } } @@ -14325,7 +14321,7 @@ namespace sqlite_orm { #include // std::integral_constant #endif -#ifdef SQLITE_ORM_CLANG_ON_WIN +#ifdef SQLITE_ORM_CLANG_MSVC namespace sqlite_orm::internal { struct statement_deleter { SQLITE_ORM_STATIC_CALLOP void operator()(sqlite3_stmt* stmt) SQLITE_ORM_OR_CONST_CALLOP noexcept { @@ -14337,7 +14333,7 @@ namespace sqlite_orm::internal { SQLITE_ORM_EXPORT namespace sqlite_orm { -#ifndef SQLITE_ORM_CLANG_ON_WIN +#ifndef SQLITE_ORM_CLANG_MSVC /** * Guard class which finalizes `sqlite3_stmt` in dtor */ @@ -14540,7 +14536,7 @@ namespace sqlite_orm { template void perform_step(sqlite3_stmt* stmt, L&& lambda) { - switch (int rc = sqlite3_step(stmt)) { + switch (/*int rc =*/sqlite3_step(stmt)) { case SQLITE_ROW: { lambda(stmt); } break; @@ -14556,7 +14552,7 @@ namespace sqlite_orm { template void perform_steps(sqlite3_stmt* stmt, L&& lambda) { for (;;) { - switch (int rc = sqlite3_step(stmt)) { + switch (/*int rc =*/sqlite3_step(stmt)) { case SQLITE_ROW: { lambda(stmt); } break; @@ -15660,7 +15656,7 @@ namespace sqlite_orm { #if SQLITE_VERSION_NUMBER >= 3014000 std::string expanded_sql() const { // note: must check return value due to SQLITE_OMIT_TRACE -#ifndef SQLITE_ORM_CLANG_ON_WIN +#ifndef SQLITE_ORM_CLANG_MSVC using char_ptr = std::unique_ptr>; #else struct sqlite3_memory_deleter { @@ -17812,7 +17808,7 @@ namespace sqlite_orm { return stream_identifier(ss, std::get(tpl)...); } -#ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED +#if defined(SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED) && defined(SQLITE_ORM_CONCEPTS_SUPPORTED) template requires polyfill::is_detected_v> void stream_identifier(std::ostream& ss, const Tpl& tpl) { @@ -20978,7 +20974,7 @@ namespace sqlite_orm { // 3. fill in blanks with numerical column identifiers { #ifdef SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED - for (size_t n = 1; std::string & name: columnNames) { + for (size_t n = 1; std::string& name: columnNames) { if (name.empty()) { name = std::to_string(n); } @@ -21363,8 +21359,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template internal::trigger_t make_trigger(std::string name, const internal::partial_trigger_t& part) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), std::move(part.base), std::move(part.statements)}); + return {std::move(name), std::move(part.base), std::move(part.statements)}; } inline internal::trigger_timing_t before() { @@ -21509,7 +21504,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template internal::virtual_table make_virtual_table(std::string name, internal::virtual_table_description description) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(description)}); + return {std::move(name), std::move(description)}; } /** @@ -21520,7 +21515,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template internal::virtual_table make_virtual_table(std::string name, internal::virtual_table_definition definition) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), std::move(definition)}); + return {std::move(name), std::move(definition)}; } /** @@ -24286,8 +24281,7 @@ namespace sqlite_orm { template cte_table make_cte_table(std::string name, Cs... args) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES( - return {std::move(name), std::make_tuple(std::forward(args)...)}); + return {std::move(name), std::make_tuple(std::forward(args)...)}; } // aliased column expressions, explicit or implicitly numbered @@ -26274,7 +26268,7 @@ namespace sqlite_orm { this->executor.will_run_query(sql); } - switch (int rc = sqlite3_step(stmt)) { + switch (/*int rc =*/sqlite3_step(stmt)) { case SQLITE_ROW: break; case SQLITE_DONE: { @@ -27296,7 +27290,7 @@ namespace sqlite_orm::internal { template inline virtual_table_definition make_dbstat_definition(Cs... columns) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(columns)...)}}); + return {{std::make_tuple(std::move(columns)...)}}; } } @@ -27434,7 +27428,7 @@ namespace sqlite_orm::internal { template inline virtual_table_definition make_generate_series_definition(Cs... columns) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(columns)...)}}); + return {{std::make_tuple(std::move(columns)...)}}; } } @@ -27535,7 +27529,7 @@ namespace sqlite_orm::internal { template inline virtual_table_definition make_fts5_definition(Cs... definition) { - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {{std::make_tuple(std::move(definition)...)}}); + return {{std::make_tuple(std::move(definition)...)}}; } } @@ -27618,7 +27612,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { static_assert((internal::is_fts5_table_element_or_constraint_v && ...), "Incorrect table elements or constraints"); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(definition)...)}); + return {std::make_tuple(std::forward(definition)...)}; } template @@ -27634,7 +27628,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { std::get(definition.elements).name = tableName; #endif - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(tableName), std::move(definition)}); + return {std::move(tableName), std::move(definition)}; } } #endif @@ -27716,7 +27710,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::virtual_table_definition using_rtree(Cs... definition) { internal::validate_rtree_definition(); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(definition)...)}); + return {std::make_tuple(std::forward(definition)...)}; } /** * Factory function for a RTREE_I32 virtual table definition. @@ -27725,7 +27719,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { internal::virtual_table_definition using_rtree_i32(Cs... definition) { internal::validate_rtree_definition(); - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::make_tuple(std::forward(definition)...)}); + return {std::make_tuple(std::forward(definition)...)}; } } #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b58930626..fff28664c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -74,7 +74,7 @@ if(MSVC) # C4458: declaration of 'symbol' hides class member /wd4458 # C4458: 'symbol': deprecated - # disabled due to unit tests still testing deprecated features + # disabled due to unit tests testing deprecated features /wd4996) if (CMAKE_CXX_FLAGS MATCHES "/D_UNICODE") # explicitly set the entry point of the executable file, @@ -84,8 +84,13 @@ if(MSVC) endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") target_compile_options(unit_tests PUBLIC - # disabled due to unit tests still testing deprecated features - -Wno-deprecated-declarations) + -Wno-c++23-extensions + # disabled due to unit tests testing deprecated features + -Wno-deprecated-declarations + # perfectly valid to have missing field initializers in library's test code + -Wno-missing-field-initializers + # suppress warnings about unused variables in test code + -Wno-unused-variable) endif() target_precompile_headers(unit_tests PRIVATE diff --git a/tests/static_tests/functional/tuple_traits.cpp b/tests/static_tests/functional/tuple_traits.cpp index f35cd84d7..60af76aea 100644 --- a/tests/static_tests/functional/tuple_traits.cpp +++ b/tests/static_tests/functional/tuple_traits.cpp @@ -13,7 +13,6 @@ using internal::tuple_has_template; using internal::tuple_has_type; TEST_CASE("tuple traits") { - using empty_tuple_type = std::tuple<>; using tuple_type = std::tuple, primary_key_t<>, std::string>; STATIC_REQUIRE(internal::tuple_has::value); diff --git a/tests/static_tests/virtual_tables.cpp b/tests/static_tests/virtual_tables.cpp index 10042e0ba..7ae721ecd 100644 --- a/tests/static_tests/virtual_tables.cpp +++ b/tests/static_tests/virtual_tables.cpp @@ -48,7 +48,6 @@ TEST_CASE("generic vtab and dbstat layout tests") { true #endif )); - using table_type = decltype(table); using elements_type = decltype(table.elements); #if SQLITE_VERSION_NUMBER >= 3031000 STATIC_REQUIRE(table_values_index_sequence::size() == 2); diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index c54a0b01f..67d25abb0 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -200,8 +200,8 @@ struct SQLITE_ORM_MSVC_SUPPRESS_OVERALIGNMENT(alignas(2 * __STDCPP_DEFAULT_NEW_A } }; -struct SQLITE_ORM_MSVC_SUPPRESS_OVERALIGNMENT(alignas(2 * - __STDCPP_DEFAULT_NEW_ALIGNMENT__)) OverAlignedAggregateFunction { +struct SQLITE_ORM_MSVC_SUPPRESS_OVERALIGNMENT(alignas(2 * __STDCPP_DEFAULT_NEW_ALIGNMENT__)) + OverAlignedAggregateFunction { double sum = 0; void step(double arg) { diff --git a/tests/xdestroy_handling.cpp b/tests/xdestroy_handling.cpp index 68dbe5a9d..0af2a32f2 100644 --- a/tests/xdestroy_handling.cpp +++ b/tests/xdestroy_handling.cpp @@ -23,7 +23,7 @@ template inline constexpr delete_default_t delete_default_f{}; #endif -#ifndef SQLITE_ORM_CLANG_ON_WIN +#ifndef SQLITE_ORM_CLANG_MSVC using free_t = std::integral_constant; inline constexpr free_t free_f{}; #endif @@ -64,7 +64,7 @@ TEST_CASE("obtain_xdestroy_for") { STATIC_REQUIRE(xDestroy1 == nullptr); REQUIRE(xDestroy1 == nullptr); -#ifndef SQLITE_ORM_CLANG_ON_WIN +#ifndef SQLITE_ORM_CLANG_MSVC // free(int*) constexpr xdestroy_fn_t xDestroy2 = obtain_xdestroy_for(free, int_nullptr); STATIC_REQUIRE(xDestroy2 == &free); @@ -120,7 +120,7 @@ TEST_CASE("obtain_xdestroy_for") { #endif #if __cpp_constexpr >= 201907L // Trivial default initialization in constexpr functions -#ifndef SQLITE_ORM_CLANG_ON_WIN +#ifndef SQLITE_ORM_CLANG_MSVC // xdestroy_holder{ free }(int*) constexpr xdestroy_fn_t xDestroy8 = obtain_xdestroy_for(xdestroy_holder{free}, int_nullptr); STATIC_REQUIRE(xDestroy8 == &free); From 8de3fac245aa6d838d3b62800a462b4fb3339a42 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 9 Dec 2025 13:56:15 +0200 Subject: [PATCH 27/49] Added an example for SQL views --- examples/view.cpp | 211 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 examples/view.cpp diff --git a/examples/view.cpp b/examples/view.cpp new file mode 100644 index 000000000..c52abab5d --- /dev/null +++ b/examples/view.cpp @@ -0,0 +1,211 @@ +/** + * This example demonstrates how to use SQL views with sqlite_orm. + * + * Views in SQL are virtual tables based on the result-set of a SELECT statement. + * With sqlite_orm, views are defined using make_view() which leverages C++ reflection + * to automatically map columns from the SELECT statement to struct fields. + * + * Unlike tables created with make_table(), views created with make_view() don't require + * explicit column definitions - the columns are automatically derived from the view's + * object type through reflection. + */ + +#include +#include +#include +#include +#include // std::remove + +#ifdef SQLITE_ORM_WITH_VIEW +#define ENABLE_THIS_EXAMPLE +#endif + +#ifdef ENABLE_THIS_EXAMPLE +using namespace sqlite_orm; +using std::cout; +using std::endl; +using std::make_unique; +using std::string; + +// Base tables +struct Employee { + int64 id; + std::string name; + int64 department_id; + double salary; +}; + +struct Department { + int64 id; + std::string name; + std::string location; +}; + +// View objects - note how we only define the struct fields, no column mappings needed! +// The fields are automatically mapped through C++ reflection + +// View 1: High earners (employees earning more than 60000) +struct HighEarner { + int64 id; + std::string name; + double salary; +}; + +// View 2: Department summary with employee count and average salary +struct DepartmentSummary { + std::string department_name; + int employee_count; + double avg_salary; +}; + +// View 3: Complete employee information with department details (join result) +struct EmployeeDetail { + int64 id; + std::string employee_name; + double salary; + std::string department_name; + std::string location; +}; + +inline auto initStorage(const std::string& path) { + return make_storage( + path, + // Define base tables + make_table("employees", + make_column("id", &Employee::id, primary_key()), + make_column("name", &Employee::name), + make_column("department_id", &Employee::department_id), + make_column("salary", &Employee::salary)), + make_table("departments", + make_column("id", &Department::id, primary_key()), + make_column("name", &Department::name), + make_column("location", &Department::location)), + + // Define views - notice how we only specify the view name and SELECT statement + // The column mappings are automatically derived from the view object type! + + // View 1: Filter high earners + make_view( + "high_earners", + select(columns(&Employee::id, &Employee::name, &Employee::salary), where(c(&Employee::salary) > 60000.0))), + + // View 2: Aggregate data by department + make_view("department_summary", + select(columns(&Department::name, count(&Employee::id), avg(&Employee::salary)), + left_join(on(c(&Employee::department_id) == &Department::id)), + group_by(&Department::name))), + + // View 3: Join employees with departments + make_view( + "employee_details", + select(columns(&Employee::id, &Employee::name, &Employee::salary, &Department::name, &Department::location), + join(on(c(&Employee::department_id) == &Department::id))))); +} +#endif + +int main() { +#ifdef ENABLE_THIS_EXAMPLE + try { + cout << "=== SQL Views Example ===" << endl << endl; + + std::remove("view_example.sqlite"); + auto storage = initStorage("view_example.sqlite"); + storage.sync_schema(); + + storage.transaction([&storage]() { + // Insert departments + cout << "Inserting departments..." << endl; + auto engineeringId = storage.insert(Department{0, "Engineering", "San Francisco"}); + auto salesId = storage.insert(Department{0, "Sales", "New York"}); + auto hrId = storage.insert(Department{0, "HR", "Chicago"}); + auto marketingId = storage.insert(Department{0, "Marketing", "Austin"}); + + // Insert employees + cout << "Inserting employees..." << endl; + storage.insert(Employee{0, "Alice Johnson", engineeringId, 95000.0}); + storage.insert(Employee{0, "Bob Smith", engineeringId, 87000.0}); + storage.insert(Employee{0, "Carol Williams", salesId, 72000.0}); + storage.insert(Employee{0, "David Brown", salesId, 68000.0}); + storage.insert(Employee{0, "Eve Davis", hrId, 55000.0}); + storage.insert(Employee{0, "Frank Miller", marketingId, 62000.0}); + storage.insert(Employee{0, "Grace Wilson", engineeringId, 78000.0}); + storage.insert(Employee{0, "Henry Moore", salesId, 45000.0}); + + return true; + }); + + cout << endl; + + // Query View 1: High Earners + cout << "=== View 1: High Earners (salary > $60,000) ===" << endl; + auto highEarners = storage.select(object()); + cout << "ID\tName\t\t\tSalary" << endl; + cout << "---\t----\t\t\t------" << endl; + for (const auto& earner: highEarners) { + cout << earner.id << '\t' << earner.name << (earner.name.length() < 16 ? "\t" : "") << '\t' << "$" + << earner.salary << endl; + } + cout << "Total high earners: " << highEarners.size() << endl << endl; + + // Query View 2: Department Summary + cout << "=== View 2: Department Summary ===" << endl; + auto summaries = storage.select(object()); + cout << "Department\tEmployees\tAvg Salary" << endl; + cout << "----------\t---------\t----------" << endl; + for (const auto& summary: summaries) { + cout << summary.department_name << '\t'; + if (summary.department_name.length() < 8) + cout << '\t'; + cout << summary.employee_count << "\t\t"; + if (summary.employee_count > 0) { + cout << "$" << static_cast(summary.avg_salary); + } else { + cout << "N/A"; + } + cout << endl; + } + cout << endl; + + // Query View 3: Employee Details + cout << "=== View 3: Employee Details (with Department Info) ===" << endl; + auto details = storage.select(object()); + cout << "ID\tEmployee\t\tSalary\t\tDepartment\tLocation" << endl; + cout << "--\t--------\t\t------\t\t----------\t--------" << endl; + for (const auto& detail: details) { + cout << detail.id << '\t' << detail.employee_name << (detail.employee_name.length() < 16 ? "\t" : "") + << '\t' << "$" << detail.salary << '\t' << detail.department_name << '\t'; + if (detail.department_name.length() < 8) + cout << '\t'; + cout << detail.location << endl; + } + cout << endl; + + // Demonstrate that you can also query views with conditions + cout << "=== View Query with Additional Filter ===" << endl; + cout << "High earners from Engineering department:" << endl; + auto engineeringHighEarners = storage.select( + object(), + where(c(&EmployeeDetail::department_name) == "Engineering" and c(&EmployeeDetail::salary) > 60000.0)); + + for (const auto& emp: engineeringHighEarners) { + cout << " - " << emp.employee_name << ": $" << emp.salary << endl; + } + cout << endl; + + // Show view metadata + cout << "=== View Metadata ===" << endl; + auto viewNames = storage.view_names(); + cout << "Views in database:" << endl; + for (const auto& viewName: viewNames) { + cout << " - " << viewName << endl; + } + cout << endl; + + cout << "Example completed successfully!" << endl; + } catch (const std::system_error& e) { + cout << "System error: " << e.what() << " (code " << e.code() << ")" << endl; + } +#endif + + return 0; +} From e8d30b05353a58cb637ffab75972fb123e073642 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Apr 2026 20:53:45 +0200 Subject: [PATCH 28/49] Compile with `gcc-16 -std=c++26 -freflection` --- dev/functional/config.h | 2 +- dev/schema/view.h | 9 ++++----- examples/CMakeLists.txt | 5 +++++ include/sqlite_orm/sqlite_orm.h | 11 +++++------ tests/CMakeLists.txt | 5 +++++ 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/dev/functional/config.h b/dev/functional/config.h index 7e0a7fce6..12a78ca26 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -90,7 +90,7 @@ // note: PFR depends on `SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED` for field name determination #if (defined(SQLITE_ORM_CONSTEVAL_SUPPORTED) && defined(SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED)) && \ (defined(SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED)) && (__cpp_lib_byte >= 201603L) && \ - (__cpp_impl_reflection >= 202500L || BOOST_PFR_ENABLED == 1) + (defined(SQLITE_ORM_REFLECTION_SUPPORTED) || BOOST_PFR_ENABLED == 1) #define SQLITE_ORM_WITH_VIEW #endif diff --git a/dev/schema/view.h b/dev/schema/view.h index d5c6b214d..d94cb37fb 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -9,7 +9,7 @@ #endif #ifdef SQLITE_ORM_WITH_VIEW -#if __cpp_impl_reflection < 202506L +#ifndef SQLITE_ORM_REFLECTION_SUPPORTED #ifdef SQLITE_ORM_HAS_BOOST_PFR #include #endif @@ -103,10 +103,9 @@ namespace sqlite_orm::internal { Select, decltype(make_column(std::string(std::get(memberNames)), std::get(memberPointers)))...>; - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return view_type{ - std::move(name), - std::tuple{make_column(std::string(std::get(memberNames)), std::get(memberPointers))...}, - std::move(select)}); + return view_type{std::move(name), + std::tuple{make_column(std::string(std::get(memberNames)), std::get(memberPointers))...}, + std::move(select)}; } } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 966217362..cc9e0955e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -23,6 +23,11 @@ foreach(file ${files}) COMMENT "Running example: ${file_basename}" VERBATIM ) + + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 16) + target_compile_options(unit_tests PUBLIC + -freflection) + endif() list(APPEND run_example_targets run_${file_basename}) endforeach() diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index f524b3ea6..530655807 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -357,7 +357,7 @@ using std::nullptr_t; // note: PFR depends on `SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED` for field name determination #if (defined(SQLITE_ORM_CONSTEVAL_SUPPORTED) && defined(SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED)) && \ (defined(SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED)) && (__cpp_lib_byte >= 201603L) && \ - (__cpp_impl_reflection >= 202500L || BOOST_PFR_ENABLED == 1) + (defined(SQLITE_ORM_REFLECTION_SUPPORTED) || BOOST_PFR_ENABLED == 1) #define SQLITE_ORM_WITH_VIEW #endif @@ -13115,7 +13115,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #endif #ifdef SQLITE_ORM_WITH_VIEW -#if __cpp_impl_reflection < 202506L +#ifndef SQLITE_ORM_REFLECTION_SUPPORTED #ifdef SQLITE_ORM_HAS_BOOST_PFR #include #endif @@ -13253,10 +13253,9 @@ namespace sqlite_orm::internal { Select, decltype(make_column(std::string(std::get(memberNames)), std::get(memberPointers)))...>; - SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return view_type{ - std::move(name), - std::tuple{make_column(std::string(std::get(memberNames)), std::get(memberPointers))...}, - std::move(select)}); + return view_type{std::move(name), + std::tuple{make_column(std::string(std::get(memberNames)), std::get(memberPointers))...}, + std::move(select)}; } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3f280ac1e..4f5933ca1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -90,6 +90,11 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") # suppress warnings about unused variables in test code -Wno-unused-variable) endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 16) + target_compile_options(unit_tests PUBLIC + -freflection) +endif() target_precompile_headers(unit_tests PRIVATE From 98709d04b22cbdd500c55e3739e6ba489f107c08 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Apr 2026 22:07:43 +0200 Subject: [PATCH 29/49] `rank()` should only be used as a window function --- dev/ast/rank.h | 2 +- dev/conditions.h | 9 + include/sqlite_orm/sqlite_orm.h | 472 ++++++++++++++++---------------- 3 files changed, 251 insertions(+), 232 deletions(-) diff --git a/dev/ast/rank.h b/dev/ast/rank.h index 9b101f4e9..2b2fe1c10 100644 --- a/dev/ast/rank.h +++ b/dev/ast/rank.h @@ -17,7 +17,7 @@ namespace sqlite_orm::internal { SQLITE_ORM_EXPORT namespace sqlite_orm { /** - * RANK() window function / FTS5 rank keyword. + * RANK() window function * https://sqlite.org/windowfunctions.html#built-in_window_functions */ inline internal::rank_t rank() { diff --git a/dev/conditions.h b/dev/conditions.h index 81f7ded90..0b2eddcfc 100644 --- a/dev/conditions.h +++ b/dev/conditions.h @@ -27,6 +27,7 @@ #include "type_printer.h" #include "literal.h" #include "ast/cross_join.h" +#include "ast/rank.h" namespace sqlite_orm::internal { /** @@ -1016,6 +1017,14 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { return {std::move(o)}; } + /** + * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. + */ + [[deprecated("Use the hidden FTS5 rank column instead")]] + inline internal::order_by_t order_by(internal::rank_t o) { + return {std::move(o)}; + } + /** * ORDER BY positional ordinal * diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 495de0073..dc755e67b 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -5283,6 +5283,239 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { } } +// #include "ast/rank.h" + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#include // std::forward +#endif + +// #include "window.h" + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#include // std::string +#include // std::tuple +#include // std::forward, std::move +#include // std::forward, std::move +#endif + +// #include "../functional/cxx_type_traits_polyfill.h" + +namespace sqlite_orm::internal { + + struct unbounded_preceding_t {}; + + template + struct preceding_t { + E expression; + }; + + struct current_row_t {}; + + template + struct following_t { + E expression; + }; + + struct unbounded_following_t {}; + + enum class frame_type_t { rows, range, groups }; + enum class frame_exclude_t { no_others, current_row, group, ties }; + + template + struct frame_spec_t { + frame_type_t type; + Start start; + End end; + frame_exclude_t exclude = frame_exclude_t::no_others; + + frame_spec_t exclude_current_row() const { + auto res = *this; + res.exclude = frame_exclude_t::current_row; + return res; + } + + frame_spec_t exclude_group() const { + auto res = *this; + res.exclude = frame_exclude_t::group; + return res; + } + + frame_spec_t exclude_ties() const { + auto res = *this; + res.exclude = frame_exclude_t::ties; + return res; + } + + frame_spec_t exclude_no_others() const { + auto res = *this; + res.exclude = frame_exclude_t::no_others; + return res; + } + }; + + template + struct partition_by_t { + using arguments_type = std::tuple; + arguments_type arguments; + }; + + template + inline constexpr bool is_partition_by_v = polyfill::is_specialization_of_v; + + template + using is_partition_by = polyfill::bool_constant>; + + struct window_ref_t { + std::string name; + }; + + template + struct over_t { + using function_type = F; + using arguments_type = std::tuple; + + function_type function; + arguments_type arguments; + }; + + template + inline constexpr bool is_over_v = polyfill::is_specialization_of_v; + + template + using is_over = polyfill::bool_constant>; + + template + struct window_defn_t { + std::string name; + using arguments_type = std::tuple; + arguments_type arguments; + }; + + template + inline constexpr bool is_window_defn_v = polyfill::is_specialization_of_v; + + template + using is_window_defn = polyfill::bool_constant>; +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + + /** + * UNBOUNDED PRECEDING frame boundary. + * https://sqlite.org/windowfunctions.html + */ + inline internal::unbounded_preceding_t unbounded_preceding() { + return {}; + } + + /** + * expr PRECEDING frame boundary. + * https://sqlite.org/windowfunctions.html + */ + template + internal::preceding_t preceding(E expression) { + return {std::move(expression)}; + } + + /** + * CURRENT ROW frame boundary. + * https://sqlite.org/windowfunctions.html + */ + inline internal::current_row_t current_row() { + return {}; + } + + /** + * expr FOLLOWING frame boundary. + * https://sqlite.org/windowfunctions.html + */ + template + internal::following_t following(E expression) { + return {std::move(expression)}; + } + + /** + * UNBOUNDED FOLLOWING frame boundary. + * https://sqlite.org/windowfunctions.html + */ + inline internal::unbounded_following_t unbounded_following() { + return {}; + } + + /** + * ROWS BETWEEN start AND end frame specification. + * Example: rows(unbounded_preceding(), current_row()) + */ + template + internal::frame_spec_t rows(Start start, End end) { + return {internal::frame_type_t::rows, std::move(start), std::move(end)}; + } + + /** + * RANGE BETWEEN start AND end frame specification. + * Example: range(current_row(), unbounded_following()) + */ + template + internal::frame_spec_t range(Start start, End end) { + return {internal::frame_type_t::range, std::move(start), std::move(end)}; + } + + /** + * GROUPS BETWEEN start AND end frame specification. + * Example: groups(unbounded_preceding(), current_row()) + */ + template + internal::frame_spec_t groups(Start start, End end) { + return {internal::frame_type_t::groups, std::move(start), std::move(end)}; + } + + /** + * PARTITION BY expression list for window functions. + * Example: partition_by(&Employee::departmentId) + */ + template + internal::partition_by_t partition_by(Args... args) { + return {{std::forward(args)...}}; + } + + /** + * Reference to a named window definition (OVER window_name). + * Example: row_number().over(window_ref("win")) + */ + inline internal::window_ref_t window_ref(std::string name) { + return {std::move(name)}; + } + + /** + * Named window definition (WINDOW name AS (...)). + * Passed as a condition to select(). + * Example: window("win", order_by(&Employee::salary)) + */ + template + internal::window_defn_t window(std::string name, Args... args) { + return {std::move(name), {std::forward(args)...}}; + } +} + +namespace sqlite_orm::internal { + struct rank_t { + template + over_t over(OverArgs... overArgs) { + return {*this, {std::forward(overArgs)...}}; + } + }; +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * RANK() window function + * https://sqlite.org/windowfunctions.html#built-in_window_functions + */ + inline internal::rank_t rank() { + return {}; + } +} + namespace sqlite_orm::internal { /** * Collated something @@ -6271,6 +6504,14 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { return {std::move(o)}; } + /** + * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. + */ + [[deprecated("Use the hidden FTS5 rank column instead")]] + inline internal::order_by_t order_by(internal::rank_t o) { + return {std::move(o)}; + } + /** * ORDER BY positional ordinal * @@ -6417,212 +6658,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "ast/window.h" -#ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::string -#include // std::tuple -#include // std::forward, std::move -#include // std::forward, std::move -#endif - -// #include "../functional/cxx_type_traits_polyfill.h" - -namespace sqlite_orm::internal { - - struct unbounded_preceding_t {}; - - template - struct preceding_t { - E expression; - }; - - struct current_row_t {}; - - template - struct following_t { - E expression; - }; - - struct unbounded_following_t {}; - - enum class frame_type_t { rows, range, groups }; - enum class frame_exclude_t { no_others, current_row, group, ties }; - - template - struct frame_spec_t { - frame_type_t type; - Start start; - End end; - frame_exclude_t exclude = frame_exclude_t::no_others; - - frame_spec_t exclude_current_row() const { - auto res = *this; - res.exclude = frame_exclude_t::current_row; - return res; - } - - frame_spec_t exclude_group() const { - auto res = *this; - res.exclude = frame_exclude_t::group; - return res; - } - - frame_spec_t exclude_ties() const { - auto res = *this; - res.exclude = frame_exclude_t::ties; - return res; - } - - frame_spec_t exclude_no_others() const { - auto res = *this; - res.exclude = frame_exclude_t::no_others; - return res; - } - }; - - template - struct partition_by_t { - using arguments_type = std::tuple; - arguments_type arguments; - }; - - template - inline constexpr bool is_partition_by_v = polyfill::is_specialization_of_v; - - template - using is_partition_by = polyfill::bool_constant>; - - struct window_ref_t { - std::string name; - }; - - template - struct over_t { - using function_type = F; - using arguments_type = std::tuple; - - function_type function; - arguments_type arguments; - }; - - template - inline constexpr bool is_over_v = polyfill::is_specialization_of_v; - - template - using is_over = polyfill::bool_constant>; - - template - struct window_defn_t { - std::string name; - using arguments_type = std::tuple; - arguments_type arguments; - }; - - template - inline constexpr bool is_window_defn_v = polyfill::is_specialization_of_v; - - template - using is_window_defn = polyfill::bool_constant>; -} - -SQLITE_ORM_EXPORT namespace sqlite_orm { - - /** - * UNBOUNDED PRECEDING frame boundary. - * https://sqlite.org/windowfunctions.html - */ - inline internal::unbounded_preceding_t unbounded_preceding() { - return {}; - } - - /** - * expr PRECEDING frame boundary. - * https://sqlite.org/windowfunctions.html - */ - template - internal::preceding_t preceding(E expression) { - return {std::move(expression)}; - } - - /** - * CURRENT ROW frame boundary. - * https://sqlite.org/windowfunctions.html - */ - inline internal::current_row_t current_row() { - return {}; - } - - /** - * expr FOLLOWING frame boundary. - * https://sqlite.org/windowfunctions.html - */ - template - internal::following_t following(E expression) { - return {std::move(expression)}; - } - - /** - * UNBOUNDED FOLLOWING frame boundary. - * https://sqlite.org/windowfunctions.html - */ - inline internal::unbounded_following_t unbounded_following() { - return {}; - } - - /** - * ROWS BETWEEN start AND end frame specification. - * Example: rows(unbounded_preceding(), current_row()) - */ - template - internal::frame_spec_t rows(Start start, End end) { - return {internal::frame_type_t::rows, std::move(start), std::move(end)}; - } - - /** - * RANGE BETWEEN start AND end frame specification. - * Example: range(current_row(), unbounded_following()) - */ - template - internal::frame_spec_t range(Start start, End end) { - return {internal::frame_type_t::range, std::move(start), std::move(end)}; - } - - /** - * GROUPS BETWEEN start AND end frame specification. - * Example: groups(unbounded_preceding(), current_row()) - */ - template - internal::frame_spec_t groups(Start start, End end) { - return {internal::frame_type_t::groups, std::move(start), std::move(end)}; - } - - /** - * PARTITION BY expression list for window functions. - * Example: partition_by(&Employee::departmentId) - */ - template - internal::partition_by_t partition_by(Args... args) { - return {{std::forward(args)...}}; - } - - /** - * Reference to a named window definition (OVER window_name). - * Example: row_number().over(window_ref("win")) - */ - inline internal::window_ref_t window_ref(std::string name) { - return {std::move(name)}; - } - - /** - * Named window definition (WINDOW name AS (...)). - * Passed as a condition to select(). - * Example: window("win", order_by(&Employee::salary)) - */ - template - internal::window_defn_t window(std::string name, Args... args) { - return {std::move(name), {std::forward(args)...}}; - } -} - // #include "field_of.h" namespace sqlite_orm::internal { @@ -12365,31 +12400,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { // #include "ast/rank.h" -#ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::forward -#endif - -// #include "window.h" - -namespace sqlite_orm::internal { - struct rank_t { - template - over_t over(OverArgs... overArgs) { - return {*this, {std::forward(overArgs)...}}; - } - }; -} - -SQLITE_ORM_EXPORT namespace sqlite_orm { - /** - * RANK() window function / FTS5 rank keyword. - * https://sqlite.org/windowfunctions.html#built-in_window_functions - */ - inline internal::rank_t rank() { - return {}; - } -} - // #include "window_functions.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE From 2b9a462a53f781776479f6e41293d2100987582b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 25 Apr 2026 23:39:12 +0200 Subject: [PATCH 30/49] Sync query views with schema --- dev/mapped_type_proxy.h | 1 - dev/statement_serializer.h | 6 ++-- dev/storage.h | 42 ++++++++++++++++------- dev/storage_base.h | 8 ++--- include/sqlite_orm/sqlite_orm.h | 58 +++++++++++++++++++------------ tests/trigger_tests.cpp | 1 + tests/view_tests.cpp | 61 +++++++++++++++++++++++++++++++++ 7 files changed, 134 insertions(+), 43 deletions(-) diff --git a/dev/mapped_type_proxy.h b/dev/mapped_type_proxy.h index c59ffa4e7..8718fe5e9 100644 --- a/dev/mapped_type_proxy.h +++ b/dev/mapped_type_proxy.h @@ -6,7 +6,6 @@ #include "functional/cxx_type_traits_polyfill.h" #include "type_traits.h" -#include "table_reference.h" #include "alias_traits.h" namespace sqlite_orm::internal { diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index 0b57e5cf8..a9f08c210 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -178,9 +178,9 @@ namespace sqlite_orm::internal { }; #ifdef SQLITE_ORM_WITH_VIEW - template - struct statement_serializer, void> { - using statement_type = query_view; + template + struct statement_serializer>> { + using statement_type = View; template std::string operator()(const statement_type& statement, const Ctx& context) { diff --git a/dev/storage.h b/dev/storage.h index 39af8f490..502044aa1 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -1162,8 +1162,22 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_VIEW template = true> - sync_schema_result schema_status(const View&, sqlite3*, bool, bool*) { - return sync_schema_result::already_in_sync; + sync_schema_result schema_status(const View& queryView, sqlite3* db, bool, bool*) { + auto dbViewSql = this->retrieve_object_sql(db, "view", queryView.name); + if (dbViewSql.empty()) { + return sync_schema_result::new_table_created; + } + + const auto& exprDBOs = db_objects_for_expression(this->db_objects, queryView.select); + + using context_t = serializer_context>; + const context_t context{exprDBOs}; + auto storageSql = serialize(queryView, context); + + if (dbViewSql == storageSql) { + return sync_schema_result::already_in_sync; + } + return sync_schema_result::dropped_and_recreated; } #endif @@ -1297,7 +1311,7 @@ namespace sqlite_orm::internal { auto res = this->schema_status(trigger, db, preserve, nullptr); if (res != sync_schema_result::already_in_sync) { if (res == sync_schema_result::dropped_and_recreated) { - this->drop_trigger_internal(trigger.name, true, db); + this->drop_trigger_internal(db, trigger.name, true); } const serializer_context context{this->db_objects}; const auto sql = serialize(trigger, context); @@ -1308,14 +1322,20 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_VIEW template = true> - sync_schema_result sync_dbo(const View& view, sqlite3* db, bool) { - const auto& exprDBOs = db_objects_for_expression(this->db_objects, view.select); - using context_t = serializer_context>; + sync_schema_result sync_dbo(const View& queryView, sqlite3* db, bool preserve) { + auto res = this->schema_status(queryView, db, preserve, nullptr); + if (res != sync_schema_result::already_in_sync) { + if (res == sync_schema_result::dropped_and_recreated) { + this->drop_view_internal(db, queryView.name, true); + } - const auto res = sync_schema_result::already_in_sync; - const context_t context{exprDBOs}; - const auto sql = serialize(view, context); - this->executor.perform_void_exec(db, sql.c_str()); + const auto& exprDBOs = db_objects_for_expression(this->db_objects, queryView.select); + + using context_t = serializer_context>; + const context_t context{exprDBOs}; + const auto sql = serialize(queryView, context); + this->executor.perform_void_exec(db, sql.c_str()); + } return res; } #endif @@ -1364,7 +1384,6 @@ namespace sqlite_orm::internal { const auto& exprDBOs = db_objects_for_expression(this->db_objects, expression); using context_t = serializer_context>; - context_t context{exprDBOs}; context.replace_bindable_with_question = parametrized; // just like prepare_impl() @@ -1384,7 +1403,6 @@ namespace sqlite_orm::internal { const auto& exprDBOs = db_objects_for_expression(this->db_objects, statement); using context_t = serializer_context>; - context_t context{exprDBOs}; context.omit_table_name = false; context.replace_bindable_with_question = true; diff --git a/dev/storage_base.h b/dev/storage_base.h index 52aaa3951..974becc49 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -1138,10 +1138,10 @@ namespace sqlite_orm::internal { void drop_trigger_internal(const std::string& triggerName, bool ifExists) { auto connection = this->get_connection(); - this->drop_trigger_internal(triggerName, ifExists, connection.get()); + this->drop_trigger_internal(connection.get(), triggerName, ifExists); } - void drop_trigger_internal(const std::string& triggerName, bool ifExists, sqlite3* db) { + void drop_trigger_internal(sqlite3* db, const std::string& triggerName, bool ifExists) { std::stringstream ss; ss << "DROP TRIGGER"; if (ifExists) { @@ -1160,9 +1160,7 @@ namespace sqlite_orm::internal { db, ss.str(), [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*columnName*/) -> int { - if (argv[0]) { - *static_cast(userData) = argv[0]; - } + *static_cast(userData) = argv[0]; return 0; }, &result); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index dc755e67b..36a6d9378 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -11103,8 +11103,6 @@ namespace sqlite_orm::internal { // #include "type_traits.h" -// #include "table_reference.h" - // #include "alias_traits.h" namespace sqlite_orm::internal { @@ -20929,10 +20927,10 @@ namespace sqlite_orm::internal { void drop_trigger_internal(const std::string& triggerName, bool ifExists) { auto connection = this->get_connection(); - this->drop_trigger_internal(triggerName, ifExists, connection.get()); + this->drop_trigger_internal(connection.get(), triggerName, ifExists); } - void drop_trigger_internal(const std::string& triggerName, bool ifExists, sqlite3* db) { + void drop_trigger_internal(sqlite3* db, const std::string& triggerName, bool ifExists) { std::stringstream ss; ss << "DROP TRIGGER"; if (ifExists) { @@ -20951,9 +20949,7 @@ namespace sqlite_orm::internal { db, ss.str(), [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*columnName*/) -> int { - if (argv[0]) { - *static_cast(userData) = argv[0]; - } + *static_cast(userData) = argv[0]; return 0; }, &result); @@ -22253,9 +22249,9 @@ namespace sqlite_orm::internal { }; #ifdef SQLITE_ORM_WITH_VIEW - template - struct statement_serializer, void> { - using statement_type = query_view; + template + struct statement_serializer>> { + using statement_type = View; template std::string operator()(const statement_type& statement, const Ctx& context) { @@ -26608,8 +26604,22 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_VIEW template = true> - sync_schema_result schema_status(const View&, sqlite3*, bool, bool*) { - return sync_schema_result::already_in_sync; + sync_schema_result schema_status(const View& queryView, sqlite3* db, bool, bool*) { + auto dbViewSql = this->retrieve_object_sql(db, "view", queryView.name); + if (dbViewSql.empty()) { + return sync_schema_result::new_table_created; + } + + const auto& exprDBOs = db_objects_for_expression(this->db_objects, queryView.select); + + using context_t = serializer_context>; + const context_t context{exprDBOs}; + auto storageSql = serialize(queryView, context); + + if (dbViewSql == storageSql) { + return sync_schema_result::already_in_sync; + } + return sync_schema_result::dropped_and_recreated; } #endif @@ -26743,7 +26753,7 @@ namespace sqlite_orm::internal { auto res = this->schema_status(trigger, db, preserve, nullptr); if (res != sync_schema_result::already_in_sync) { if (res == sync_schema_result::dropped_and_recreated) { - this->drop_trigger_internal(trigger.name, true, db); + this->drop_trigger_internal(db, trigger.name, true); } const serializer_context context{this->db_objects}; const auto sql = serialize(trigger, context); @@ -26754,14 +26764,20 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_VIEW template = true> - sync_schema_result sync_dbo(const View& view, sqlite3* db, bool) { - const auto& exprDBOs = db_objects_for_expression(this->db_objects, view.select); - using context_t = serializer_context>; + sync_schema_result sync_dbo(const View& queryView, sqlite3* db, bool preserve) { + auto res = this->schema_status(queryView, db, preserve, nullptr); + if (res != sync_schema_result::already_in_sync) { + if (res == sync_schema_result::dropped_and_recreated) { + this->drop_view_internal(db, queryView.name, true); + } - const auto res = sync_schema_result::already_in_sync; - const context_t context{exprDBOs}; - const auto sql = serialize(view, context); - this->executor.perform_void_exec(db, sql.c_str()); + const auto& exprDBOs = db_objects_for_expression(this->db_objects, queryView.select); + + using context_t = serializer_context>; + const context_t context{exprDBOs}; + const auto sql = serialize(queryView, context); + this->executor.perform_void_exec(db, sql.c_str()); + } return res; } #endif @@ -26810,7 +26826,6 @@ namespace sqlite_orm::internal { const auto& exprDBOs = db_objects_for_expression(this->db_objects, expression); using context_t = serializer_context>; - context_t context{exprDBOs}; context.replace_bindable_with_question = parametrized; // just like prepare_impl() @@ -26830,7 +26845,6 @@ namespace sqlite_orm::internal { const auto& exprDBOs = db_objects_for_expression(this->db_objects, statement); using context_t = serializer_context>; - context_t context{exprDBOs}; context.omit_table_name = false; context.replace_bindable_with_question = true; diff --git a/tests/trigger_tests.cpp b/tests/trigger_tests.cpp index 8c556b13d..c05da0834 100644 --- a/tests/trigger_tests.cpp +++ b/tests/trigger_tests.cpp @@ -1,5 +1,6 @@ #include #include +#include // std::remove using namespace sqlite_orm; diff --git a/tests/view_tests.cpp b/tests/view_tests.cpp index b5a181059..8efbc9ad5 100644 --- a/tests/view_tests.cpp +++ b/tests/view_tests.cpp @@ -1,5 +1,6 @@ #include #include +#include // std::remove #ifdef SQLITE_ORM_WITH_VIEW using namespace sqlite_orm; @@ -17,6 +18,18 @@ struct UserViewTests { #endif }; +struct UserView2Tests { + std::string name; + +#ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED + bool operator==(const UserView2Tests&) const = default; +#else + bool operator==(const UserView2Tests& right) const { + return name == right.name; + } +#endif +}; + TEST_CASE("sql view") { using Catch::Matchers::UnorderedEquals; @@ -76,4 +89,52 @@ TEST_CASE("sql view") { #endif } } + +TEST_CASE("sync sql view") { + struct User { + int id = 0; + std::string name; + }; + + auto storagePath = "sync_sql_view.sqlite"; + std::remove(storagePath); + + // first: create storage with trigger checking "name" column + { + auto storage = make_storage( + storagePath, + make_table("user", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), + make_view("user_view", select(&User::name))); + auto syncResult = storage.sync_schema(); + REQUIRE(syncResult.at("user_view") == sync_schema_result::new_table_created); + + // second sync should report already_in_sync + syncResult = storage.sync_schema(); + REQUIRE(syncResult.at("user_view") == sync_schema_result::already_in_sync); + } + // second: create storage with a different view on User instead + { + auto storage = make_storage( + storagePath, + make_table("user", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), + make_view("user_view", select(asterisk()))); + + // simulate should detect the change + auto simulateResult = storage.sync_schema_simulate(); + REQUIRE(simulateResult.at("user_view") == sync_schema_result::dropped_and_recreated); + + // sync should update the view + auto syncResult = storage.sync_schema(); + REQUIRE(syncResult.at("user_view") == sync_schema_result::dropped_and_recreated); + + // verify view was updated + REQUIRE_NOTHROW(storage.iterate()); + + // after update, second sync should be already_in_sync + syncResult = storage.sync_schema(); + REQUIRE(syncResult.at("user_view") == sync_schema_result::already_in_sync); + } + + std::remove(storagePath); +} #endif From 98bdf43ee98e5c9b52bf00e42beffe6c4ab0f907 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 26 Apr 2026 09:20:33 +0200 Subject: [PATCH 31/49] Corrected compile options target for examples --- examples/CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index cc9e0955e..f8b9dd985 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -15,6 +15,11 @@ foreach(file ${files}) # note: sqlite3 already linked in top-level CMakeLists target_link_libraries(${file_basename} PRIVATE sqlite_orm) + + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 16) + target_compile_options(${file_basename} PUBLIC + -freflection) + endif() add_custom_target(run_${file_basename} COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${file_basename} @@ -23,11 +28,6 @@ foreach(file ${files}) COMMENT "Running example: ${file_basename}" VERBATIM ) - - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 16) - target_compile_options(unit_tests PUBLIC - -freflection) - endif() list(APPEND run_example_targets run_${file_basename}) endforeach() From 30cbcabcb3465a867da4181bf2e4baf7a502e43b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 26 Apr 2026 10:01:55 +0200 Subject: [PATCH 32/49] Updated .clang-format-ignore with paths that the github linter action sees --- .clang-format-ignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format-ignore b/.clang-format-ignore index e37bf2876..9468717b1 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -1,2 +1,2 @@ # exclude until clang-format understands C++ reflection syntax -dev/functional/meta_util.h +./dev/functional/meta_util.h From 663fa8b8b7f4233417317f3770aadebdade8c7bb Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 26 Apr 2026 20:31:31 +0200 Subject: [PATCH 33/49] Corrected superfluous space before comments --- dev/cte_storage.h | 48 +- dev/object_from_column_builder.h | 12 +- dev/statement_serializer.h | 18 +- dev/storage.h | 568 +++++++-------- dev/storage_base.h | 418 ++++++------ dev/storage_impl.h | 36 +- include/sqlite_orm/sqlite_orm.h | 1100 +++++++++++++++--------------- tests/tests.cpp | 8 + 8 files changed, 1108 insertions(+), 1100 deletions(-) diff --git a/dev/cte_storage.h b/dev/cte_storage.h index dea7e1f7e..090e244e9 100644 --- a/dev/cte_storage.h +++ b/dev/cte_storage.h @@ -89,8 +89,8 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED /** - * Concatenate newly created tables with given DBOs, forming a new set of DBOs. - */ + * Concatenate newly created tables with given DBOs, forming a new set of DBOs. + */ template auto db_objects_cat(const DBOs& dbObjects, CTETables&&... cteTables) { auto& [... elements] = dbObjects; @@ -98,16 +98,16 @@ namespace sqlite_orm::internal { } #else /** - * Concatenate newly created tables with given DBOs, forming a new set of DBOs. - */ + * Concatenate newly created tables with given DBOs, forming a new set of DBOs. + */ template auto db_objects_cat(const DBOs& dbObjects, std::index_sequence, CTETables&&... cteTables) { return std::tuple{std::forward(cteTables)..., std::get(dbObjects)...}; } /** - * Concatenate newly created tables with given DBOs, forming a new set of DBOs. - */ + * Concatenate newly created tables with given DBOs, forming a new set of DBOs. + */ template auto db_objects_cat(const DBOs& dbObjects, CTETables&&... cteTables) { return db_objects_cat(dbObjects, @@ -117,35 +117,35 @@ namespace sqlite_orm::internal { #endif /** - * This function returns the expression contained in a subselect that is relevant for - * creating the definition of a CTE table. - * Because CTEs can recursively refer to themselves in a compound statement, parsing - * the whole compound statement would lead to compiler errors if a column_pointer<> - * can't be resolved. Therefore, at the time of building a CTE table, we are only - * interested in the column results of the left-most select expression. - */ + * This function returns the expression contained in a subselect that is relevant for + * creating the definition of a CTE table. + * Because CTEs can recursively refer to themselves in a compound statement, parsing + * the whole compound statement would lead to compiler errors if a column_pointer<> + * can't be resolved. Therefore, at the time of building a CTE table, we are only + * interested in the column results of the left-most select expression. + */ template decltype(auto) get_cte_driving_subselect(const Select& subSelect); /** - * Return given select expression. - */ + * Return given select expression. + */ template decltype(auto) get_cte_driving_subselect(const Select& subSelect) { return subSelect; } /** - * Return left-most select expression of compound statement. - */ + * Return left-most select expression of compound statement. + */ template, bool> = true> decltype(auto) get_cte_driving_subselect(const select_t& subSelect) { return std::get<0>(subSelect.col.compound); } /** - * Return a tuple of member pointers of all columns - */ + * Return a tuple of member pointers of all columns + */ template auto get_table_columns_fields(const C& coldef, std::index_sequence) { return std::make_tuple(get(coldef).member_pointer...); @@ -229,9 +229,9 @@ namespace sqlite_orm::internal { void extract_colref_expressions(const DBOs& /*dbObjects*/, const Compound& /*subSelect*/) = delete; /* - * Depending on ExplicitColRef's type returns either the explicit column reference - * or the expression's column reference otherwise. - */ + * Depending on ExplicitColRef's type returns either the explicit column reference + * or the expression's column reference otherwise. + */ template auto determine_cte_colref(const DBOs& /*dbObjects*/, const SubselectColRef& subselectColRef, @@ -333,8 +333,8 @@ namespace sqlite_orm::internal { } /** - * Return new DBOs for CTE expressions. - */ + * Return new DBOs for CTE expressions. + */ template = true> decltype(auto) db_objects_for_expression(DBOs& dbObjects, const with_t& e) { return make_recursive_cte_db_objects(dbObjects, e.cte, std::index_sequence_for{}); diff --git a/dev/object_from_column_builder.h b/dev/object_from_column_builder.h index 300eab0fc..c2bcd665d 100644 --- a/dev/object_from_column_builder.h +++ b/dev/object_from_column_builder.h @@ -24,8 +24,8 @@ namespace sqlite_orm::internal { }; /** - * Function object for building an object from a result row. - */ + * Function object for building an object from a result row. + */ template struct object_from_column_builder : object_from_column_builder_base { using object_type = O; @@ -62,10 +62,10 @@ namespace sqlite_orm::internal { }; /** - * Specialization for a table reference. - * - * This plays together with `column_result_of_t`, which returns `object_t` as `table_reference` - */ + * Specialization for a table reference. + * + * This plays together with `column_result_of_t`, which returns `object_t` as `table_reference` + */ template struct struct_extractor, DBOs> { const DBOs& db_objects; diff --git a/dev/statement_serializer.h b/dev/statement_serializer.h index a9f08c210..2fa66bb01 100644 --- a/dev/statement_serializer.h +++ b/dev/statement_serializer.h @@ -77,8 +77,8 @@ namespace sqlite_orm::internal { } /** - * Serializer for bindable types. - */ + * Serializer for bindable types. + */ template struct statement_serializer> { using statement_type = T; @@ -139,8 +139,8 @@ namespace sqlite_orm::internal { #endif #endif /** - * Specialization for binary data (std::vector). - */ + * Specialization for binary data (std::vector). + */ static std::string do_serialize(const std::vector& t) { return quote_blob_literal(field_printer>{}(t)); } @@ -293,8 +293,8 @@ namespace sqlite_orm::internal { }; /** - * Serializer for literal values. - */ + * Serializer for literal values. + */ template struct statement_serializer> { using statement_type = T; @@ -2763,9 +2763,9 @@ namespace sqlite_orm::internal { }; /** - * HO - has offset - * OI - offset is implicit - */ + * HO - has offset + * OI - offset is implicit + */ template struct statement_serializer, void> { using statement_type = limit_t; diff --git a/dev/storage.h b/dev/storage.h index 502044aa1..ccf2d6787 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -70,10 +70,10 @@ namespace sqlite_orm::internal { /* - * Implementation note: the technique of indirect expression testing is because - * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. - * It must also be a type that differs from those for `is_printable_v`, `is_bindable_v`. - */ + * Implementation note: the technique of indirect expression testing is because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + * It must also be a type that differs from those for `is_printable_v`, `is_bindable_v`. + */ template struct indirectly_test_preparable; @@ -95,18 +95,18 @@ namespace sqlite_orm::internal { } /** - * Storage class itself. Create an instance to use it as an interfacto to sqlite db by calling `make_storage` - * function. - */ + * Storage class itself. Create an instance to use it as an interfacto to sqlite db by calling `make_storage` + * function. + */ template struct storage_t : storage_base { using self_type = storage_t; using db_objects_type = db_objects_tuple; /** - * @param filename database filename. - * @param dbObjects db_objects_tuple - */ + * @param filename database filename. + * @param dbObjects db_objects_tuple + */ template storage_t(std::string filename, db_objects_type dbObjects, OptionsTpl options) : storage_base{std::move(filename), @@ -127,17 +127,17 @@ namespace sqlite_orm::internal { db_objects_type db_objects; /** - * Obtain a storage_t's const db_objects_tuple. - * - * @note Historically, `serializer_context_builder` was declared friend, along with - * a few other library stock objects, in order to limit access to the db_objects_tuple. - * However, one could gain access to a storage_t's db_objects_tuple through - * `serializer_context_builder`, hence leading the whole friend declaration mambo-jumbo - * ad absurdum. - * Providing a free function is way better and cleaner. - * - * Hence, friend was replaced by `obtain_db_objects()` and `pick_const_impl()`. - */ + * Obtain a storage_t's const db_objects_tuple. + * + * @note Historically, `serializer_context_builder` was declared friend, along with + * a few other library stock objects, in order to limit access to the db_objects_tuple. + * However, one could gain access to a storage_t's db_objects_tuple through + * `serializer_context_builder`, hence leading the whole friend declaration mambo-jumbo + * ad absurdum. + * Providing a free function is way better and cleaner. + * + * Hence, friend was replaced by `obtain_db_objects()` and `pick_const_impl()`. + */ friend const db_objects_type& obtain_db_objects(const self_type& storage) noexcept { return storage.db_objects; } @@ -172,9 +172,9 @@ namespace sqlite_orm::internal { } /** - * Copies sourceTableName to another table with name: destinationTableName - * Performs INSERT INTO %destinationTableName% () SELECT %table.column_names% FROM %sourceTableName% - */ + * Copies sourceTableName to another table with name: destinationTableName + * Performs INSERT INTO %destinationTableName% () SELECT %table.column_names% FROM %sourceTableName% + */ template void copy_table(sqlite3* db, const std::string& sourceTableName, @@ -299,11 +299,11 @@ namespace sqlite_orm::internal { public: /* - * Iterate over objects of a type mapped as a table, lazily fetched from a result set. - * - * The returned C++ view models a C++ input range and is also a 'borrowed range', - * meaning that iterators obtained from it are not tied to the lifetime of the view instance. - */ + * Iterate over objects of a type mapped as a table, lazily fetched from a result set. + * + * The returned C++ view models a C++ input range and is also a 'borrowed range', + * meaning that iterators obtained from it are not tied to the lifetime of the view instance. + */ template, class... Args> mapped_view iterate(Args&&... args) { this->assert_mapped_type(); @@ -314,11 +314,11 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /* - * Iterate over objects of a type mapped as a table, lazily fetched from a result set. - * - * The returned C++ view models a C++ input range and is also a 'borrowed range', - * meaning that iterators obtained from it are not tied to the lifetime of the view instance. - */ + * Iterate over objects of a type mapped as a table, lazily fetched from a result set. + * + * The returned C++ view models a C++ input range and is also a 'borrowed range', + * meaning that iterators obtained from it are not tied to the lifetime of the view instance. + */ template auto iterate(Args&&... args) { return this->iterate(std::forward(args)...); @@ -326,11 +326,11 @@ namespace sqlite_orm::internal { #endif /* - * Iterate over a result set of a select statement or a select statement involving a common table expression. - * - * The returned C++ view models a C++ input range and is also a 'borrowed range', - * meaning that iterators obtained from it are not tied to the lifetime of the view instance. - */ + * Iterate over a result set of a select statement or a select statement involving a common table expression. + * + * The returned C++ view models a C++ input range and is also a 'borrowed range', + * meaning that iterators obtained from it are not tied to the lifetime of the view instance. + */ template #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED requires (is_select_expression_v) auto yield(Select expression) -> std::generatoriterate(std::move(expression)).begin())> { @@ -390,12 +390,12 @@ namespace sqlite_orm::internal { #endif /** - * Delete from routine. - * O is an object's type. Must be specified explicitly. - * @param args optional conditions: `where`, `join` etc - * @example: storage.remove_all(); - DELETE FROM users - * @example: storage.remove_all(where(in(&User::id, {5, 6, 7}))); - DELETE FROM users WHERE id IN (5, 6, 7) - */ + * Delete from routine. + * O is an object's type. Must be specified explicitly. + * @param args optional conditions: `where`, `join` etc + * @example: storage.remove_all(); - DELETE FROM users + * @example: storage.remove_all(where(in(&User::id, {5, 6, 7}))); - DELETE FROM users WHERE id IN (5, 6, 7) + */ template void remove_all(Args&&... args) { this->assert_mapped_type(); @@ -411,10 +411,10 @@ namespace sqlite_orm::internal { #endif /** - * Delete routine. - * O is an object's type. Must be specified explicitly. - * @param ids ids of object to be removed. - */ + * Delete routine. + * O is an object's type. Must be specified explicitly. + * @param ids ids of object to be removed. + */ template void remove(Ids... ids) { this->assert_mapped_type(); @@ -430,11 +430,11 @@ namespace sqlite_orm::internal { #endif /** - * Update routine. Sets all non primary key fields where primary key is equal. - * O is an object type. May be not specified explicitly cause it can be deduced by - * compiler from first parameter. - * @param o object to be updated. - */ + * Update routine. Sets all non primary key fields where primary key is equal. + * O is an object type. May be not specified explicitly cause it can be deduced by + * compiler from first parameter. + * @param o object to be updated. + */ template void update(const O& o) { this->assert_mapped_type(); @@ -468,13 +468,13 @@ namespace sqlite_orm::internal { public: /** - * SELECT * routine. - * T is an explicitly specified object mapped to a storage or a table alias. - * R is an explicit return type. This type must have `push_back(O &&)` function. Defaults to `std::vector` - * @return All objects of type O stored in database at the moment in `R`. - * @example: storage.get_all>(); - SELECT * FROM users - * @example: storage.get_all>(where(like(&User::name, "N%")), order_by(&User::id)); - SELECT * FROM users WHERE name LIKE 'N%' ORDER BY id - */ + * SELECT * routine. + * T is an explicitly specified object mapped to a storage or a table alias. + * R is an explicit return type. This type must have `push_back(O &&)` function. Defaults to `std::vector` + * @return All objects of type O stored in database at the moment in `R`. + * @example: storage.get_all>(); - SELECT * FROM users + * @example: storage.get_all>(where(like(&User::name, "N%")), order_by(&User::id)); - SELECT * FROM users WHERE name LIKE 'N%' ORDER BY id + */ template>, class... Args> R get_all(Args&&... args) { this->assert_mapped_type>(); @@ -484,12 +484,12 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * SELECT * routine. - * `mapped` is an explicitly specified table reference or table alias of an object to be extracted. - * `R` is the container return type, which must have a `R::push_back(O&&)` method, and defaults to `std::vector` - * @return All objects stored in database. - * @example: storage.get_all>(); - SELECT sqlite_schema.* FROM sqlite_master AS sqlite_schema - */ + * SELECT * routine. + * `mapped` is an explicitly specified table reference or table alias of an object to be extracted. + * `R` is the container return type, which must have a `R::push_back(O&&)` method, and defaults to `std::vector` + * @return All objects stored in database. + * @example: storage.get_all>(); - SELECT sqlite_schema.* FROM sqlite_master AS sqlite_schema + */ template>, class... Args> @@ -501,13 +501,13 @@ namespace sqlite_orm::internal { #endif /** - * SELECT * routine. - * O is an object type to be extracted. Must be specified explicitly. - * R is a container type. std::vector> is default - * @return All objects of type O as std::unique_ptr stored in database at the moment. - * @example: storage.get_all_pointer>>(); - SELECT * FROM users - * @example: storage.get_all_pointer>>(where(length(&User::name) > 6)); - SELECT * FROM users WHERE LENGTH(name) > 6 - */ + * SELECT * routine. + * O is an object type to be extracted. Must be specified explicitly. + * R is a container type. std::vector> is default + * @return All objects of type O as std::unique_ptr stored in database at the moment. + * @example: storage.get_all_pointer>>(); - SELECT * FROM users + * @example: storage.get_all_pointer>>(where(length(&User::name) > 6)); - SELECT * FROM users WHERE LENGTH(name) > 6 + */ template>, class... Args> auto get_all_pointer(Args&&... args) { this->assert_mapped_type(); @@ -526,13 +526,13 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED /** - * SELECT * routine. - * O is an object type to be extracted. Must be specified explicitly. - * R is a container type. std::vector> is default - * @return All objects of type O as std::optional stored in database at the moment. - * @example: storage.get_all_optional>>(); - SELECT * FROM users - * @example: storage.get_all_optional>>(where(length(&User::name) > 6)); - SELECT * FROM users WHERE LENGTH(name) > 6 - */ + * SELECT * routine. + * O is an object type to be extracted. Must be specified explicitly. + * R is a container type. std::vector> is default + * @return All objects of type O as std::optional stored in database at the moment. + * @example: storage.get_all_optional>>(); - SELECT * FROM users + * @example: storage.get_all_optional>>(where(length(&User::name) > 6)); - SELECT * FROM users WHERE LENGTH(name) > 6 + */ template>, class... Args> auto get_all_optional(Args&&... conditions) { this->assert_mapped_type(); @@ -551,13 +551,13 @@ namespace sqlite_orm::internal { #endif /** - * Select * by id routine. - * throws std::system_error{orm_error_code::not_found} if object not found with given - * id. throws std::system_error with orm_error_category in case of db error. O is an object type to be - * extracted. Must be specified explicitly. - * @return Object of type O where id is equal parameter passed or throws - * `std::system_error{orm_error_code::not_found}` if there is no object with such id. - */ + * Select * by id routine. + * throws std::system_error{orm_error_code::not_found} if object not found with given + * id. throws std::system_error with orm_error_category in case of db error. O is an object type to be + * extracted. Must be specified explicitly. + * @return Object of type O where id is equal parameter passed or throws + * `std::system_error{orm_error_code::not_found}` if there is no object with such id. + */ template O get(Ids... ids) { this->assert_mapped_type(); @@ -574,9 +574,9 @@ namespace sqlite_orm::internal { #endif /** - * The same as `get` function but doesn't throw an exception if noting found but returns std::unique_ptr - * with null value. throws std::system_error in case of db error. - */ + * The same as `get` function but doesn't throw an exception if noting found but returns std::unique_ptr + * with null value. throws std::system_error in case of db error. + */ template std::unique_ptr get_pointer(Ids... ids) { this->assert_mapped_type(); @@ -593,18 +593,18 @@ namespace sqlite_orm::internal { #endif /** - * A previous version of get_pointer() that returns a shared_ptr - * instead of a unique_ptr. New code should prefer get_pointer() - * unless the data needs to be shared. - * - * @note - * Most scenarios don't need shared ownership of data, so we should prefer - * unique_ptr when possible. It's more efficient, doesn't require atomic - * ops for a reference count (which can cause major slowdowns on - * weakly-ordered platforms like ARM), and can be easily promoted to a - * shared_ptr, exactly like we're doing here. - * (Conversely, you _can't_ go from shared back to unique.) - */ + * A previous version of get_pointer() that returns a shared_ptr + * instead of a unique_ptr. New code should prefer get_pointer() + * unless the data needs to be shared. + * + * @note + * Most scenarios don't need shared ownership of data, so we should prefer + * unique_ptr when possible. It's more efficient, doesn't require atomic + * ops for a reference count (which can cause major slowdowns on + * weakly-ordered platforms like ARM), and can be easily promoted to a + * shared_ptr, exactly like we're doing here. + * (Conversely, you _can't_ go from shared back to unique.) + */ template std::shared_ptr get_no_throw(Ids... ids) { return std::shared_ptr(this->get_pointer(std::forward(ids)...)); @@ -612,9 +612,9 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED /** - * The same as `get` function but doesn't throw an exception if noting found but - * returns an empty std::optional. throws std::system_error in case of db error. - */ + * The same as `get` function but doesn't throw an exception if noting found but + * returns an empty std::optional. throws std::system_error in case of db error. + */ template std::optional get_optional(Ids... ids) { this->assert_mapped_type(); @@ -632,9 +632,9 @@ namespace sqlite_orm::internal { #endif /** - * SELECT COUNT(*) https://www.sqlite.org/lang_aggfunc.html#count - * @return Number of O object in table. - */ + * SELECT COUNT(*) https://www.sqlite.org/lang_aggfunc.html#count + * @return Number of O object in table. + */ template int count(Args&&... args) { using R = mapped_type_proxy_t; @@ -655,10 +655,10 @@ namespace sqlite_orm::internal { #endif /** - * SELECT COUNT(X) https://www.sqlite.org/lang_aggfunc.html#count - * @param m member pointer to class mapped to the storage. - * @return count of `m` values from database. - */ + * SELECT COUNT(X) https://www.sqlite.org/lang_aggfunc.html#count + * @param m member pointer to class mapped to the storage. + * @return count of `m` values from database. + */ template, is_column_pointer>::value, bool> = @@ -674,10 +674,10 @@ namespace sqlite_orm::internal { } /** - * AVG(X) query. https://www.sqlite.org/lang_aggfunc.html#avg - * @param m is a class member pointer (the same you passed into make_column). - * @return average value from database. - */ + * AVG(X) query. https://www.sqlite.org/lang_aggfunc.html#avg + * @param m is a class member pointer (the same you passed into make_column). + * @return average value from database. + */ template, is_column_pointer>::value, bool> = @@ -700,10 +700,10 @@ namespace sqlite_orm::internal { } /** - * GROUP_CONCAT(X) query. https://www.sqlite.org/lang_aggfunc.html#groupconcat - * @param m is a class member pointer (the same you passed into make_column). - * @return group_concat query result. - */ + * GROUP_CONCAT(X) query. https://www.sqlite.org/lang_aggfunc.html#groupconcat + * @param m is a class member pointer (the same you passed into make_column). + * @return group_concat query result. + */ template, @@ -715,10 +715,10 @@ namespace sqlite_orm::internal { } /** - * GROUP_CONCAT(X, Y) query. https://www.sqlite.org/lang_aggfunc.html#groupconcat - * @param m is a class member pointer (the same you passed into make_column). - * @return group_concat query result. - */ + * GROUP_CONCAT(X, Y) query. https://www.sqlite.org/lang_aggfunc.html#groupconcat + * @param m is a class member pointer (the same you passed into make_column). + * @return group_concat query result. + */ template, is_column_pointer>::value, bool> = @@ -744,10 +744,10 @@ namespace sqlite_orm::internal { } /** - * MAX(x) query. - * @param m is a class member pointer (the same you passed into make_column). - * @return std::unique_ptr with max value or null if sqlite engine returned null. - */ + * MAX(x) query. + * @param m is a class member pointer (the same you passed into make_column). + * @return std::unique_ptr with max value or null if sqlite engine returned null. + */ template, @@ -764,10 +764,10 @@ namespace sqlite_orm::internal { } /** - * MIN(x) query. - * @param m is a class member pointer (the same you passed into make_column). - * @return std::unique_ptr with min value or null if sqlite engine returned null. - */ + * MIN(x) query. + * @param m is a class member pointer (the same you passed into make_column). + * @return std::unique_ptr with min value or null if sqlite engine returned null. + */ template, @@ -784,10 +784,10 @@ namespace sqlite_orm::internal { } /** - * SUM(x) query. - * @param m is a class member pointer (the same you passed into make_column). - * @return std::unique_ptr with sum value or null if sqlite engine returned null. - */ + * SUM(x) query. + * @param m is a class member pointer (the same you passed into make_column). + * @return std::unique_ptr with sum value or null if sqlite engine returned null. + */ template, @@ -809,11 +809,11 @@ namespace sqlite_orm::internal { } /** - * TOTAL(x) query. - * @param m is a class member pointer (the same you passed into make_column). - * @return total value (the same as SUM but not nullable. More details here - * https://www.sqlite.org/lang_aggfunc.html) - */ + * TOTAL(x) query. + * @param m is a class member pointer (the same you passed into make_column). + * @return total value (the same as SUM but not nullable. More details here + * https://www.sqlite.org/lang_aggfunc.html) + */ template, is_column_pointer>::value, bool> = @@ -829,10 +829,10 @@ namespace sqlite_orm::internal { } /** - * Select a single column into std::vector or multiple columns into std::vector>. - * For a single column use `auto rows = storage.select(&User::id, where(...)); - * For multicolumns use `auto rows = storage.select(columns(&User::id, &User::name), where(...)); - */ + * Select a single column into std::vector or multiple columns into std::vector>. + * For a single column use `auto rows = storage.select(&User::id, where(...)); + * For multicolumns use `auto rows = storage.select(columns(&User::id, &User::name), where(...)); + */ template auto select(T m, Args... args) { static_assert(!is_compound_operator_v || sizeof...(Args) == 0, @@ -843,8 +843,8 @@ namespace sqlite_orm::internal { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) /** - * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. - */ + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ template auto with(CTE cte, E expression) { auto statement = this->prepare(sqlite_orm::with(std::move(cte), std::move(expression))); @@ -852,8 +852,8 @@ namespace sqlite_orm::internal { } /** - * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. - */ + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ template auto with(common_table_expressions cte, E expression) { auto statement = this->prepare(sqlite_orm::with(std::move(cte), std::move(expression))); @@ -861,8 +861,8 @@ namespace sqlite_orm::internal { } /** - * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. - */ + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ template auto with_recursive(CTE cte, E expression) { auto statement = this->prepare(sqlite_orm::with_recursive(std::move(cte), std::move(expression))); @@ -870,8 +870,8 @@ namespace sqlite_orm::internal { } /** - * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. - */ + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ template auto with_recursive(common_table_expressions cte, E expression) { auto statement = this->prepare(sqlite_orm::with_recursive(std::move(cte), std::move(expression))); @@ -901,9 +901,9 @@ namespace sqlite_orm::internal { } /** - * Returns a string representation of object of a class mapped to the storage. - * Type of string has json-like style. - */ + * Returns a string representation of object of a class mapped to the storage. + * Type of string has json-like style. + */ template = true> std::string dump(const O& object) const { auto& table = this->get_table(); @@ -921,11 +921,11 @@ namespace sqlite_orm::internal { } /** - * This is REPLACE (INSERT OR REPLACE) function. - * Also if you need to insert value with knows id you should - * also you this function instead of insert cause inserts ignores - * id and creates own one. - */ + * This is REPLACE (INSERT OR REPLACE) function. + * Also if you need to insert value with knows id you should + * also you this function instead of insert cause inserts ignores + * id and creates own one. + */ template void replace(const O& o) { this->assert_mapped_type(); @@ -959,13 +959,13 @@ namespace sqlite_orm::internal { } /** - * Insert routine with explicitly specified columns. - * - * @return The ID of the last inserted record for a rowid table, otherwise a meaningless value. - * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. - * Attention: While SQLite returns a 64-bit integer as rowid, this function returns an `int` that most likely has less precision. - * If you need the full 64-bit rowid value, use `storage_t<>::execute()` instead, or call `storage_t<>::last_insert_rowid()` after inserting. - */ + * Insert routine with explicitly specified columns. + * + * @return The ID of the last inserted record for a rowid table, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. + * Attention: While SQLite returns a 64-bit integer as rowid, this function returns an `int` that most likely has less precision. + * If you need the full 64-bit rowid value, use `storage_t<>::execute()` instead, or call `storage_t<>::last_insert_rowid()` after inserting. + */ template int insert(const O& o, columns_t cols) { static_assert(cols.count > 0, "Use insert or replace with 1 argument instead"); @@ -975,22 +975,22 @@ namespace sqlite_orm::internal { } /** - * Ordinary insert routine. - * - * - For objects mapped to a rowid table with a single primary key: - * Inserts a record with all fields of a mapped object except the primary key column. - * The primary key column must be implicitly insertable. - * The 'ID' of the specified object is irrelevant as it is implicitly inserted. - * - For objects mapped to a rowid table with a composite primary key or no primary key: - * Inserts a record with all fields of a mapped object except primary key columns having a default value. - * - For objects mapped to a table without rowid: - * Inserts a record with all fields of a mapped object except primary key columns having a default value. - * - * @return The ID of the last inserted record for a rowid table, otherwise a meaningless value. - * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. - * Attention: While SQLite returns a 64-bit integer as rowid, this function returns an `int` that most likely has less precision. - * If you need the full 64-bit rowid value, use `storage_t<>::execute()` instead, or call `storage_t<>::last_insert_rowid()` after inserting. - */ + * Ordinary insert routine. + * + * - For objects mapped to a rowid table with a single primary key: + * Inserts a record with all fields of a mapped object except the primary key column. + * The primary key column must be implicitly insertable. + * The 'ID' of the specified object is irrelevant as it is implicitly inserted. + * - For objects mapped to a rowid table with a composite primary key or no primary key: + * Inserts a record with all fields of a mapped object except primary key columns having a default value. + * - For objects mapped to a table without rowid: + * Inserts a record with all fields of a mapped object except primary key columns having a default value. + * + * @return The ID of the last inserted record for a rowid table, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. + * Attention: While SQLite returns a 64-bit integer as rowid, this function returns an `int` that most likely has less precision. + * If you need the full 64-bit rowid value, use `storage_t<>::execute()` instead, or call `storage_t<>::last_insert_rowid()` after inserting. + */ template int insert(const O& o) { this->assert_mapped_type(); @@ -1000,39 +1000,39 @@ namespace sqlite_orm::internal { } /** - * Raw insert routine. Use this if `insert` with object does not fit you. This insert is designed to be able - * to call any type of `INSERT` query with no limitations. - * @example - * ```sql - * INSERT INTO users (id, name) VALUES(5, 'Little Mix') - * ``` - * will be - * ```c++ - * storage.insert(into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))); - * ``` - * One more example: - * ```sql - * INSERT INTO singers (name) VALUES ('Sofia Reyes')('Kungs') - * ``` - * will be - * ```c++ - * storage.insert(into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))); - * ``` - * One can use `default_values` to add `DEFAULT VALUES` modifier: - * ```sql - * INSERT INTO users DEFAULT VALUES - * ``` - * will be - * ```c++ - * storage.insert(into(), default_values()); - * ``` - * Also one can use `INSERT OR ABORT`/`INSERT OR FAIL`/`INSERT OR IGNORE`/`INSERT OR REPLACE`/`INSERT ROLLBACK`: - * ```c++ - * storage.insert(or_ignore(), into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))); - * storage.insert(or_rollback(), into(), default_values()); - * storage.insert(or_abort(), into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))); - * ``` - */ + * Raw insert routine. Use this if `insert` with object does not fit you. This insert is designed to be able + * to call any type of `INSERT` query with no limitations. + * @example + * ```sql + * INSERT INTO users (id, name) VALUES(5, 'Little Mix') + * ``` + * will be + * ```c++ + * storage.insert(into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))); + * ``` + * One more example: + * ```sql + * INSERT INTO singers (name) VALUES ('Sofia Reyes')('Kungs') + * ``` + * will be + * ```c++ + * storage.insert(into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))); + * ``` + * One can use `default_values` to add `DEFAULT VALUES` modifier: + * ```sql + * INSERT INTO users DEFAULT VALUES + * ``` + * will be + * ```c++ + * storage.insert(into(), default_values()); + * ``` + * Also one can use `INSERT OR ABORT`/`INSERT OR FAIL`/`INSERT OR IGNORE`/`INSERT OR REPLACE`/`INSERT ROLLBACK`: + * ```c++ + * storage.insert(or_ignore(), into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))); + * storage.insert(or_rollback(), into(), default_values()); + * storage.insert(or_abort(), into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))); + * ``` + */ template void insert(Args... args) { auto statement = this->prepare(sqlite_orm::insert(std::forward(args)...)); @@ -1040,33 +1040,33 @@ namespace sqlite_orm::internal { } /** - * Raw replace statement creation routine. Use this if `replace` with object does not fit you. This replace is designed to be able - * to call any type of `REPLACE` query with no limitations. Actually this is the same query as raw insert except `OR...` option existance. - * @example - * ```sql - * REPLACE INTO users (id, name) VALUES(5, 'Little Mix') - * ``` - * will be - * ```c++ - * storage.prepare(replace(into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix")))); - * ``` - * One more example: - * ```sql - * REPLACE INTO singers (name) VALUES ('Sofia Reyes')('Kungs') - * ``` - * will be - * ```c++ - * storage.prepare(replace(into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs")))); - * ``` - * One can use `default_values` to add `DEFAULT VALUES` modifier: - * ```sql - * REPLACE INTO users DEFAULT VALUES - * ``` - * will be - * ```c++ - * storage.prepare(replace(into(), default_values())); - * ``` - */ + * Raw replace statement creation routine. Use this if `replace` with object does not fit you. This replace is designed to be able + * to call any type of `REPLACE` query with no limitations. Actually this is the same query as raw insert except `OR...` option existance. + * @example + * ```sql + * REPLACE INTO users (id, name) VALUES(5, 'Little Mix') + * ``` + * will be + * ```c++ + * storage.prepare(replace(into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix")))); + * ``` + * One more example: + * ```sql + * REPLACE INTO singers (name) VALUES ('Sofia Reyes')('Kungs') + * ``` + * will be + * ```c++ + * storage.prepare(replace(into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs")))); + * ``` + * One can use `default_values` to add `DEFAULT VALUES` modifier: + * ```sql + * REPLACE INTO users DEFAULT VALUES + * ``` + * will be + * ```c++ + * storage.prepare(replace(into(), default_values())); + * ``` + */ template void replace(Args... args) { auto statement = this->prepare(sqlite_orm::replace(std::forward(args)...)); @@ -1099,9 +1099,9 @@ namespace sqlite_orm::internal { } /** - * Change table name inside storage's schema info. This function does not - * affect database - */ + * Change table name inside storage's schema info. This function does not + * affect database + */ template void rename_table(std::string name) { this->assert_mapped_type(); @@ -1112,9 +1112,9 @@ namespace sqlite_orm::internal { using storage_base::rename_table; /** - * Get table's name stored in storage's schema info. This function does not call - * any SQLite queries - */ + * Get table's name stored in storage's schema info. This function does not call + * any SQLite queries + */ template const std::string& tablename() const { this->assert_mapped_type(); @@ -1415,8 +1415,8 @@ namespace sqlite_orm::internal { public: /** - * This is a cute function used to replace migration up/down functionality. - * It performs check storage schema with actual db schema and: + * This is a cute function used to replace migration up/down functionality. + * It performs check storage schema with actual db schema and: * - if there are excess tables exist in db they are ignored (not dropped) * - every table from storage is compared with it's db analog and * - if table doesn't exist it is being created @@ -1424,21 +1424,21 @@ namespace sqlite_orm::internal { * - if there are columns in db that do not exist in storage (excess) table will be dropped and recreated * - if there are columns in storage that do not exist in db they will be added using `ALTER TABLE ... ADD COLUMN ...' command * - if there is any column existing in both db and storage but differs by any of - * properties/constraints (pk, notnull, dflt_value) table will be dropped and recreated. Be aware that - * `sync_schema` doesn't guarantee that data will not be dropped. It guarantees only that it will make db - * schema the same as you specified in `make_storage` function call. A good point is that if you have no db - * file at all it will be created and all tables also will be created with exact tables and columns you - * specified in `make_storage`, `make_table` and `make_column` calls. The best practice is to call this - * function right after storage creation. - * @param preserve affects function's behaviour in case it is needed to remove a column. If it is `false` - * so table will be dropped if there is column to remove if SQLite version is < 3.35.0 and remove column if SQLite version >= 3.35.0, - * if `true` - table is being copied into another table, dropped and copied table is renamed with source table name. - * Warning: sync_schema doesn't check foreign keys cause it is unable to do so in sqlite3. If you know how to get foreign key info please - * submit an issue https://github.com/fnc12/sqlite_orm/issues - * @return std::map with std::string key equal table name and `sync_schema_result` as value. - * `sync_schema_result` is a enum value that stores table state after syncing a schema. `sync_schema_result` - * can be printed out on std::ostream with `operator<<`. - */ + * properties/constraints (pk, notnull, dflt_value) table will be dropped and recreated. Be aware that + * `sync_schema` doesn't guarantee that data will not be dropped. It guarantees only that it will make db + * schema the same as you specified in `make_storage` function call. A good point is that if you have no db + * file at all it will be created and all tables also will be created with exact tables and columns you + * specified in `make_storage`, `make_table` and `make_column` calls. The best practice is to call this + * function right after storage creation. + * @param preserve affects function's behaviour in case it is needed to remove a column. If it is `false` + * so table will be dropped if there is column to remove if SQLite version is < 3.35.0 and remove column if SQLite version >= 3.35.0, + * if `true` - table is being copied into another table, dropped and copied table is renamed with source table name. + * Warning: sync_schema doesn't check foreign keys cause it is unable to do so in sqlite3. If you know how to get foreign key info please + * submit an issue https://github.com/fnc12/sqlite_orm/issues + * @return std::map with std::string key equal table name and `sync_schema_result` as value. + * `sync_schema_result` is a enum value that stores table state after syncing a schema. `sync_schema_result` + * can be printed out on std::ostream with `operator<<`. + */ std::map sync_schema(bool preserve = false) { auto conRef = this->get_connection(); std::map result; @@ -1450,10 +1450,10 @@ namespace sqlite_orm::internal { } /** - * This function returns the same map that `sync_schema` returns but it - * doesn't perform `sync_schema` actually - just simulates it in case you want to know - * what will happen if you sync your schema. - */ + * This function returns the same map that `sync_schema` returns but it + * doesn't perform `sync_schema` actually - just simulates it in case you want to know + * what will happen if you sync your schema. + */ std::map sync_schema_simulate(bool preserve = false) { auto conRef = this->get_connection(); std::map result; @@ -1580,9 +1580,9 @@ namespace sqlite_orm::internal { } /** - * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. - * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. - */ + * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. + */ template int64 execute(const prepared_statement_t>& statement) { using object_type = statement_object_type_t; @@ -1640,9 +1640,9 @@ namespace sqlite_orm::internal { } /** - * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. - * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. - */ + * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. + */ template, is_insert_range>::value, bool> = true> int64 execute(const prepared_statement_t& statement) { using object_type = statement_object_type_t; diff --git a/dev/storage_base.h b/dev/storage_base.h index 974becc49..2d575574d 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -53,13 +53,13 @@ namespace sqlite_orm::internal { public: /** - * Attention: You must ensure that to set this function only from a single-threaded context. - */ + * Attention: You must ensure that to set this function only from a single-threaded context. + */ std::function on_open; pragma_t pragma; /** - * Attention: You must ensure that to set database limit only from a single-threaded context. - */ + * Attention: You must ensure that to set database limit only from a single-threaded context. + */ limit_accessor limit; transaction_guard_t transaction_guard() { @@ -99,93 +99,93 @@ namespace sqlite_orm::internal { } /** - * Drops index with given name. - * Calls `DROP INDEX indexName`. - * More info: https://www.sqlite.org/lang_dropindex.html - */ + * Drops index with given name. + * Calls `DROP INDEX indexName`. + * More info: https://www.sqlite.org/lang_dropindex.html + */ void drop_index(const std::string& indexName) { this->drop_index_internal(indexName, false); } /** - * Drops trigger with given name if trigger exists. - * Calls `DROP INDEX IF EXISTS indexName`. - * More info: https://www.sqlite.org/lang_dropindex.html - */ + * Drops trigger with given name if trigger exists. + * Calls `DROP INDEX IF EXISTS indexName`. + * More info: https://www.sqlite.org/lang_dropindex.html + */ void drop_index_if_exists(const std::string& indexName) { this->drop_index_internal(indexName, true); } /** - * Drops trigger with given name. - * Calls `DROP TRIGGER triggerName`. - * More info: https://www.sqlite.org/lang_droptrigger.html - */ + * Drops trigger with given name. + * Calls `DROP TRIGGER triggerName`. + * More info: https://www.sqlite.org/lang_droptrigger.html + */ void drop_trigger(const std::string& triggerName) { this->drop_trigger_internal(triggerName, false); } /** - * Drops trigger with given name if trigger exists. - * Calls `DROP TRIGGER IF EXISTS triggerName`. - * More info: https://www.sqlite.org/lang_droptrigger.html - */ + * Drops trigger with given name if trigger exists. + * Calls `DROP TRIGGER IF EXISTS triggerName`. + * More info: https://www.sqlite.org/lang_droptrigger.html + */ void drop_trigger_if_exists(const std::string& triggerName) { this->drop_trigger_internal(triggerName, true); } /** - * Drops table with given name. - * Calls `DROP TABLE tableName`. - * More info: https://www.sqlite.org/lang_droptable.html - */ + * Drops table with given name. + * Calls `DROP TABLE tableName`. + * More info: https://www.sqlite.org/lang_droptable.html + */ void drop_table(const std::string& tableName) { auto connection = this->get_connection(); this->drop_table_internal(connection.get(), tableName, false); } /** - * Drops table with given name if table exists. - * Calls `DROP TABLE IF EXISTS tableName`. - * More info: https://www.sqlite.org/lang_droptable.html - */ + * Drops table with given name if table exists. + * Calls `DROP TABLE IF EXISTS tableName`. + * More info: https://www.sqlite.org/lang_droptable.html + */ void drop_table_if_exists(const std::string& tableName) { auto connection = this->get_connection(); this->drop_table_internal(connection.get(), tableName, true); } /** - * Drops the view with the specified name. - * Calls `DROP VIEW "viewName"`. - * More info: https://www.sqlite.org/lang_droptable.html - */ + * Drops the view with the specified name. + * Calls `DROP VIEW "viewName"`. + * More info: https://www.sqlite.org/lang_droptable.html + */ void drop_view(const std::string& tableName) { auto connection = this->get_connection(); this->drop_view_internal(connection.get(), tableName, false); } /** - * Drops the view with the specified name if it exists. - * Calls `DROP VIEW IF EXISTS "viewName"`. - * More info: https://www.sqlite.org/lang_droptable.html - */ + * Drops the view with the specified name if it exists. + * Calls `DROP VIEW IF EXISTS "viewName"`. + * More info: https://www.sqlite.org/lang_droptable.html + */ void drop_view_if_exists(const std::string& tableName) { auto connection = this->get_connection(); this->drop_view_internal(connection.get(), tableName, true); } /** - * Rename table named `from` to `to`. - */ + * Rename table named `from` to `to`. + */ void rename_table(const std::string& from, const std::string& to) { auto connection = this->get_connection(); this->rename_table(connection.get(), from, to); } /** - * `VACUUM` query. - * More info: https://www.sqlite.org/lang_vacuum.html - */ + * `VACUUM` query. + * More info: https://www.sqlite.org/lang_vacuum.html + */ void vacuum() { auto connection = this->get_connection(); this->executor.perform_void_exec(connection.get(), "VACUUM"); @@ -204,10 +204,10 @@ namespace sqlite_orm::internal { } /** - * Checks whether table exists in db. Doesn't check storage itself - works only with actual database. - * Note: table can be not mapped to a storage - * @return true if table with a given name exists in db, false otherwise. - */ + * Checks whether table exists in db. Doesn't check storage itself - works only with actual database. + * Note: table can be not mapped to a storage + * @return true if table with a given name exists in db, false otherwise. + */ bool table_exists(const std::string& tableName) { auto connection = this->get_connection(); return this->table_exists(connection.get(), tableName); @@ -235,9 +235,9 @@ namespace sqlite_orm::internal { } /** - * Directly checks the actual database whether the specified view exists, bypassing the library's 'storage' mapping. - * @return true if view with the specified name exists in the database, false otherwise. - */ + * Directly checks the actual database whether the specified view exists, bypassing the library's 'storage' mapping. + * @return true if view with the specified name exists in the database, false otherwise. + */ bool view_exists(const std::string& tableName) { auto connection = this->get_connection(); return this->view_exists(connection.get(), tableName); @@ -276,16 +276,16 @@ namespace sqlite_orm::internal { public: /** - * sqlite3_changes function. - */ + * sqlite3_changes function. + */ int changes() { auto connection = this->get_connection(); return sqlite3_changes(connection.get()); } /** - * sqlite3_total_changes function. - */ + * sqlite3_total_changes function. + */ int total_changes() { auto connection = this->get_connection(); return sqlite3_total_changes(connection.get()); @@ -302,8 +302,8 @@ namespace sqlite_orm::internal { } /** - * Returns libsqlite3 version, not sqlite_orm - */ + * Returns libsqlite3 version, not sqlite_orm + */ std::string libversion() { return sqlite3_libversion(); } @@ -333,11 +333,11 @@ namespace sqlite_orm::internal { #if SQLITE_VERSION_NUMBER >= 3007010 /** - * \fn db_release_memory - * \brief Releases freeable memory of database. It is function can/should be called periodically by - * application, if application has less memory usage constraint. \note sqlite3_db_release_memory added - * in 3.7.10 https://sqlite.org/changes.html - */ + * \fn db_release_memory + * \brief Releases freeable memory of database. It is function can/should be called periodically by + * application, if application has less memory usage constraint. \note sqlite3_db_release_memory added + * in 3.7.10 https://sqlite.org/changes.html + */ int db_release_memory() { auto connection = this->get_connection(); return sqlite3_db_release_memory(connection.get()); @@ -345,41 +345,41 @@ namespace sqlite_orm::internal { #endif /** - * Returns the names of existing permanent view in the database. Doesn't check storage itself - works only with - * actual database. - * @return Returns list of tables in database. - */ + * Returns the names of existing permanent view in the database. Doesn't check storage itself - works only with + * actual database. + * @return Returns list of tables in database. + */ std::vector view_names() { return this->object_names("view"); } /** - * Returns existing permanent table names in database. Doesn't check storage itself - works only with - * actual database. - * @return Returns list of tables in database. - */ + * Returns existing permanent table names in database. Doesn't check storage itself - works only with + * actual database. + * @return Returns list of tables in database. + */ std::vector table_names() { return this->object_names("table"); } /** - * Returns existing permanent trigger names in database. Doesn't check storage itself - works only with - * actual database. - * @return Returns list of triggers in database. - */ + * Returns existing permanent trigger names in database. Doesn't check storage itself - works only with + * actual database. + * @return Returns list of triggers in database. + */ std::vector trigger_names() { return this->object_names("trigger"); } /** - * Call it once during storage lifetime to make it keeping its connection opened till dtor call. - * By default if storage is not in-memory it calls `sqlite3_open` only when the connection is really - * needed and closes when it is not needed. This function establishes a permanent connection. - * In-memory storage always establishes a permanent connection, so calling this method is a no-op. - * - * Attention: You must ensure to call this method only in a single-threaded context. - * An alternative way to establish a permanent connection is to specify control options to `make_storage()`. - */ + * Call it once during storage lifetime to make it keeping its connection opened till dtor call. + * By default if storage is not in-memory it calls `sqlite3_open` only when the connection is really + * needed and closes when it is not needed. This function establishes a permanent connection. + * In-memory storage always establishes a permanent connection, so calling this method is a no-op. + * + * Attention: You must ensure to call this method only in a single-threaded context. + * An alternative way to establish a permanent connection is to specify control options to `make_storage()`. + */ void open_forever() { if (!this->isOpenedForever) { this->isOpenedForever = true; @@ -389,30 +389,30 @@ namespace sqlite_orm::internal { } /** - * Create an application-defined scalar SQL function. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - * - * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, - * together with a copy of the passed initialization arguments. - * If `F` is a stateless function object, an instance of the function object is created once, otherwise - * an instance of the function object is repeatedly recreated for each result row, - * ensuring that the calculations always start with freshly initialized values. - * - * T - function class. T must have a single call operator and static name function like this: - * ``` - * struct SqrtFunction { - * double operator()(double arg) const { - * return std::sqrt(arg); - * } - * - * static const char* name() { - * return "SQRT"; - * } - * }; - * ``` - */ + * Create an application-defined scalar SQL function. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + * + * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * If `F` is a stateless function object, an instance of the function object is created once, otherwise + * an instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. + * + * T - function class. T must have a single call operator and static name function like this: + * ``` + * struct SqrtFunction { + * double operator()(double arg) const { + * return std::sqrt(arg); + * } + * + * static const char* name() { + * return "SQRT"; + * } + * }; + * ``` + */ template void create_scalar_function(Args&&... constructorArgs) { static_assert(is_scalar_udf_v, "F must be a scalar function"); @@ -433,32 +433,32 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Create an application-defined scalar function. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - * - * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, - * together with a copy of the passed initialization arguments. - * If `F` is a stateless function object, an instance of the function object is created once, otherwise - * an instance of the function object is repeatedly recreated for each result row, - * ensuring that the calculations always start with freshly initialized values. - */ + * Create an application-defined scalar function. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + * + * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * If `F` is a stateless function object, an instance of the function object is created once, otherwise + * an instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. + */ template void create_scalar_function(Args&&... constructorArgs) { return this->create_scalar_function>(std::forward(constructorArgs)...); } /** - * Create an application-defined scalar function. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - * - * If `quotedF` contains a freestanding function, stateless lambda or stateless function object, - * `quoted_scalar_function::_callable()` uses the original function object, assuming it is free of side effects; - * otherwise, it repeatedly uses a copy of the contained function object, assuming possible side effects. - */ + * Create an application-defined scalar function. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + * + * If `quotedF` contains a freestanding function, stateless lambda or stateless function object, + * `quoted_scalar_function::_callable()` uses the original function object, assuming it is free of side effects; + * otherwise, it repeatedly uses a copy of the contained function object, assuming possible side effects. + */ template requires (orm_quoted_scalar_function) void create_scalar_function() { @@ -492,37 +492,37 @@ namespace sqlite_orm::internal { #endif /** - * Create an application-defined aggregate SQL function. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - * - * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, - * together with a copy of the passed initialization arguments. - * An instance of the function object is repeatedly recreated for each result row, - * ensuring that the calculations always start with freshly initialized values. - * - * T - function class. T must have step member function, fin member function and static name function like this: - * ``` - * struct MeanFunction { - * double total = 0; - * int count = 0; - * - * void step(double value) { - * total += value; - * ++count; - * } - * - * int fin() const { - * return total / count; - * } - * - * static std::string name() { - * return "MEAN"; - * } - * }; - * ``` - */ + * Create an application-defined aggregate SQL function. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + * + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. + * + * T - function class. T must have step member function, fin member function and static name function like this: + * ``` + * struct MeanFunction { + * double total = 0; + * int count = 0; + * + * void step(double value) { + * total += value; + * ++count; + * } + * + * int fin() const { + * return total / count; + * } + * + * static std::string name() { + * return "MEAN"; + * } + * }; + * ``` + */ template void create_aggregate_function(Args&&... constructorArgs) { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); @@ -543,16 +543,16 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Create an application-defined aggregate function. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - * - * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, - * together with a copy of the passed initialization arguments. - * An instance of the function object is repeatedly recreated for each result row, - * ensuring that the calculations always start with freshly initialized values. - */ + * Create an application-defined aggregate function. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + * + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. + */ template void create_aggregate_function(Args&&... constructorArgs) { return this->create_aggregate_function>(std::forward(constructorArgs)...); @@ -560,11 +560,11 @@ namespace sqlite_orm::internal { #endif /** - * Delete a scalar function you created before. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Delete a scalar function you created before. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void delete_scalar_function() { static_assert(is_scalar_udf_v, "F must be a scalar function"); @@ -574,22 +574,22 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Delete a scalar function you created before. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Delete a scalar function you created before. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void delete_scalar_function() { this->delete_function_impl(f.name(), this->scalarFunctions); } /** - * Delete a quoted scalar function you created before. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Delete a quoted scalar function you created before. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template requires (orm_quoted_scalar_function) void delete_scalar_function() { @@ -598,11 +598,11 @@ namespace sqlite_orm::internal { #endif /** - * Delete aggregate function you created before. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Delete aggregate function you created before. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void delete_aggregate_function() { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); @@ -612,11 +612,11 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Delete aggregate function you created before. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Delete aggregate function you created before. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void delete_aggregate_function() { this->delete_function_impl(f.name(), this->aggregateFunctions); @@ -624,8 +624,8 @@ namespace sqlite_orm::internal { #endif /** - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void create_collation() { collating_function func = [](int leftLength, const void* lhs, int rightLength, const void* rhs) @@ -639,8 +639,8 @@ namespace sqlite_orm::internal { } /** - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Attention: You must ensure that to call this method only in a single-threaded context. + */ void create_collation(const std::string& name, collating_function f) { const auto functionExists = bool(f); collating_function* function = nullptr; @@ -666,8 +666,8 @@ namespace sqlite_orm::internal { } /** - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void delete_collation() { std::stringstream ss; @@ -760,41 +760,41 @@ namespace sqlite_orm::internal { } /** - * Checks whether connection to database is opened right now. - * Returns always `true` for in memory databases. - * @attention While retrieving the reference count value is atomic it makes only sense in single-threaded contexts. - */ + * Checks whether connection to database is opened right now. + * Returns always `true` for in memory databases. + * @attention While retrieving the reference count value is atomic it makes only sense in single-threaded contexts. + */ bool is_opened() const { connection_ptr maybeConnection = *this->connection; return maybeConnection || false; } /** - * Return the name of the VFS object used by the database connection. - */ + * Return the name of the VFS object used by the database connection. + */ const std::string& vfs_name() const { return this->connection->dbArgs.vfs_name; } /** - * Return the current open_mode for this storage object. - */ + * Return the current open_mode for this storage object. + */ db_open_mode open_mode() const { return this->connection->dbArgs.open_mode; } /** - * Return true if this database object is opened in a readonly state. - */ + * Return true if this database object is opened in a readonly state. + */ bool db_readonly() { auto connection = this->get_connection(); return static_cast(sqlite3_db_readonly(connection.get(), "main")); } /* - * returning false when there is a transaction in place - * otherwise true; function is not const because it has to call get_connection() - */ + * returning false when there is a transaction in place + * otherwise true; function is not const because it has to call get_connection() + */ bool get_autocommit() { auto connection = this->get_connection(); return sqlite3_get_autocommit(connection.get()); @@ -1155,7 +1155,7 @@ namespace sqlite_orm::internal { std::string result; std::stringstream ss; ss << "SELECT sql FROM sqlite_master WHERE type = " << quote_string_literal(type) - << " AND name = " << quote_string_literal(name); + << " AND name = " << quote_string_literal(name) << std::flush; this->executor.perform_exec( db, ss.str(), diff --git a/dev/storage_impl.h b/dev/storage_impl.h index 1518b0bb6..1122f424f 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -46,18 +46,18 @@ namespace sqlite_orm::internal { } /** - * Find column name by its type and member pointer. - */ + * Find column name by its type and member pointer. + */ template = true> const std::string* find_column_name(const DBOs& dbObjects, F Lookup::* field) { return pick_table>(dbObjects).find_column_name(field); } /** - * Materialize column pointer: - * 1. by explicit object type and member pointer. - * 2. by moniker and member pointer. - */ + * Materialize column pointer: + * 1. by explicit object type and member pointer. + * 2. by moniker and member pointer. + */ template = true> constexpr decltype(auto) materialize_column_pointer(const DBOs&, const column_pointer& cp) { return cp.field; @@ -65,11 +65,11 @@ namespace sqlite_orm::internal { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) /** - * Materialize column pointer: - * 3. by moniker and alias_holder<>. - * - * internal note: there's an overload for `find_column_name()` that avoids going through `cte_table<>::find_column_name()` - */ + * Materialize column pointer: + * 3. by moniker and alias_holder<>. + * + * internal note: there's an overload for `find_column_name()` that avoids going through `cte_table<>::find_column_name()` + */ template = true> constexpr decltype(auto) materialize_column_pointer(const DBOs&, const column_pointer>&) { @@ -87,10 +87,10 @@ namespace sqlite_orm::internal { #endif /** - * Find column name by: - * 1. by explicit object type and member pointer. - * 2. by moniker and member pointer. - */ + * Find column name by: + * 1. by explicit object type and member pointer. + * 2. by moniker and member pointer. + */ template = true> const std::string* find_column_name(const DBOs& dbObjects, const column_pointer& cp) { auto field = materialize_column_pointer(dbObjects, cp); @@ -99,9 +99,9 @@ namespace sqlite_orm::internal { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) /** - * Find column name by: - * 3. by moniker and alias_holder<>. - */ + * Find column name by: + * 3. by moniker and alias_holder<>. + */ template = true> constexpr decltype(auto) find_column_name(const DBOs& dboObjects, const column_pointer>&) { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 36a6d9378..877d80ead 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -14077,18 +14077,18 @@ namespace sqlite_orm::internal { } /** - * Find column name by its type and member pointer. - */ + * Find column name by its type and member pointer. + */ template = true> const std::string* find_column_name(const DBOs& dbObjects, F Lookup::* field) { return pick_table>(dbObjects).find_column_name(field); } /** - * Materialize column pointer: - * 1. by explicit object type and member pointer. - * 2. by moniker and member pointer. - */ + * Materialize column pointer: + * 1. by explicit object type and member pointer. + * 2. by moniker and member pointer. + */ template = true> constexpr decltype(auto) materialize_column_pointer(const DBOs&, const column_pointer& cp) { return cp.field; @@ -14096,11 +14096,11 @@ namespace sqlite_orm::internal { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) /** - * Materialize column pointer: - * 3. by moniker and alias_holder<>. - * - * internal note: there's an overload for `find_column_name()` that avoids going through `cte_table<>::find_column_name()` - */ + * Materialize column pointer: + * 3. by moniker and alias_holder<>. + * + * internal note: there's an overload for `find_column_name()` that avoids going through `cte_table<>::find_column_name()` + */ template = true> constexpr decltype(auto) materialize_column_pointer(const DBOs&, const column_pointer>&) { @@ -14118,10 +14118,10 @@ namespace sqlite_orm::internal { #endif /** - * Find column name by: - * 1. by explicit object type and member pointer. - * 2. by moniker and member pointer. - */ + * Find column name by: + * 1. by explicit object type and member pointer. + * 2. by moniker and member pointer. + */ template = true> const std::string* find_column_name(const DBOs& dbObjects, const column_pointer& cp) { auto field = materialize_column_pointer(dbObjects, cp); @@ -14130,9 +14130,9 @@ namespace sqlite_orm::internal { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) /** - * Find column name by: - * 3. by moniker and alias_holder<>. - */ + * Find column name by: + * 3. by moniker and alias_holder<>. + */ template = true> constexpr decltype(auto) find_column_name(const DBOs& dboObjects, const column_pointer>&) { @@ -14955,8 +14955,8 @@ namespace sqlite_orm::internal { }; /** - * Function object for building an object from a result row. - */ + * Function object for building an object from a result row. + */ template struct object_from_column_builder : object_from_column_builder_base { using object_type = O; @@ -14993,10 +14993,10 @@ namespace sqlite_orm::internal { }; /** - * Specialization for a table reference. - * - * This plays together with `column_result_of_t`, which returns `object_t` as `table_reference` - */ + * Specialization for a table reference. + * + * This plays together with `column_result_of_t`, which returns `object_t` as `table_reference` + */ template struct struct_extractor, DBOs> { const DBOs& db_objects; @@ -19842,13 +19842,13 @@ namespace sqlite_orm::internal { public: /** - * Attention: You must ensure that to set this function only from a single-threaded context. - */ + * Attention: You must ensure that to set this function only from a single-threaded context. + */ std::function on_open; pragma_t pragma; /** - * Attention: You must ensure that to set database limit only from a single-threaded context. - */ + * Attention: You must ensure that to set database limit only from a single-threaded context. + */ limit_accessor limit; transaction_guard_t transaction_guard() { @@ -19888,93 +19888,93 @@ namespace sqlite_orm::internal { } /** - * Drops index with given name. - * Calls `DROP INDEX indexName`. - * More info: https://www.sqlite.org/lang_dropindex.html - */ + * Drops index with given name. + * Calls `DROP INDEX indexName`. + * More info: https://www.sqlite.org/lang_dropindex.html + */ void drop_index(const std::string& indexName) { this->drop_index_internal(indexName, false); } /** - * Drops trigger with given name if trigger exists. - * Calls `DROP INDEX IF EXISTS indexName`. - * More info: https://www.sqlite.org/lang_dropindex.html - */ + * Drops trigger with given name if trigger exists. + * Calls `DROP INDEX IF EXISTS indexName`. + * More info: https://www.sqlite.org/lang_dropindex.html + */ void drop_index_if_exists(const std::string& indexName) { this->drop_index_internal(indexName, true); } /** - * Drops trigger with given name. - * Calls `DROP TRIGGER triggerName`. - * More info: https://www.sqlite.org/lang_droptrigger.html - */ + * Drops trigger with given name. + * Calls `DROP TRIGGER triggerName`. + * More info: https://www.sqlite.org/lang_droptrigger.html + */ void drop_trigger(const std::string& triggerName) { this->drop_trigger_internal(triggerName, false); } /** - * Drops trigger with given name if trigger exists. - * Calls `DROP TRIGGER IF EXISTS triggerName`. - * More info: https://www.sqlite.org/lang_droptrigger.html - */ + * Drops trigger with given name if trigger exists. + * Calls `DROP TRIGGER IF EXISTS triggerName`. + * More info: https://www.sqlite.org/lang_droptrigger.html + */ void drop_trigger_if_exists(const std::string& triggerName) { this->drop_trigger_internal(triggerName, true); } /** - * Drops table with given name. - * Calls `DROP TABLE tableName`. - * More info: https://www.sqlite.org/lang_droptable.html - */ + * Drops table with given name. + * Calls `DROP TABLE tableName`. + * More info: https://www.sqlite.org/lang_droptable.html + */ void drop_table(const std::string& tableName) { auto connection = this->get_connection(); this->drop_table_internal(connection.get(), tableName, false); } /** - * Drops table with given name if table exists. - * Calls `DROP TABLE IF EXISTS tableName`. - * More info: https://www.sqlite.org/lang_droptable.html - */ + * Drops table with given name if table exists. + * Calls `DROP TABLE IF EXISTS tableName`. + * More info: https://www.sqlite.org/lang_droptable.html + */ void drop_table_if_exists(const std::string& tableName) { auto connection = this->get_connection(); this->drop_table_internal(connection.get(), tableName, true); } /** - * Drops the view with the specified name. - * Calls `DROP VIEW "viewName"`. - * More info: https://www.sqlite.org/lang_droptable.html - */ + * Drops the view with the specified name. + * Calls `DROP VIEW "viewName"`. + * More info: https://www.sqlite.org/lang_droptable.html + */ void drop_view(const std::string& tableName) { auto connection = this->get_connection(); this->drop_view_internal(connection.get(), tableName, false); } /** - * Drops the view with the specified name if it exists. - * Calls `DROP VIEW IF EXISTS "viewName"`. - * More info: https://www.sqlite.org/lang_droptable.html - */ + * Drops the view with the specified name if it exists. + * Calls `DROP VIEW IF EXISTS "viewName"`. + * More info: https://www.sqlite.org/lang_droptable.html + */ void drop_view_if_exists(const std::string& tableName) { auto connection = this->get_connection(); this->drop_view_internal(connection.get(), tableName, true); } /** - * Rename table named `from` to `to`. - */ + * Rename table named `from` to `to`. + */ void rename_table(const std::string& from, const std::string& to) { auto connection = this->get_connection(); this->rename_table(connection.get(), from, to); } /** - * `VACUUM` query. - * More info: https://www.sqlite.org/lang_vacuum.html - */ + * `VACUUM` query. + * More info: https://www.sqlite.org/lang_vacuum.html + */ void vacuum() { auto connection = this->get_connection(); this->executor.perform_void_exec(connection.get(), "VACUUM"); @@ -19993,10 +19993,10 @@ namespace sqlite_orm::internal { } /** - * Checks whether table exists in db. Doesn't check storage itself - works only with actual database. - * Note: table can be not mapped to a storage - * @return true if table with a given name exists in db, false otherwise. - */ + * Checks whether table exists in db. Doesn't check storage itself - works only with actual database. + * Note: table can be not mapped to a storage + * @return true if table with a given name exists in db, false otherwise. + */ bool table_exists(const std::string& tableName) { auto connection = this->get_connection(); return this->table_exists(connection.get(), tableName); @@ -20024,9 +20024,9 @@ namespace sqlite_orm::internal { } /** - * Directly checks the actual database whether the specified view exists, bypassing the library's 'storage' mapping. - * @return true if view with the specified name exists in the database, false otherwise. - */ + * Directly checks the actual database whether the specified view exists, bypassing the library's 'storage' mapping. + * @return true if view with the specified name exists in the database, false otherwise. + */ bool view_exists(const std::string& tableName) { auto connection = this->get_connection(); return this->view_exists(connection.get(), tableName); @@ -20065,16 +20065,16 @@ namespace sqlite_orm::internal { public: /** - * sqlite3_changes function. - */ + * sqlite3_changes function. + */ int changes() { auto connection = this->get_connection(); return sqlite3_changes(connection.get()); } /** - * sqlite3_total_changes function. - */ + * sqlite3_total_changes function. + */ int total_changes() { auto connection = this->get_connection(); return sqlite3_total_changes(connection.get()); @@ -20091,8 +20091,8 @@ namespace sqlite_orm::internal { } /** - * Returns libsqlite3 version, not sqlite_orm - */ + * Returns libsqlite3 version, not sqlite_orm + */ std::string libversion() { return sqlite3_libversion(); } @@ -20122,11 +20122,11 @@ namespace sqlite_orm::internal { #if SQLITE_VERSION_NUMBER >= 3007010 /** - * \fn db_release_memory - * \brief Releases freeable memory of database. It is function can/should be called periodically by - * application, if application has less memory usage constraint. \note sqlite3_db_release_memory added - * in 3.7.10 https://sqlite.org/changes.html - */ + * \fn db_release_memory + * \brief Releases freeable memory of database. It is function can/should be called periodically by + * application, if application has less memory usage constraint. \note sqlite3_db_release_memory added + * in 3.7.10 https://sqlite.org/changes.html + */ int db_release_memory() { auto connection = this->get_connection(); return sqlite3_db_release_memory(connection.get()); @@ -20134,41 +20134,41 @@ namespace sqlite_orm::internal { #endif /** - * Returns the names of existing permanent view in the database. Doesn't check storage itself - works only with - * actual database. - * @return Returns list of tables in database. - */ + * Returns the names of existing permanent view in the database. Doesn't check storage itself - works only with + * actual database. + * @return Returns list of tables in database. + */ std::vector view_names() { return this->object_names("view"); } /** - * Returns existing permanent table names in database. Doesn't check storage itself - works only with - * actual database. - * @return Returns list of tables in database. - */ + * Returns existing permanent table names in database. Doesn't check storage itself - works only with + * actual database. + * @return Returns list of tables in database. + */ std::vector table_names() { return this->object_names("table"); } /** - * Returns existing permanent trigger names in database. Doesn't check storage itself - works only with - * actual database. - * @return Returns list of triggers in database. - */ + * Returns existing permanent trigger names in database. Doesn't check storage itself - works only with + * actual database. + * @return Returns list of triggers in database. + */ std::vector trigger_names() { return this->object_names("trigger"); } /** - * Call it once during storage lifetime to make it keeping its connection opened till dtor call. - * By default if storage is not in-memory it calls `sqlite3_open` only when the connection is really - * needed and closes when it is not needed. This function establishes a permanent connection. - * In-memory storage always establishes a permanent connection, so calling this method is a no-op. - * - * Attention: You must ensure to call this method only in a single-threaded context. - * An alternative way to establish a permanent connection is to specify control options to `make_storage()`. - */ + * Call it once during storage lifetime to make it keeping its connection opened till dtor call. + * By default if storage is not in-memory it calls `sqlite3_open` only when the connection is really + * needed and closes when it is not needed. This function establishes a permanent connection. + * In-memory storage always establishes a permanent connection, so calling this method is a no-op. + * + * Attention: You must ensure to call this method only in a single-threaded context. + * An alternative way to establish a permanent connection is to specify control options to `make_storage()`. + */ void open_forever() { if (!this->isOpenedForever) { this->isOpenedForever = true; @@ -20178,30 +20178,30 @@ namespace sqlite_orm::internal { } /** - * Create an application-defined scalar SQL function. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - * - * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, - * together with a copy of the passed initialization arguments. - * If `F` is a stateless function object, an instance of the function object is created once, otherwise - * an instance of the function object is repeatedly recreated for each result row, - * ensuring that the calculations always start with freshly initialized values. - * - * T - function class. T must have a single call operator and static name function like this: - * ``` - * struct SqrtFunction { - * double operator()(double arg) const { - * return std::sqrt(arg); - * } - * - * static const char* name() { - * return "SQRT"; - * } - * }; - * ``` - */ + * Create an application-defined scalar SQL function. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + * + * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * If `F` is a stateless function object, an instance of the function object is created once, otherwise + * an instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. + * + * T - function class. T must have a single call operator and static name function like this: + * ``` + * struct SqrtFunction { + * double operator()(double arg) const { + * return std::sqrt(arg); + * } + * + * static const char* name() { + * return "SQRT"; + * } + * }; + * ``` + */ template void create_scalar_function(Args&&... constructorArgs) { static_assert(is_scalar_udf_v, "F must be a scalar function"); @@ -20222,32 +20222,32 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Create an application-defined scalar function. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - * - * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, - * together with a copy of the passed initialization arguments. - * If `F` is a stateless function object, an instance of the function object is created once, otherwise - * an instance of the function object is repeatedly recreated for each result row, - * ensuring that the calculations always start with freshly initialized values. - */ + * Create an application-defined scalar function. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + * + * Note: `create_scalar_function()` merely creates a closure to generate an instance of the scalar function object, + * together with a copy of the passed initialization arguments. + * If `F` is a stateless function object, an instance of the function object is created once, otherwise + * an instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. + */ template void create_scalar_function(Args&&... constructorArgs) { return this->create_scalar_function>(std::forward(constructorArgs)...); } /** - * Create an application-defined scalar function. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - * - * If `quotedF` contains a freestanding function, stateless lambda or stateless function object, - * `quoted_scalar_function::_callable()` uses the original function object, assuming it is free of side effects; - * otherwise, it repeatedly uses a copy of the contained function object, assuming possible side effects. - */ + * Create an application-defined scalar function. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + * + * If `quotedF` contains a freestanding function, stateless lambda or stateless function object, + * `quoted_scalar_function::_callable()` uses the original function object, assuming it is free of side effects; + * otherwise, it repeatedly uses a copy of the contained function object, assuming possible side effects. + */ template requires (orm_quoted_scalar_function) void create_scalar_function() { @@ -20281,37 +20281,37 @@ namespace sqlite_orm::internal { #endif /** - * Create an application-defined aggregate SQL function. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - * - * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, - * together with a copy of the passed initialization arguments. - * An instance of the function object is repeatedly recreated for each result row, - * ensuring that the calculations always start with freshly initialized values. - * - * T - function class. T must have step member function, fin member function and static name function like this: - * ``` - * struct MeanFunction { - * double total = 0; - * int count = 0; - * - * void step(double value) { - * total += value; - * ++count; - * } - * - * int fin() const { - * return total / count; - * } - * - * static std::string name() { - * return "MEAN"; - * } - * }; - * ``` - */ + * Create an application-defined aggregate SQL function. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + * + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. + * + * T - function class. T must have step member function, fin member function and static name function like this: + * ``` + * struct MeanFunction { + * double total = 0; + * int count = 0; + * + * void step(double value) { + * total += value; + * ++count; + * } + * + * int fin() const { + * return total / count; + * } + * + * static std::string name() { + * return "MEAN"; + * } + * }; + * ``` + */ template void create_aggregate_function(Args&&... constructorArgs) { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); @@ -20332,16 +20332,16 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Create an application-defined aggregate function. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - * - * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, - * together with a copy of the passed initialization arguments. - * An instance of the function object is repeatedly recreated for each result row, - * ensuring that the calculations always start with freshly initialized values. - */ + * Create an application-defined aggregate function. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + * + * Note: `create_aggregate_function()` merely creates a closure to generate an instance of the aggregate function object, + * together with a copy of the passed initialization arguments. + * An instance of the function object is repeatedly recreated for each result row, + * ensuring that the calculations always start with freshly initialized values. + */ template void create_aggregate_function(Args&&... constructorArgs) { return this->create_aggregate_function>(std::forward(constructorArgs)...); @@ -20349,11 +20349,11 @@ namespace sqlite_orm::internal { #endif /** - * Delete a scalar function you created before. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Delete a scalar function you created before. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void delete_scalar_function() { static_assert(is_scalar_udf_v, "F must be a scalar function"); @@ -20363,22 +20363,22 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Delete a scalar function you created before. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Delete a scalar function you created before. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void delete_scalar_function() { this->delete_function_impl(f.name(), this->scalarFunctions); } /** - * Delete a quoted scalar function you created before. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Delete a quoted scalar function you created before. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template requires (orm_quoted_scalar_function) void delete_scalar_function() { @@ -20387,11 +20387,11 @@ namespace sqlite_orm::internal { #endif /** - * Delete aggregate function you created before. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Delete aggregate function you created before. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void delete_aggregate_function() { static_assert(is_aggregate_udf_v, "F must be an aggregate function"); @@ -20401,11 +20401,11 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * Delete aggregate function you created before. - * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. - * - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Delete aggregate function you created before. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. + * + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void delete_aggregate_function() { this->delete_function_impl(f.name(), this->aggregateFunctions); @@ -20413,8 +20413,8 @@ namespace sqlite_orm::internal { #endif /** - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void create_collation() { collating_function func = [](int leftLength, const void* lhs, int rightLength, const void* rhs) @@ -20428,8 +20428,8 @@ namespace sqlite_orm::internal { } /** - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Attention: You must ensure that to call this method only in a single-threaded context. + */ void create_collation(const std::string& name, collating_function f) { const auto functionExists = bool(f); collating_function* function = nullptr; @@ -20455,8 +20455,8 @@ namespace sqlite_orm::internal { } /** - * Attention: You must ensure that to call this method only in a single-threaded context. - */ + * Attention: You must ensure that to call this method only in a single-threaded context. + */ template void delete_collation() { std::stringstream ss; @@ -20549,41 +20549,41 @@ namespace sqlite_orm::internal { } /** - * Checks whether connection to database is opened right now. - * Returns always `true` for in memory databases. - * @attention While retrieving the reference count value is atomic it makes only sense in single-threaded contexts. - */ + * Checks whether connection to database is opened right now. + * Returns always `true` for in memory databases. + * @attention While retrieving the reference count value is atomic it makes only sense in single-threaded contexts. + */ bool is_opened() const { connection_ptr maybeConnection = *this->connection; return maybeConnection || false; } /** - * Return the name of the VFS object used by the database connection. - */ + * Return the name of the VFS object used by the database connection. + */ const std::string& vfs_name() const { return this->connection->dbArgs.vfs_name; } /** - * Return the current open_mode for this storage object. - */ + * Return the current open_mode for this storage object. + */ db_open_mode open_mode() const { return this->connection->dbArgs.open_mode; } /** - * Return true if this database object is opened in a readonly state. - */ + * Return true if this database object is opened in a readonly state. + */ bool db_readonly() { auto connection = this->get_connection(); return static_cast(sqlite3_db_readonly(connection.get(), "main")); } /* - * returning false when there is a transaction in place - * otherwise true; function is not const because it has to call get_connection() - */ + * returning false when there is a transaction in place + * otherwise true; function is not const because it has to call get_connection() + */ bool get_autocommit() { auto connection = this->get_connection(); return sqlite3_get_autocommit(connection.get()); @@ -20944,7 +20944,7 @@ namespace sqlite_orm::internal { std::string result; std::stringstream ss; ss << "SELECT sql FROM sqlite_master WHERE type = " << quote_string_literal(type) - << " AND name = " << quote_string_literal(name); + << " AND name = " << quote_string_literal(name) << std::flush; this->executor.perform_exec( db, ss.str(), @@ -22148,8 +22148,8 @@ namespace sqlite_orm::internal { } /** - * Serializer for bindable types. - */ + * Serializer for bindable types. + */ template struct statement_serializer> { using statement_type = T; @@ -22210,8 +22210,8 @@ namespace sqlite_orm::internal { #endif #endif /** - * Specialization for binary data (std::vector). - */ + * Specialization for binary data (std::vector). + */ static std::string do_serialize(const std::vector& t) { return quote_blob_literal(field_printer>{}(t)); } @@ -22364,8 +22364,8 @@ namespace sqlite_orm::internal { }; /** - * Serializer for literal values. - */ + * Serializer for literal values. + */ template struct statement_serializer> { using statement_type = T; @@ -24834,9 +24834,9 @@ namespace sqlite_orm::internal { }; /** - * HO - has offset - * OI - offset is implicit - */ + * HO - has offset + * OI - offset is implicit + */ template struct statement_serializer, void> { using statement_type = limit_t; @@ -25183,8 +25183,8 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED /** - * Concatenate newly created tables with given DBOs, forming a new set of DBOs. - */ + * Concatenate newly created tables with given DBOs, forming a new set of DBOs. + */ template auto db_objects_cat(const DBOs& dbObjects, CTETables&&... cteTables) { auto& [... elements] = dbObjects; @@ -25192,16 +25192,16 @@ namespace sqlite_orm::internal { } #else /** - * Concatenate newly created tables with given DBOs, forming a new set of DBOs. - */ + * Concatenate newly created tables with given DBOs, forming a new set of DBOs. + */ template auto db_objects_cat(const DBOs& dbObjects, std::index_sequence, CTETables&&... cteTables) { return std::tuple{std::forward(cteTables)..., std::get(dbObjects)...}; } /** - * Concatenate newly created tables with given DBOs, forming a new set of DBOs. - */ + * Concatenate newly created tables with given DBOs, forming a new set of DBOs. + */ template auto db_objects_cat(const DBOs& dbObjects, CTETables&&... cteTables) { return db_objects_cat(dbObjects, @@ -25211,35 +25211,35 @@ namespace sqlite_orm::internal { #endif /** - * This function returns the expression contained in a subselect that is relevant for - * creating the definition of a CTE table. - * Because CTEs can recursively refer to themselves in a compound statement, parsing - * the whole compound statement would lead to compiler errors if a column_pointer<> - * can't be resolved. Therefore, at the time of building a CTE table, we are only - * interested in the column results of the left-most select expression. - */ + * This function returns the expression contained in a subselect that is relevant for + * creating the definition of a CTE table. + * Because CTEs can recursively refer to themselves in a compound statement, parsing + * the whole compound statement would lead to compiler errors if a column_pointer<> + * can't be resolved. Therefore, at the time of building a CTE table, we are only + * interested in the column results of the left-most select expression. + */ template decltype(auto) get_cte_driving_subselect(const Select& subSelect); /** - * Return given select expression. - */ + * Return given select expression. + */ template decltype(auto) get_cte_driving_subselect(const Select& subSelect) { return subSelect; } /** - * Return left-most select expression of compound statement. - */ + * Return left-most select expression of compound statement. + */ template, bool> = true> decltype(auto) get_cte_driving_subselect(const select_t& subSelect) { return std::get<0>(subSelect.col.compound); } /** - * Return a tuple of member pointers of all columns - */ + * Return a tuple of member pointers of all columns + */ template auto get_table_columns_fields(const C& coldef, std::index_sequence) { return std::make_tuple(get(coldef).member_pointer...); @@ -25323,9 +25323,9 @@ namespace sqlite_orm::internal { void extract_colref_expressions(const DBOs& /*dbObjects*/, const Compound& /*subSelect*/) = delete; /* - * Depending on ExplicitColRef's type returns either the explicit column reference - * or the expression's column reference otherwise. - */ + * Depending on ExplicitColRef's type returns either the explicit column reference + * or the expression's column reference otherwise. + */ template auto determine_cte_colref(const DBOs& /*dbObjects*/, const SubselectColRef& subselectColRef, @@ -25427,8 +25427,8 @@ namespace sqlite_orm::internal { } /** - * Return new DBOs for CTE expressions. - */ + * Return new DBOs for CTE expressions. + */ template = true> decltype(auto) db_objects_for_expression(DBOs& dbObjects, const with_t& e) { return make_recursive_cte_db_objects(dbObjects, e.cte, std::index_sequence_for{}); @@ -25512,10 +25512,10 @@ namespace sqlite_orm::internal { namespace sqlite_orm::internal { /* - * Implementation note: the technique of indirect expression testing is because - * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. - * It must also be a type that differs from those for `is_printable_v`, `is_bindable_v`. - */ + * Implementation note: the technique of indirect expression testing is because + * of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_ALIAS_TEMPLATE_DEPENDENT_EXPR_SFINAE]. + * It must also be a type that differs from those for `is_printable_v`, `is_bindable_v`. + */ template struct indirectly_test_preparable; @@ -25537,18 +25537,18 @@ namespace sqlite_orm::internal { } /** - * Storage class itself. Create an instance to use it as an interfacto to sqlite db by calling `make_storage` - * function. - */ + * Storage class itself. Create an instance to use it as an interfacto to sqlite db by calling `make_storage` + * function. + */ template struct storage_t : storage_base { using self_type = storage_t; using db_objects_type = db_objects_tuple; /** - * @param filename database filename. - * @param dbObjects db_objects_tuple - */ + * @param filename database filename. + * @param dbObjects db_objects_tuple + */ template storage_t(std::string filename, db_objects_type dbObjects, OptionsTpl options) : storage_base{std::move(filename), @@ -25569,17 +25569,17 @@ namespace sqlite_orm::internal { db_objects_type db_objects; /** - * Obtain a storage_t's const db_objects_tuple. - * - * @note Historically, `serializer_context_builder` was declared friend, along with - * a few other library stock objects, in order to limit access to the db_objects_tuple. - * However, one could gain access to a storage_t's db_objects_tuple through - * `serializer_context_builder`, hence leading the whole friend declaration mambo-jumbo - * ad absurdum. - * Providing a free function is way better and cleaner. - * - * Hence, friend was replaced by `obtain_db_objects()` and `pick_const_impl()`. - */ + * Obtain a storage_t's const db_objects_tuple. + * + * @note Historically, `serializer_context_builder` was declared friend, along with + * a few other library stock objects, in order to limit access to the db_objects_tuple. + * However, one could gain access to a storage_t's db_objects_tuple through + * `serializer_context_builder`, hence leading the whole friend declaration mambo-jumbo + * ad absurdum. + * Providing a free function is way better and cleaner. + * + * Hence, friend was replaced by `obtain_db_objects()` and `pick_const_impl()`. + */ friend const db_objects_type& obtain_db_objects(const self_type& storage) noexcept { return storage.db_objects; } @@ -25614,9 +25614,9 @@ namespace sqlite_orm::internal { } /** - * Copies sourceTableName to another table with name: destinationTableName - * Performs INSERT INTO %destinationTableName% () SELECT %table.column_names% FROM %sourceTableName% - */ + * Copies sourceTableName to another table with name: destinationTableName + * Performs INSERT INTO %destinationTableName% () SELECT %table.column_names% FROM %sourceTableName% + */ template void copy_table(sqlite3* db, const std::string& sourceTableName, @@ -25741,11 +25741,11 @@ namespace sqlite_orm::internal { public: /* - * Iterate over objects of a type mapped as a table, lazily fetched from a result set. - * - * The returned C++ view models a C++ input range and is also a 'borrowed range', - * meaning that iterators obtained from it are not tied to the lifetime of the view instance. - */ + * Iterate over objects of a type mapped as a table, lazily fetched from a result set. + * + * The returned C++ view models a C++ input range and is also a 'borrowed range', + * meaning that iterators obtained from it are not tied to the lifetime of the view instance. + */ template, class... Args> mapped_view iterate(Args&&... args) { this->assert_mapped_type(); @@ -25756,11 +25756,11 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /* - * Iterate over objects of a type mapped as a table, lazily fetched from a result set. - * - * The returned C++ view models a C++ input range and is also a 'borrowed range', - * meaning that iterators obtained from it are not tied to the lifetime of the view instance. - */ + * Iterate over objects of a type mapped as a table, lazily fetched from a result set. + * + * The returned C++ view models a C++ input range and is also a 'borrowed range', + * meaning that iterators obtained from it are not tied to the lifetime of the view instance. + */ template auto iterate(Args&&... args) { return this->iterate(std::forward(args)...); @@ -25768,11 +25768,11 @@ namespace sqlite_orm::internal { #endif /* - * Iterate over a result set of a select statement or a select statement involving a common table expression. - * - * The returned C++ view models a C++ input range and is also a 'borrowed range', - * meaning that iterators obtained from it are not tied to the lifetime of the view instance. - */ + * Iterate over a result set of a select statement or a select statement involving a common table expression. + * + * The returned C++ view models a C++ input range and is also a 'borrowed range', + * meaning that iterators obtained from it are not tied to the lifetime of the view instance. + */ template #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED requires (is_select_expression_v) auto yield(Select expression) -> std::generatoriterate(std::move(expression)).begin())> { @@ -25832,12 +25832,12 @@ namespace sqlite_orm::internal { #endif /** - * Delete from routine. - * O is an object's type. Must be specified explicitly. - * @param args optional conditions: `where`, `join` etc - * @example: storage.remove_all(); - DELETE FROM users - * @example: storage.remove_all(where(in(&User::id, {5, 6, 7}))); - DELETE FROM users WHERE id IN (5, 6, 7) - */ + * Delete from routine. + * O is an object's type. Must be specified explicitly. + * @param args optional conditions: `where`, `join` etc + * @example: storage.remove_all(); - DELETE FROM users + * @example: storage.remove_all(where(in(&User::id, {5, 6, 7}))); - DELETE FROM users WHERE id IN (5, 6, 7) + */ template void remove_all(Args&&... args) { this->assert_mapped_type(); @@ -25853,10 +25853,10 @@ namespace sqlite_orm::internal { #endif /** - * Delete routine. - * O is an object's type. Must be specified explicitly. - * @param ids ids of object to be removed. - */ + * Delete routine. + * O is an object's type. Must be specified explicitly. + * @param ids ids of object to be removed. + */ template void remove(Ids... ids) { this->assert_mapped_type(); @@ -25872,11 +25872,11 @@ namespace sqlite_orm::internal { #endif /** - * Update routine. Sets all non primary key fields where primary key is equal. - * O is an object type. May be not specified explicitly cause it can be deduced by - * compiler from first parameter. - * @param o object to be updated. - */ + * Update routine. Sets all non primary key fields where primary key is equal. + * O is an object type. May be not specified explicitly cause it can be deduced by + * compiler from first parameter. + * @param o object to be updated. + */ template void update(const O& o) { this->assert_mapped_type(); @@ -25910,13 +25910,13 @@ namespace sqlite_orm::internal { public: /** - * SELECT * routine. - * T is an explicitly specified object mapped to a storage or a table alias. - * R is an explicit return type. This type must have `push_back(O &&)` function. Defaults to `std::vector` - * @return All objects of type O stored in database at the moment in `R`. - * @example: storage.get_all>(); - SELECT * FROM users - * @example: storage.get_all>(where(like(&User::name, "N%")), order_by(&User::id)); - SELECT * FROM users WHERE name LIKE 'N%' ORDER BY id - */ + * SELECT * routine. + * T is an explicitly specified object mapped to a storage or a table alias. + * R is an explicit return type. This type must have `push_back(O &&)` function. Defaults to `std::vector` + * @return All objects of type O stored in database at the moment in `R`. + * @example: storage.get_all>(); - SELECT * FROM users + * @example: storage.get_all>(where(like(&User::name, "N%")), order_by(&User::id)); - SELECT * FROM users WHERE name LIKE 'N%' ORDER BY id + */ template>, class... Args> R get_all(Args&&... args) { this->assert_mapped_type>(); @@ -25926,12 +25926,12 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** - * SELECT * routine. - * `mapped` is an explicitly specified table reference or table alias of an object to be extracted. - * `R` is the container return type, which must have a `R::push_back(O&&)` method, and defaults to `std::vector` - * @return All objects stored in database. - * @example: storage.get_all>(); - SELECT sqlite_schema.* FROM sqlite_master AS sqlite_schema - */ + * SELECT * routine. + * `mapped` is an explicitly specified table reference or table alias of an object to be extracted. + * `R` is the container return type, which must have a `R::push_back(O&&)` method, and defaults to `std::vector` + * @return All objects stored in database. + * @example: storage.get_all>(); - SELECT sqlite_schema.* FROM sqlite_master AS sqlite_schema + */ template>, class... Args> @@ -25943,13 +25943,13 @@ namespace sqlite_orm::internal { #endif /** - * SELECT * routine. - * O is an object type to be extracted. Must be specified explicitly. - * R is a container type. std::vector> is default - * @return All objects of type O as std::unique_ptr stored in database at the moment. - * @example: storage.get_all_pointer>>(); - SELECT * FROM users - * @example: storage.get_all_pointer>>(where(length(&User::name) > 6)); - SELECT * FROM users WHERE LENGTH(name) > 6 - */ + * SELECT * routine. + * O is an object type to be extracted. Must be specified explicitly. + * R is a container type. std::vector> is default + * @return All objects of type O as std::unique_ptr stored in database at the moment. + * @example: storage.get_all_pointer>>(); - SELECT * FROM users + * @example: storage.get_all_pointer>>(where(length(&User::name) > 6)); - SELECT * FROM users WHERE LENGTH(name) > 6 + */ template>, class... Args> auto get_all_pointer(Args&&... args) { this->assert_mapped_type(); @@ -25968,13 +25968,13 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED /** - * SELECT * routine. - * O is an object type to be extracted. Must be specified explicitly. - * R is a container type. std::vector> is default - * @return All objects of type O as std::optional stored in database at the moment. - * @example: storage.get_all_optional>>(); - SELECT * FROM users - * @example: storage.get_all_optional>>(where(length(&User::name) > 6)); - SELECT * FROM users WHERE LENGTH(name) > 6 - */ + * SELECT * routine. + * O is an object type to be extracted. Must be specified explicitly. + * R is a container type. std::vector> is default + * @return All objects of type O as std::optional stored in database at the moment. + * @example: storage.get_all_optional>>(); - SELECT * FROM users + * @example: storage.get_all_optional>>(where(length(&User::name) > 6)); - SELECT * FROM users WHERE LENGTH(name) > 6 + */ template>, class... Args> auto get_all_optional(Args&&... conditions) { this->assert_mapped_type(); @@ -25993,13 +25993,13 @@ namespace sqlite_orm::internal { #endif /** - * Select * by id routine. - * throws std::system_error{orm_error_code::not_found} if object not found with given - * id. throws std::system_error with orm_error_category in case of db error. O is an object type to be - * extracted. Must be specified explicitly. - * @return Object of type O where id is equal parameter passed or throws - * `std::system_error{orm_error_code::not_found}` if there is no object with such id. - */ + * Select * by id routine. + * throws std::system_error{orm_error_code::not_found} if object not found with given + * id. throws std::system_error with orm_error_category in case of db error. O is an object type to be + * extracted. Must be specified explicitly. + * @return Object of type O where id is equal parameter passed or throws + * `std::system_error{orm_error_code::not_found}` if there is no object with such id. + */ template O get(Ids... ids) { this->assert_mapped_type(); @@ -26016,9 +26016,9 @@ namespace sqlite_orm::internal { #endif /** - * The same as `get` function but doesn't throw an exception if noting found but returns std::unique_ptr - * with null value. throws std::system_error in case of db error. - */ + * The same as `get` function but doesn't throw an exception if noting found but returns std::unique_ptr + * with null value. throws std::system_error in case of db error. + */ template std::unique_ptr get_pointer(Ids... ids) { this->assert_mapped_type(); @@ -26035,18 +26035,18 @@ namespace sqlite_orm::internal { #endif /** - * A previous version of get_pointer() that returns a shared_ptr - * instead of a unique_ptr. New code should prefer get_pointer() - * unless the data needs to be shared. - * - * @note - * Most scenarios don't need shared ownership of data, so we should prefer - * unique_ptr when possible. It's more efficient, doesn't require atomic - * ops for a reference count (which can cause major slowdowns on - * weakly-ordered platforms like ARM), and can be easily promoted to a - * shared_ptr, exactly like we're doing here. - * (Conversely, you _can't_ go from shared back to unique.) - */ + * A previous version of get_pointer() that returns a shared_ptr + * instead of a unique_ptr. New code should prefer get_pointer() + * unless the data needs to be shared. + * + * @note + * Most scenarios don't need shared ownership of data, so we should prefer + * unique_ptr when possible. It's more efficient, doesn't require atomic + * ops for a reference count (which can cause major slowdowns on + * weakly-ordered platforms like ARM), and can be easily promoted to a + * shared_ptr, exactly like we're doing here. + * (Conversely, you _can't_ go from shared back to unique.) + */ template std::shared_ptr get_no_throw(Ids... ids) { return std::shared_ptr(this->get_pointer(std::forward(ids)...)); @@ -26054,9 +26054,9 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_OPTIONAL_SUPPORTED /** - * The same as `get` function but doesn't throw an exception if noting found but - * returns an empty std::optional. throws std::system_error in case of db error. - */ + * The same as `get` function but doesn't throw an exception if noting found but + * returns an empty std::optional. throws std::system_error in case of db error. + */ template std::optional get_optional(Ids... ids) { this->assert_mapped_type(); @@ -26074,9 +26074,9 @@ namespace sqlite_orm::internal { #endif /** - * SELECT COUNT(*) https://www.sqlite.org/lang_aggfunc.html#count - * @return Number of O object in table. - */ + * SELECT COUNT(*) https://www.sqlite.org/lang_aggfunc.html#count + * @return Number of O object in table. + */ template int count(Args&&... args) { using R = mapped_type_proxy_t; @@ -26097,10 +26097,10 @@ namespace sqlite_orm::internal { #endif /** - * SELECT COUNT(X) https://www.sqlite.org/lang_aggfunc.html#count - * @param m member pointer to class mapped to the storage. - * @return count of `m` values from database. - */ + * SELECT COUNT(X) https://www.sqlite.org/lang_aggfunc.html#count + * @param m member pointer to class mapped to the storage. + * @return count of `m` values from database. + */ template, is_column_pointer>::value, bool> = @@ -26116,10 +26116,10 @@ namespace sqlite_orm::internal { } /** - * AVG(X) query. https://www.sqlite.org/lang_aggfunc.html#avg - * @param m is a class member pointer (the same you passed into make_column). - * @return average value from database. - */ + * AVG(X) query. https://www.sqlite.org/lang_aggfunc.html#avg + * @param m is a class member pointer (the same you passed into make_column). + * @return average value from database. + */ template, is_column_pointer>::value, bool> = @@ -26142,10 +26142,10 @@ namespace sqlite_orm::internal { } /** - * GROUP_CONCAT(X) query. https://www.sqlite.org/lang_aggfunc.html#groupconcat - * @param m is a class member pointer (the same you passed into make_column). - * @return group_concat query result. - */ + * GROUP_CONCAT(X) query. https://www.sqlite.org/lang_aggfunc.html#groupconcat + * @param m is a class member pointer (the same you passed into make_column). + * @return group_concat query result. + */ template, @@ -26157,10 +26157,10 @@ namespace sqlite_orm::internal { } /** - * GROUP_CONCAT(X, Y) query. https://www.sqlite.org/lang_aggfunc.html#groupconcat - * @param m is a class member pointer (the same you passed into make_column). - * @return group_concat query result. - */ + * GROUP_CONCAT(X, Y) query. https://www.sqlite.org/lang_aggfunc.html#groupconcat + * @param m is a class member pointer (the same you passed into make_column). + * @return group_concat query result. + */ template, is_column_pointer>::value, bool> = @@ -26186,10 +26186,10 @@ namespace sqlite_orm::internal { } /** - * MAX(x) query. - * @param m is a class member pointer (the same you passed into make_column). - * @return std::unique_ptr with max value or null if sqlite engine returned null. - */ + * MAX(x) query. + * @param m is a class member pointer (the same you passed into make_column). + * @return std::unique_ptr with max value or null if sqlite engine returned null. + */ template, @@ -26206,10 +26206,10 @@ namespace sqlite_orm::internal { } /** - * MIN(x) query. - * @param m is a class member pointer (the same you passed into make_column). - * @return std::unique_ptr with min value or null if sqlite engine returned null. - */ + * MIN(x) query. + * @param m is a class member pointer (the same you passed into make_column). + * @return std::unique_ptr with min value or null if sqlite engine returned null. + */ template, @@ -26226,10 +26226,10 @@ namespace sqlite_orm::internal { } /** - * SUM(x) query. - * @param m is a class member pointer (the same you passed into make_column). - * @return std::unique_ptr with sum value or null if sqlite engine returned null. - */ + * SUM(x) query. + * @param m is a class member pointer (the same you passed into make_column). + * @return std::unique_ptr with sum value or null if sqlite engine returned null. + */ template, @@ -26251,11 +26251,11 @@ namespace sqlite_orm::internal { } /** - * TOTAL(x) query. - * @param m is a class member pointer (the same you passed into make_column). - * @return total value (the same as SUM but not nullable. More details here - * https://www.sqlite.org/lang_aggfunc.html) - */ + * TOTAL(x) query. + * @param m is a class member pointer (the same you passed into make_column). + * @return total value (the same as SUM but not nullable. More details here + * https://www.sqlite.org/lang_aggfunc.html) + */ template, is_column_pointer>::value, bool> = @@ -26271,10 +26271,10 @@ namespace sqlite_orm::internal { } /** - * Select a single column into std::vector or multiple columns into std::vector>. - * For a single column use `auto rows = storage.select(&User::id, where(...)); - * For multicolumns use `auto rows = storage.select(columns(&User::id, &User::name), where(...)); - */ + * Select a single column into std::vector or multiple columns into std::vector>. + * For a single column use `auto rows = storage.select(&User::id, where(...)); + * For multicolumns use `auto rows = storage.select(columns(&User::id, &User::name), where(...)); + */ template auto select(T m, Args... args) { static_assert(!is_compound_operator_v || sizeof...(Args) == 0, @@ -26285,8 +26285,8 @@ namespace sqlite_orm::internal { #if (SQLITE_VERSION_NUMBER >= 3008003) && defined(SQLITE_ORM_WITH_CTE) /** - * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. - */ + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ template auto with(CTE cte, E expression) { auto statement = this->prepare(sqlite_orm::with(std::move(cte), std::move(expression))); @@ -26294,8 +26294,8 @@ namespace sqlite_orm::internal { } /** - * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. - */ + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ template auto with(common_table_expressions cte, E expression) { auto statement = this->prepare(sqlite_orm::with(std::move(cte), std::move(expression))); @@ -26303,8 +26303,8 @@ namespace sqlite_orm::internal { } /** - * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. - */ + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ template auto with_recursive(CTE cte, E expression) { auto statement = this->prepare(sqlite_orm::with_recursive(std::move(cte), std::move(expression))); @@ -26312,8 +26312,8 @@ namespace sqlite_orm::internal { } /** - * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. - */ + * Using a CTE, select a single column into std::vector or multiple columns into std::vector>. + */ template auto with_recursive(common_table_expressions cte, E expression) { auto statement = this->prepare(sqlite_orm::with_recursive(std::move(cte), std::move(expression))); @@ -26343,9 +26343,9 @@ namespace sqlite_orm::internal { } /** - * Returns a string representation of object of a class mapped to the storage. - * Type of string has json-like style. - */ + * Returns a string representation of object of a class mapped to the storage. + * Type of string has json-like style. + */ template = true> std::string dump(const O& object) const { auto& table = this->get_table(); @@ -26363,11 +26363,11 @@ namespace sqlite_orm::internal { } /** - * This is REPLACE (INSERT OR REPLACE) function. - * Also if you need to insert value with knows id you should - * also you this function instead of insert cause inserts ignores - * id and creates own one. - */ + * This is REPLACE (INSERT OR REPLACE) function. + * Also if you need to insert value with knows id you should + * also you this function instead of insert cause inserts ignores + * id and creates own one. + */ template void replace(const O& o) { this->assert_mapped_type(); @@ -26401,13 +26401,13 @@ namespace sqlite_orm::internal { } /** - * Insert routine with explicitly specified columns. - * - * @return The ID of the last inserted record for a rowid table, otherwise a meaningless value. - * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. - * Attention: While SQLite returns a 64-bit integer as rowid, this function returns an `int` that most likely has less precision. - * If you need the full 64-bit rowid value, use `storage_t<>::execute()` instead, or call `storage_t<>::last_insert_rowid()` after inserting. - */ + * Insert routine with explicitly specified columns. + * + * @return The ID of the last inserted record for a rowid table, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. + * Attention: While SQLite returns a 64-bit integer as rowid, this function returns an `int` that most likely has less precision. + * If you need the full 64-bit rowid value, use `storage_t<>::execute()` instead, or call `storage_t<>::last_insert_rowid()` after inserting. + */ template int insert(const O& o, columns_t cols) { static_assert(cols.count > 0, "Use insert or replace with 1 argument instead"); @@ -26417,22 +26417,22 @@ namespace sqlite_orm::internal { } /** - * Ordinary insert routine. - * - * - For objects mapped to a rowid table with a single primary key: - * Inserts a record with all fields of a mapped object except the primary key column. - * The primary key column must be implicitly insertable. - * The 'ID' of the specified object is irrelevant as it is implicitly inserted. - * - For objects mapped to a rowid table with a composite primary key or no primary key: - * Inserts a record with all fields of a mapped object except primary key columns having a default value. - * - For objects mapped to a table without rowid: - * Inserts a record with all fields of a mapped object except primary key columns having a default value. - * - * @return The ID of the last inserted record for a rowid table, otherwise a meaningless value. - * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. - * Attention: While SQLite returns a 64-bit integer as rowid, this function returns an `int` that most likely has less precision. - * If you need the full 64-bit rowid value, use `storage_t<>::execute()` instead, or call `storage_t<>::last_insert_rowid()` after inserting. - */ + * Ordinary insert routine. + * + * - For objects mapped to a rowid table with a single primary key: + * Inserts a record with all fields of a mapped object except the primary key column. + * The primary key column must be implicitly insertable. + * The 'ID' of the specified object is irrelevant as it is implicitly inserted. + * - For objects mapped to a rowid table with a composite primary key or no primary key: + * Inserts a record with all fields of a mapped object except primary key columns having a default value. + * - For objects mapped to a table without rowid: + * Inserts a record with all fields of a mapped object except primary key columns having a default value. + * + * @return The ID of the last inserted record for a rowid table, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. + * Attention: While SQLite returns a 64-bit integer as rowid, this function returns an `int` that most likely has less precision. + * If you need the full 64-bit rowid value, use `storage_t<>::execute()` instead, or call `storage_t<>::last_insert_rowid()` after inserting. + */ template int insert(const O& o) { this->assert_mapped_type(); @@ -26442,39 +26442,39 @@ namespace sqlite_orm::internal { } /** - * Raw insert routine. Use this if `insert` with object does not fit you. This insert is designed to be able - * to call any type of `INSERT` query with no limitations. - * @example - * ```sql - * INSERT INTO users (id, name) VALUES(5, 'Little Mix') - * ``` - * will be - * ```c++ - * storage.insert(into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))); - * ``` - * One more example: - * ```sql - * INSERT INTO singers (name) VALUES ('Sofia Reyes')('Kungs') - * ``` - * will be - * ```c++ - * storage.insert(into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))); - * ``` - * One can use `default_values` to add `DEFAULT VALUES` modifier: - * ```sql - * INSERT INTO users DEFAULT VALUES - * ``` - * will be - * ```c++ - * storage.insert(into(), default_values()); - * ``` - * Also one can use `INSERT OR ABORT`/`INSERT OR FAIL`/`INSERT OR IGNORE`/`INSERT OR REPLACE`/`INSERT ROLLBACK`: - * ```c++ - * storage.insert(or_ignore(), into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))); - * storage.insert(or_rollback(), into(), default_values()); - * storage.insert(or_abort(), into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))); - * ``` - */ + * Raw insert routine. Use this if `insert` with object does not fit you. This insert is designed to be able + * to call any type of `INSERT` query with no limitations. + * @example + * ```sql + * INSERT INTO users (id, name) VALUES(5, 'Little Mix') + * ``` + * will be + * ```c++ + * storage.insert(into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))); + * ``` + * One more example: + * ```sql + * INSERT INTO singers (name) VALUES ('Sofia Reyes')('Kungs') + * ``` + * will be + * ```c++ + * storage.insert(into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))); + * ``` + * One can use `default_values` to add `DEFAULT VALUES` modifier: + * ```sql + * INSERT INTO users DEFAULT VALUES + * ``` + * will be + * ```c++ + * storage.insert(into(), default_values()); + * ``` + * Also one can use `INSERT OR ABORT`/`INSERT OR FAIL`/`INSERT OR IGNORE`/`INSERT OR REPLACE`/`INSERT ROLLBACK`: + * ```c++ + * storage.insert(or_ignore(), into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))); + * storage.insert(or_rollback(), into(), default_values()); + * storage.insert(or_abort(), into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))); + * ``` + */ template void insert(Args... args) { auto statement = this->prepare(sqlite_orm::insert(std::forward(args)...)); @@ -26482,33 +26482,33 @@ namespace sqlite_orm::internal { } /** - * Raw replace statement creation routine. Use this if `replace` with object does not fit you. This replace is designed to be able - * to call any type of `REPLACE` query with no limitations. Actually this is the same query as raw insert except `OR...` option existance. - * @example - * ```sql - * REPLACE INTO users (id, name) VALUES(5, 'Little Mix') - * ``` - * will be - * ```c++ - * storage.prepare(replace(into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix")))); - * ``` - * One more example: - * ```sql - * REPLACE INTO singers (name) VALUES ('Sofia Reyes')('Kungs') - * ``` - * will be - * ```c++ - * storage.prepare(replace(into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs")))); - * ``` - * One can use `default_values` to add `DEFAULT VALUES` modifier: - * ```sql - * REPLACE INTO users DEFAULT VALUES - * ``` - * will be - * ```c++ - * storage.prepare(replace(into(), default_values())); - * ``` - */ + * Raw replace statement creation routine. Use this if `replace` with object does not fit you. This replace is designed to be able + * to call any type of `REPLACE` query with no limitations. Actually this is the same query as raw insert except `OR...` option existance. + * @example + * ```sql + * REPLACE INTO users (id, name) VALUES(5, 'Little Mix') + * ``` + * will be + * ```c++ + * storage.prepare(replace(into, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix")))); + * ``` + * One more example: + * ```sql + * REPLACE INTO singers (name) VALUES ('Sofia Reyes')('Kungs') + * ``` + * will be + * ```c++ + * storage.prepare(replace(into(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs")))); + * ``` + * One can use `default_values` to add `DEFAULT VALUES` modifier: + * ```sql + * REPLACE INTO users DEFAULT VALUES + * ``` + * will be + * ```c++ + * storage.prepare(replace(into(), default_values())); + * ``` + */ template void replace(Args... args) { auto statement = this->prepare(sqlite_orm::replace(std::forward(args)...)); @@ -26541,9 +26541,9 @@ namespace sqlite_orm::internal { } /** - * Change table name inside storage's schema info. This function does not - * affect database - */ + * Change table name inside storage's schema info. This function does not + * affect database + */ template void rename_table(std::string name) { this->assert_mapped_type(); @@ -26554,9 +26554,9 @@ namespace sqlite_orm::internal { using storage_base::rename_table; /** - * Get table's name stored in storage's schema info. This function does not call - * any SQLite queries - */ + * Get table's name stored in storage's schema info. This function does not call + * any SQLite queries + */ template const std::string& tablename() const { this->assert_mapped_type(); @@ -26857,8 +26857,8 @@ namespace sqlite_orm::internal { public: /** - * This is a cute function used to replace migration up/down functionality. - * It performs check storage schema with actual db schema and: + * This is a cute function used to replace migration up/down functionality. + * It performs check storage schema with actual db schema and: * - if there are excess tables exist in db they are ignored (not dropped) * - every table from storage is compared with it's db analog and * - if table doesn't exist it is being created @@ -26866,21 +26866,21 @@ namespace sqlite_orm::internal { * - if there are columns in db that do not exist in storage (excess) table will be dropped and recreated * - if there are columns in storage that do not exist in db they will be added using `ALTER TABLE ... ADD COLUMN ...' command * - if there is any column existing in both db and storage but differs by any of - * properties/constraints (pk, notnull, dflt_value) table will be dropped and recreated. Be aware that - * `sync_schema` doesn't guarantee that data will not be dropped. It guarantees only that it will make db - * schema the same as you specified in `make_storage` function call. A good point is that if you have no db - * file at all it will be created and all tables also will be created with exact tables and columns you - * specified in `make_storage`, `make_table` and `make_column` calls. The best practice is to call this - * function right after storage creation. - * @param preserve affects function's behaviour in case it is needed to remove a column. If it is `false` - * so table will be dropped if there is column to remove if SQLite version is < 3.35.0 and remove column if SQLite version >= 3.35.0, - * if `true` - table is being copied into another table, dropped and copied table is renamed with source table name. - * Warning: sync_schema doesn't check foreign keys cause it is unable to do so in sqlite3. If you know how to get foreign key info please - * submit an issue https://github.com/fnc12/sqlite_orm/issues - * @return std::map with std::string key equal table name and `sync_schema_result` as value. - * `sync_schema_result` is a enum value that stores table state after syncing a schema. `sync_schema_result` - * can be printed out on std::ostream with `operator<<`. - */ + * properties/constraints (pk, notnull, dflt_value) table will be dropped and recreated. Be aware that + * `sync_schema` doesn't guarantee that data will not be dropped. It guarantees only that it will make db + * schema the same as you specified in `make_storage` function call. A good point is that if you have no db + * file at all it will be created and all tables also will be created with exact tables and columns you + * specified in `make_storage`, `make_table` and `make_column` calls. The best practice is to call this + * function right after storage creation. + * @param preserve affects function's behaviour in case it is needed to remove a column. If it is `false` + * so table will be dropped if there is column to remove if SQLite version is < 3.35.0 and remove column if SQLite version >= 3.35.0, + * if `true` - table is being copied into another table, dropped and copied table is renamed with source table name. + * Warning: sync_schema doesn't check foreign keys cause it is unable to do so in sqlite3. If you know how to get foreign key info please + * submit an issue https://github.com/fnc12/sqlite_orm/issues + * @return std::map with std::string key equal table name and `sync_schema_result` as value. + * `sync_schema_result` is a enum value that stores table state after syncing a schema. `sync_schema_result` + * can be printed out on std::ostream with `operator<<`. + */ std::map sync_schema(bool preserve = false) { auto conRef = this->get_connection(); std::map result; @@ -26892,10 +26892,10 @@ namespace sqlite_orm::internal { } /** - * This function returns the same map that `sync_schema` returns but it - * doesn't perform `sync_schema` actually - just simulates it in case you want to know - * what will happen if you sync your schema. - */ + * This function returns the same map that `sync_schema` returns but it + * doesn't perform `sync_schema` actually - just simulates it in case you want to know + * what will happen if you sync your schema. + */ std::map sync_schema_simulate(bool preserve = false) { auto conRef = this->get_connection(); std::map result; @@ -27022,9 +27022,9 @@ namespace sqlite_orm::internal { } /** - * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. - * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. - */ + * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. + */ template int64 execute(const prepared_statement_t>& statement) { using object_type = statement_object_type_t; @@ -27082,9 +27082,9 @@ namespace sqlite_orm::internal { } /** - * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. - * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. - */ + * @return The ID of the last inserted record for a table with rowid, otherwise a meaningless value. + * Attention: `sqlite3_last_insert_rowid()` is used to retrieve the last inserted ID, therefore the ID is only useful in single-threaded contexts. + */ template, is_insert_range>::value, bool> = true> int64 execute(const prepared_statement_t& statement) { using object_type = statement_object_type_t; diff --git a/tests/tests.cpp b/tests/tests.cpp index 8940998c1..52a755873 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -7,6 +7,14 @@ #include // std::remove #include // std::strncmp +#ifdef _DEBUG +#pragma comment(lib, "Catch2d.Lib") +//#pragma comment(lib, "manual-link/Catch2Maind.Lib") +#else +#pragma comment(lib, "Catch2.Lib") +//#pragma comment(lib, "manual-link/Catch2Main.Lib") +#endif + using namespace sqlite_orm; TEST_CASE("Limits") { From f84a9d9c425ae4375df6101ab5c6354e0daff53b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 26 Apr 2026 21:33:40 +0200 Subject: [PATCH 34/49] Updated TODO.md --- TODO.md | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO.md b/TODO.md index 44da03cb2..1235976c6 100644 --- a/TODO.md +++ b/TODO.md @@ -6,7 +6,6 @@ * rest of core functions(https://sqlite.org/lang_corefunc.html) * `ATTACH` * blob incremental I/O https://sqlite.org/c3ref/blob_open.html -* CREATE VIEW and other view operations https://sqlite.org/lang_createview.html * query static check for correct order (e.g. `GROUP BY` after `WHERE`) * `SAVEPOINT` https://www.sqlite.org/lang_savepoint.html * add `static_assert` in crud `get*` functions in case user passes `where_t` instead of id to make compilation error more clear (example https://github.com/fnc12/sqlite_orm/issues/485) From 87b89130df84c0f3eb7126bb80f27445e000ec1e Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 28 Apr 2026 22:12:47 +0300 Subject: [PATCH 35/49] Removed fallback to Boost.PFR for query views sqlite_orm's policy is to provide an API without external dependencies (even if implicit). --- dev/functional/config.h | 6 +- dev/functional/meta_util.h | 2 +- dev/functional/pfr_config.h | 9 -- dev/object_from_column_builder.h | 17 --- dev/schema/view.h | 159 +------------------------ include/sqlite_orm/sqlite_orm.h | 193 ++----------------------------- tests/CMakeLists.txt | 2 - 7 files changed, 16 insertions(+), 372 deletions(-) delete mode 100644 dev/functional/pfr_config.h diff --git a/dev/functional/config.h b/dev/functional/config.h index 12a78ca26..b88da1910 100644 --- a/dev/functional/config.h +++ b/dev/functional/config.h @@ -4,7 +4,6 @@ #include "platform_definitions.h" // pull in SQLite3 configuration early, such that version and feature macros are globally available in sqlite_orm #include "sqlite3_config.h" -#include "pfr_config.h" #ifdef BUILD_SQLITE_ORM_MODULE #define SQLITE_ORM_EXPORT export @@ -87,10 +86,7 @@ #define SQLITE_ORM_WITH_CTE -// note: PFR depends on `SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED` for field name determination -#if (defined(SQLITE_ORM_CONSTEVAL_SUPPORTED) && defined(SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED)) && \ - (defined(SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED)) && (__cpp_lib_byte >= 201603L) && \ - (defined(SQLITE_ORM_REFLECTION_SUPPORTED) || BOOST_PFR_ENABLED == 1) +#if defined(SQLITE_ORM_REFLECTION_SUPPORTED) #define SQLITE_ORM_WITH_VIEW #endif diff --git a/dev/functional/meta_util.h b/dev/functional/meta_util.h index 0821d1727..73116c189 100644 --- a/dev/functional/meta_util.h +++ b/dev/functional/meta_util.h @@ -3,7 +3,7 @@ #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_REFLECTION_SUPPORTED #include // std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of -#include // std::tuple, std::get +#include // std::tuple #include // std::index_sequence, std::make_index_sequence #endif #endif diff --git a/dev/functional/pfr_config.h b/dev/functional/pfr_config.h deleted file mode 100644 index 835af15ef..000000000 --- a/dev/functional/pfr_config.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#if __has_include() -#define SQLITE_ORM_HAS_BOOST_PFR -#endif - -#if defined(SQLITE_ORM_HAS_BOOST_PFR) && (!defined(BOOST_PFR_ENABLED) || (BOOST_PFR_ENABLED == 1)) -#include -#endif diff --git a/dev/object_from_column_builder.h b/dev/object_from_column_builder.h index c2bcd665d..9b73e1892 100644 --- a/dev/object_from_column_builder.h +++ b/dev/object_from_column_builder.h @@ -4,9 +4,6 @@ #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include // std::is_member_object_pointer #include // std::move -#if defined(SQLITE_ORM_WITH_VIEW) && (BOOST_PFR_ENABLED == 1) -#include // std::byte -#endif #endif #include "functional/gsl.h" @@ -45,20 +42,6 @@ namespace sqlite_orm::internal { (object.*column.setter)(std::move(value)); }; } - -#if defined(SQLITE_ORM_WITH_VIEW) && (BOOST_PFR_ENABLED == 1) - template - requires (is_column_pointer_v) - void operator()(const column_field& column) { - using field_type = field_type_t>; - const auto rowExtractor = row_value_extractor(); - auto value = rowExtractor.extract(this->stmt, ++this->columnIndex); - // calculate absolute address of member from relative address - const std::byte* fieldAddress = (std::byte*)(uintptr_t(&object) + size_t(column.member_pointer.field)); - field_type* field = (field_type*)fieldAddress; - *field = std::move(value); - } -#endif }; /** diff --git a/dev/schema/view.h b/dev/schema/view.h index d94cb37fb..5db8d0ad9 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -4,15 +4,6 @@ #ifdef SQLITE_ORM_WITH_VIEW #include // std::remove_cvref #include // std::forward, std::move, std::index_sequence, std::make_index_sequence -#include // std::byte -#endif -#endif - -#ifdef SQLITE_ORM_WITH_VIEW -#ifndef SQLITE_ORM_REFLECTION_SUPPORTED -#ifdef SQLITE_ORM_HAS_BOOST_PFR -#include -#endif #endif #endif @@ -23,47 +14,6 @@ #include "column.h" #include "table_base.h" -#ifdef SQLITE_ORM_WITH_VIEW -#ifdef SQLITE_ORM_REFLECTION_SUPPORTED -#elif BOOST_PFR_ENABLED == 1 -namespace boost::pfr { - namespace detail { - namespace sequence_tuple { - template - consteval auto get_nth_base(const base_from_member& t) noexcept { - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - return t; - } - - template - constexpr auto* get_nth_relative_address() noexcept { - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - static_assert(N < Tpl::size_v); - using nth_type = decltype(get_nth_base(std::declval())); - using field_type = decltype(nth_type::value); - - return (field_type*)(std::byte*) - // offsetof - the official one cannot be used because of some implementations using the compiler intrinsic builtin - ((std::size_t)&reinterpret_cast((((Tpl*)0)->nth_type::value))); - } - } - } - - template - constexpr auto* get_relative_address() { - static_assert(sizeof(O) == sizeof(TS), - "====================> Boost.PFR: Member sequence does not indicate correct size for struct " - "type! Maybe the user-provided type is not a SimpleAggregate?"); - static_assert( - alignof(O) == alignof(TS), - "====================> Boost.PFR: Member sequence does not indicate correct alignment for struct type!"); - - return detail::sequence_tuple::get_nth_relative_address(); - } -} -#endif -#endif - namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_VIEW /** @@ -93,7 +43,7 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_VIEW #ifdef SQLITE_ORM_REFLECTION_SUPPORTED namespace sqlite_orm::internal { - template + template auto make_view(std::string name, std::index_sequence, Select select) { constexpr auto memberNames = extract_member_names(); constexpr auto memberPointers = extract_member_pointers(); @@ -110,107 +60,12 @@ namespace sqlite_orm::internal { } SQLITE_ORM_EXPORT namespace sqlite_orm { - template - requires (internal::is_select_expression_v) { - select.highest_level = true; - } - return internal::make_view(std::move(name), - std::make_index_sequence()>{}, - std::move(select)); - } -} -#elif BOOST_PFR_ENABLED == 1 -namespace sqlite_orm::internal { /** - * Factory function for a column definition from a relative pointer to an object of the object to be mapped. - */ - template - requires (internal::is_column_pointer_v) - internal::column_t - make_column(std::string name, C relativeField, Op... constraints) { - static_assert(polyfill::conjunction_v...>, "Incorrect constraints pack"); - - // attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`, - // as this will lead to UB with Clang on MinGW! - return {std::move(name), relativeField, {}, std::tuple{std::move(constraints)...}}; - } - - /* - * A column field carrying a relative address to a member of an object. + * Factory function for a view definition. * - * Internal note: According to my tests msvc or compilers in general have a hard time to use pointer-to-members at compile-time. - * That's why we use a relative address. + * The mapped object type is explicitly specified, columns and their names are deferred from the object type. + * The object type must be an aggregate. */ - template - struct column_field, empty_setter> { - using member_pointer_t = F O::*; - using setter_type = empty_setter; - using object_type = O; - using field_type = F; - - /** - * Relative pointer to member (offset) used to read and write a field value. - */ - const column_pointer member_pointer; - - SQLITE_ORM_NOUNIQUEADDRESS - const empty_setter setter; - - /** - * Simplified interface for `NOT NULL` constraint - */ - constexpr bool is_not_null() const { - return !type_is_nullable::value; - } - }; - - template - bool compare_fields(F O::* m, const column_pointer& relative) { - constexpr O* object = nullptr; - return &(object->*m) == relative.field; - } - - template - bool compare_fields(const column_pointer& relative, F O::* m) { - constexpr O* object = nullptr; - return relative.field == &(object->*m); - } - - template - auto make_view(std::string name, std::index_sequence, Select select) { - namespace pfr = boost::pfr; - namespace pfrd = pfr::detail; - namespace pfrs = pfrd::sequence_tuple; - -#if __cpp_lib_is_aggregate >= 201703L - static_assert(std::is_aggregate_v); -#endif - - using PfrTpl = decltype(pfrd::tie_as_tuple(pfrd::fake_object())); - // object's member types as a tuple - using TS = pfrs::tuple::type>...>; - - using view_type = query_view( - std::string(pfr::get_name()), - column_pointer())>{ - pfr::get_relative_address()}))...>; - - return view_type{ - std::move(name), - std::tuple{internal::make_column<>(std::string(pfr::get_name()), - column_pointer())>{ - pfr::get_relative_address()})...}, - std::move(select)}; - } -} - -SQLITE_ORM_EXPORT namespace sqlite_orm { template requires (internal::is_select_expression_v) - auto make_view(std::string name, Select select) { - using namespace ::sqlite_orm::internal; - - if constexpr (is_select_v) auto make_view(std::string name, Select select) { @@ -14023,13 +13865,10 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { select.highest_level = true; } return internal::make_view(std::move(name), - std::make_index_sequence>{}, + std::make_index_sequence()>{}, std::move(select)); } -} -#endif -SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Factory function for a view definition. @@ -14044,6 +13883,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #endif } #endif +#endif // #include "storage_lookup.h" @@ -14929,9 +14769,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include // std::is_member_object_pointer #include // std::move -#if defined(SQLITE_ORM_WITH_VIEW) && (BOOST_PFR_ENABLED == 1) -#include // std::byte -#endif #endif // #include "functional/gsl.h" @@ -14976,20 +14813,6 @@ namespace sqlite_orm::internal { (object.*column.setter)(std::move(value)); }; } - -#if defined(SQLITE_ORM_WITH_VIEW) && (BOOST_PFR_ENABLED == 1) - template - requires (is_column_pointer_v) - void operator()(const column_field& column) { - using field_type = field_type_t>; - const auto rowExtractor = row_value_extractor(); - auto value = rowExtractor.extract(this->stmt, ++this->columnIndex); - // calculate absolute address of member from relative address - const std::byte* fieldAddress = (std::byte*)(uintptr_t(&object) + size_t(column.member_pointer.field)); - field_type* field = (field_type*)fieldAddress; - *field = std::move(value); - } -#endif }; /** diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4f5933ca1..ee83aa6e3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,8 +32,6 @@ if(CMAKE_CXX_STANDARD GREATER_EQUAL 23 AND CMAKE_VERSION VERSION_GREATER_EQUAL 3 message(STATUS "SQLITE_ORM_OMITS_CODECVT is enabled") target_compile_definitions(module_tests PRIVATE SQLITE_ORM_OMITS_CODECVT=1) endif() - # Explicitly disable boost-pfr for now until it supports C++ named modules with msvc - target_compile_definitions(module_tests PRIVATE BOOST_PFR_ENABLED=0) target_precompile_headers(module_tests PRIVATE ) From 7ce965ab491332aa5a8995c2e49cd3811b2641a5 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Tue, 28 Apr 2026 22:15:11 +0300 Subject: [PATCH 36/49] Removed code used in local test environment --- tests/tests.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/tests.cpp b/tests/tests.cpp index 52a755873..8940998c1 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -7,14 +7,6 @@ #include // std::remove #include // std::strncmp -#ifdef _DEBUG -#pragma comment(lib, "Catch2d.Lib") -//#pragma comment(lib, "manual-link/Catch2Maind.Lib") -#else -#pragma comment(lib, "Catch2.Lib") -//#pragma comment(lib, "manual-link/Catch2Main.Lib") -#endif - using namespace sqlite_orm; TEST_CASE("Limits") { From 2c1121f4f0309338f6332380f8ba536abaade8b5 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 1 May 2026 17:27:30 +0300 Subject: [PATCH 37/49] Address a few things popping up when compiling with GCC --- examples/CMakeLists.txt | 6 ++++++ examples/rtree.cpp | 2 +- tests/CMakeLists.txt | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f8b9dd985..303dcb375 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -16,6 +16,12 @@ foreach(file ${files}) target_link_libraries(${file_basename} PRIVATE sqlite_orm) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(${file_basename} PUBLIC + # disabled due to examples potentially containing deprecated features + -Wno-deprecated-declarations) + endif() + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 16) target_compile_options(${file_basename} PUBLIC -freflection) diff --git a/examples/rtree.cpp b/examples/rtree.cpp index b0eb60a2e..b2047f04c 100644 --- a/examples/rtree.cpp +++ b/examples/rtree.cpp @@ -117,7 +117,7 @@ void nearby_restaurants() { std::string name; std::string cuisine; }; - constexpr orm_table_reference auto restaurant = c(); + static /*gcc*/ constexpr orm_table_reference auto restaurant = c(); struct NearbyRestaurant { int64 id = 0; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ee83aa6e3..8b599becd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -83,6 +83,8 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") target_compile_options(unit_tests PUBLIC # disabled due to unit tests testing deprecated features -Wno-deprecated-declarations + # disabled due to unit tests testing overaligned structs + -Winterference-size # perfectly valid to have missing field initializers in library's test code -Wno-missing-field-initializers # suppress warnings about unused variables in test code From f80f0de9f0a5c816ac743f953cc5e8bcd4676472 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 1 May 2026 17:34:21 +0300 Subject: [PATCH 38/49] Supply query view name via C++ annotation --- dev/functional/meta_util.h | 63 +++-- dev/functional/mpl.h | 6 + dev/schema/dbo_name.h | 84 +++++++ dev/schema/view.h | 49 ++-- dev/tuple_helper/tuple_traits.h | 10 + examples/view.cpp | 17 +- include/sqlite_orm/sqlite_orm.h | 218 +++++++++++++++--- tests/logger_tests.cpp | 4 +- tests/schema/view_tests.cpp | 36 ++- .../schema/view.cpp | 4 +- tests/static_tests/view_static_tests.cpp | 6 +- tests/storage_tests.cpp | 15 +- tests/view_tests.cpp | 13 +- 13 files changed, 419 insertions(+), 106 deletions(-) create mode 100644 dev/schema/dbo_name.h diff --git a/dev/functional/meta_util.h b/dev/functional/meta_util.h index 73116c189..fde2a0982 100644 --- a/dev/functional/meta_util.h +++ b/dev/functional/meta_util.h @@ -2,7 +2,8 @@ #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_REFLECTION_SUPPORTED -#include // std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of +#include // std::array +#include // std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of, std::meta::annotations_of #include // std::tuple #include // std::index_sequence, std::make_index_sequence #endif @@ -10,31 +11,65 @@ #ifdef SQLITE_ORM_REFLECTION_SUPPORTED namespace sqlite_orm::internal { + /** + * Reflects the non-static data members of `T` and returns them as a fixed-size array + * of `std::meta::info` reflections. + */ template - consteval auto extract_member_names() { + consteval auto extract_members() { constexpr auto ctx = std::meta::access_context::current(); constexpr size_t N = nonstatic_data_members_of(^^T, ctx).size(); - auto members = nonstatic_data_members_of(^^T, ctx); - return [&members](std::index_sequence) consteval { - return std::tuple{std::meta::identifier_of(members[I])...}; + return [&ctx](std::index_sequence) consteval { + return std::array{nonstatic_data_members_of(^^T, ctx)[I]...}; }(std::make_index_sequence{}); } + /** + * Returns the identifier of `T`. + */ template - consteval auto extract_member_pointers() { - constexpr auto ctx = std::meta::access_context::current(); - constexpr size_t N = nonstatic_data_members_of(^^T, ctx).size(); + consteval auto extract_type_identifier() { + return std::meta::identifier_of(^^T); + } - return [&ctx](std::index_sequence) consteval { - return std::tuple{&[:nonstatic_data_members_of(^^T, ctx)[I]:]...}; - }(std::make_index_sequence{}); + /** + * Splices a non-static data member reflection into a member-pointer expression. + * Encapsulated here so the splice operator does not leak into consumer headers. + */ + template + consteval auto splice_member_pointer() { + return &[:member:]; + } + + /** + * Splices a reflection's annotations into a tuple of values. The reflection may be + * a type or a non-static data member. + * Encapsulated here so the splice operator does not leak into consumer headers. + * + * Two P3394 details inform this implementation: + * - Annotation reflections returned by `annotations_of` are not directly spliceable; + * they must first be routed through `std::meta::constant_of`, which returns a + * splice-able constant reflection. + * - `std::meta::annotations_of` returns a `std::vector`, whose heap + * allocation is transient under C++20 constexpr rules and cannot be bound to a + * `constexpr` variable. The size and per-index lookups therefore re-call + * `annotations_of` inline so each transient vector dies within its own constant + * expression. + */ + template + consteval auto splice_annotations() { + return [](std::index_sequence) consteval { + return std::tuple{[:std::meta::constant_of(std::meta::annotations_of(refl)[I]):]...}; + }(std::make_index_sequence{}); } + /** + * Returns the class-scope annotations of `T` as a tuple. + */ template - consteval auto count_members() { - constexpr auto ctx = std::meta::access_context::current(); - return nonstatic_data_members_of(^^T, ctx).size(); + consteval auto extract_type_annotations() { + return splice_annotations<^^T>(); } } #endif diff --git a/dev/functional/mpl.h b/dev/functional/mpl.h index 3d6665d05..f53a2c076 100644 --- a/dev/functional/mpl.h +++ b/dev/functional/mpl.h @@ -471,6 +471,12 @@ namespace sqlite_orm::internal { template class Op> using check_if_lacks = mpl::not_>; + /* + * Quoted metafunction that finds the index of the element having the specified trait in a tuple. + */ + template class TraitFn> + using finds_if_has = mpl::finds>; + /* * Quoted metafunction that finds the index of the given type in a tuple. */ diff --git a/dev/schema/dbo_name.h b/dev/schema/dbo_name.h new file mode 100644 index 000000000..5d21fc330 --- /dev/null +++ b/dev/schema/dbo_name.h @@ -0,0 +1,84 @@ +#pragma once + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED +#include // std::string_view +#include // std::tuple +#include // std::bool_constant +#include // std::forward +#endif +#endif + +#include "../functional/cstring_literal.h" +#include "../functional/meta_util.h" +#include "../functional/mpl.h" +#include "../tuple_helper/tuple_filter.h" +#include "../tuple_helper/tuple_traits.h" + +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED +namespace sqlite_orm::internal { + /** + * Class-scope annotation that overrides the database object name (table or view). + * When absent, the name falls back to `std::meta::identifier_of(^^T)`. + * + * The string is embedded in the type's bytes via `cstring_literal` rather than + * carried by pointer + size: pointers to string literals are not accepted as + * annotation values by current reflection implementations (the underlying object + * has no linkage), so a self-contained fixed-size byte array is required. + */ + template + struct dbo_name_t : cstring_literal { + constexpr dbo_name_t(const char (&cstr)[N]) : cstring_literal{cstr} {} + + constexpr auto name() const noexcept { + return this->cstr; + } + }; + + template + constexpr bool is_dbo_name_v = false; + + template + constexpr bool is_dbo_name_v> = true; + + template + using is_dbo_name = std::bool_constant>; + + /** + * Returns the database object name carried by the `dbo_name_t<…>` element of `annotations`, + * or the type's reflected identifier when no such element is present. + */ + template + constexpr std::string_view resolve_dbo_name(const Tuple& annotations) { + using name_index = find_tuple_element; + + if constexpr (name_index::value < std::tuple_size_v) { + return std::get(annotations).name(); + } else { + return extract_type_identifier(); + } + } + + /** + * Returns a copy of `tuple` with all `dbo_name_t<…>` elements removed. + */ + template + constexpr auto filter_out_dbo_name(Tuple&& tuple) { + using constraints_index_sequence = filter_tuple_sequence_t::template fn>; + return create_from_tuple(std::forward(tuple), constraints_index_sequence{}); + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Database object name annotation factory. + * Use as a class-scope annotation: `struct [[=dbo_name("users")]] User { ... };`. + * Both `make_table()` and `make_view()` consume this annotation; when absent + * the name falls back to `T`'s reflected identifier. + */ + template + constexpr internal::dbo_name_t dbo_name(const char (&dboName)[N]) { + return {dboName}; + } +} +#endif diff --git a/dev/schema/view.h b/dev/schema/view.h index 5db8d0ad9..c29912091 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -2,8 +2,10 @@ #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_WITH_VIEW -#include // std::remove_cvref +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED #include // std::forward, std::move, std::index_sequence, std::make_index_sequence +#include // std::meta::info, std::meta::identifier_of +#endif #endif #endif @@ -13,6 +15,7 @@ #include "../select_constraints.h" #include "column.h" #include "table_base.h" +#include "dbo_name.h" namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_VIEW @@ -43,19 +46,23 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_VIEW #ifdef SQLITE_ORM_REFLECTION_SUPPORTED namespace sqlite_orm::internal { - template - auto make_view(std::string name, std::index_sequence, Select select) { - constexpr auto memberNames = extract_member_names(); - constexpr auto memberPointers = extract_member_pointers(); + template + auto make_reflected_view(Select select) { + std::string viewName{resolve_dbo_name(extract_type_annotations())}; + static /*gcc*/ constexpr auto members = extract_members(); - using view_type = - query_view(memberNames)), std::get(memberPointers)))...>; + auto columns = [](std::index_sequence) static { + return std::tuple { + []() static { + return sqlite_orm::make_column(std::string(std::meta::identifier_of(member)), + splice_member_pointer()); + }.template operator()()... + }; + }(std::make_index_sequence{}); - return view_type{std::move(name), - std::tuple{make_column(std::string(std::get(memberNames)), std::get(memberPointers))...}, - std::move(select)}; + return [&](std::tuple&& cols) { + return query_view{std::move(viewName), std::move(cols), std::move(select)}; + }(std::move(columns)); } } @@ -64,31 +71,31 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * Factory function for a view definition. * * The mapped object type is explicitly specified, columns and their names are deferred from the object type. - * The object type must be an aggregate. + * The object type must be an aggregate. The optional `[[=dbo_name("…")]]` class-scope annotation overrides + * the view name (otherwise the type's reflected identifier is used). */ template requires (internal::is_select_expression_v) { select.highest_level = true; } - return internal::make_view(std::move(name), - std::make_index_sequence()>{}, - std::move(select)); + return make_reflected_view(std::move(select)); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Factory function for a view definition. - * + * * The mapped object type is explicitly specified, columns and their names are deferred from the object type. - * The object type must be an aggregate. + * The object type must be an aggregate. The optional `[[=dbo_name("…")]]` class-scope annotation overrides + * the view name (otherwise the type's reflected identifier is used). */ template - auto make_view(std::string name, Select select) { - return make_view>(std::move(name), std::forward(select)); } #endif } diff --git a/dev/tuple_helper/tuple_traits.h b/dev/tuple_helper/tuple_traits.h index e0ee762f0..d66a2573c 100644 --- a/dev/tuple_helper/tuple_traits.h +++ b/dev/tuple_helper/tuple_traits.h @@ -29,8 +29,18 @@ namespace sqlite_orm::internal { template class Template, template class ProjOp = polyfill::type_identity_t> using tuple_has_template = mpl::invoke_t, Pack, mpl::quote_fn>; + /* + * Higher-order metafunction returning the first index constant of the desired element having the specified trait in a tuple (possibly projected). + * + * `ProjOp` is a metafunction + */ + template class TraitFn, template class ProjOp = polyfill::type_identity_t> + using find_tuple_element = mpl::invoke_t, Pack, mpl::quote_fn>; + /* * Higher-order metafunction returning the first index constant of the desired type in a tuple (possibly projected). + * + * `ProjOp` is a metafunction */ template class ProjOp = polyfill::type_identity_t> using find_tuple_type = mpl::invoke_t, Pack, mpl::quote_fn>; diff --git a/examples/view.cpp b/examples/view.cpp index c52abab5d..1f32577d2 100644 --- a/examples/view.cpp +++ b/examples/view.cpp @@ -45,21 +45,21 @@ struct Department { // The fields are automatically mapped through C++ reflection // View 1: High earners (employees earning more than 60000) -struct HighEarner { +struct[[= dbo_name("high_earners")]] HighEarner { int64 id; std::string name; double salary; }; // View 2: Department summary with employee count and average salary -struct DepartmentSummary { +struct[[= dbo_name("department_summary")]] DepartmentSummary { std::string department_name; int employee_count; double avg_salary; }; // View 3: Complete employee information with department details (join result) -struct EmployeeDetail { +struct[[= dbo_name("employee_details")]] EmployeeDetail { int64 id; std::string employee_name; double salary; @@ -81,23 +81,22 @@ inline auto initStorage(const std::string& path) { make_column("name", &Department::name), make_column("location", &Department::location)), - // Define views - notice how we only specify the view name and SELECT statement - // The column mappings are automatically derived from the view object type! + // Define views - notice how we only specify the SELECT statement. + // The column mappings and view name are derived from the view object type + // (column names from non-static data members; view name from the optional + // `[[=dbo_name("…")]]` annotation, falling back to the type's identifier). // View 1: Filter high earners make_view( - "high_earners", select(columns(&Employee::id, &Employee::name, &Employee::salary), where(c(&Employee::salary) > 60000.0))), // View 2: Aggregate data by department - make_view("department_summary", - select(columns(&Department::name, count(&Employee::id), avg(&Employee::salary)), + make_view(select(columns(&Department::name, count(&Employee::id), avg(&Employee::salary)), left_join(on(c(&Employee::department_id) == &Department::id)), group_by(&Department::name))), // View 3: Join employees with departments make_view( - "employee_details", select(columns(&Employee::id, &Employee::name, &Employee::salary, &Department::name, &Department::location), join(on(c(&Employee::department_id) == &Department::id))))); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 3d4722648..f6500e954 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -1343,6 +1343,12 @@ namespace sqlite_orm::internal { template class Op> using check_if_lacks = mpl::not_>; + /* + * Quoted metafunction that finds the index of the element having the specified trait in a tuple. + */ + template class TraitFn> + using finds_if_has = mpl::finds>; + /* * Quoted metafunction that finds the index of the given type in a tuple. */ @@ -1430,8 +1436,18 @@ namespace sqlite_orm::internal { template class Template, template class ProjOp = polyfill::type_identity_t> using tuple_has_template = mpl::invoke_t, Pack, mpl::quote_fn>; + /* + * Higher-order metafunction returning the first index constant of the desired element having the specified trait in a tuple (possibly projected). + * + * `ProjOp` is a metafunction + */ + template class TraitFn, template class ProjOp = polyfill::type_identity_t> + using find_tuple_element = mpl::invoke_t, Pack, mpl::quote_fn>; + /* * Higher-order metafunction returning the first index constant of the desired type in a tuple (possibly projected). + * + * `ProjOp` is a metafunction */ template class ProjOp = polyfill::type_identity_t> using find_tuple_type = mpl::invoke_t, Pack, mpl::quote_fn>; @@ -13748,8 +13764,10 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_WITH_VIEW -#include // std::remove_cvref +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED #include // std::forward, std::move, std::index_sequence, std::make_index_sequence +#include // std::meta::info, std::meta::identifier_of +#endif #endif #endif @@ -13759,7 +13777,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_REFLECTION_SUPPORTED -#include // std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of +#include // std::array +#include // std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of, std::meta::annotations_of #include // std::tuple #include // std::index_sequence, std::make_index_sequence #endif @@ -13767,31 +13786,65 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ORM_REFLECTION_SUPPORTED namespace sqlite_orm::internal { + /** + * Reflects the non-static data members of `T` and returns them as a fixed-size array + * of `std::meta::info` reflections. + */ template - consteval auto extract_member_names() { + consteval auto extract_members() { constexpr auto ctx = std::meta::access_context::current(); constexpr size_t N = nonstatic_data_members_of(^^T, ctx).size(); - auto members = nonstatic_data_members_of(^^T, ctx); - return [&members](std::index_sequence) consteval { - return std::tuple{std::meta::identifier_of(members[I])...}; + return [&ctx](std::index_sequence) consteval { + return std::array{nonstatic_data_members_of(^^T, ctx)[I]...}; }(std::make_index_sequence{}); } + /** + * Returns the identifier of `T`. + */ template - consteval auto extract_member_pointers() { - constexpr auto ctx = std::meta::access_context::current(); - constexpr size_t N = nonstatic_data_members_of(^^T, ctx).size(); + consteval auto extract_type_identifier() { + return std::meta::identifier_of(^^T); + } - return [&ctx](std::index_sequence) consteval { - return std::tuple{&[:nonstatic_data_members_of(^^T, ctx)[I]:]...}; - }(std::make_index_sequence{}); + /** + * Splices a non-static data member reflection into a member-pointer expression. + * Encapsulated here so the splice operator does not leak into consumer headers. + */ + template + consteval auto splice_member_pointer() { + return &[:member:]; + } + + /** + * Splices a reflection's annotations into a tuple of values. The reflection may be + * a type or a non-static data member. + * Encapsulated here so the splice operator does not leak into consumer headers. + * + * Two P3394 details inform this implementation: + * - Annotation reflections returned by `annotations_of` are not directly spliceable; + * they must first be routed through `std::meta::constant_of`, which returns a + * splice-able constant reflection. + * - `std::meta::annotations_of` returns a `std::vector`, whose heap + * allocation is transient under C++20 constexpr rules and cannot be bound to a + * `constexpr` variable. The size and per-index lookups therefore re-call + * `annotations_of` inline so each transient vector dies within its own constant + * expression. + */ + template + consteval auto splice_annotations() { + return [](std::index_sequence) consteval { + return std::tuple{[:std::meta::constant_of(std::meta::annotations_of(refl)[I]):]...}; + }(std::make_index_sequence{}); } + /** + * Returns the class-scope annotations of `T` as a tuple. + */ template - consteval auto count_members() { - constexpr auto ctx = std::meta::access_context::current(); - return nonstatic_data_members_of(^^T, ctx).size(); + consteval auto extract_type_annotations() { + return splice_annotations<^^T>(); } } #endif @@ -13804,6 +13857,95 @@ namespace sqlite_orm::internal { // #include "table_base.h" +// #include "dbo_name.h" + +#ifndef SQLITE_ORM_IMPORT_STD_MODULE +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED +#include // std::string_view +#include // std::tuple +#include // std::bool_constant +#include // std::forward +#endif +#endif + +// #include "../functional/cstring_literal.h" + +// #include "../functional/meta_util.h" + +// #include "../functional/mpl.h" + +// #include "../tuple_helper/tuple_filter.h" + +// #include "../tuple_helper/tuple_traits.h" + +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED +namespace sqlite_orm::internal { + /** + * Class-scope annotation that overrides the database object name (table or view). + * When absent, the name falls back to `std::meta::identifier_of(^^T)`. + * + * The string is embedded in the type's bytes via `cstring_literal` rather than + * carried by pointer + size: pointers to string literals are not accepted as + * annotation values by current reflection implementations (the underlying object + * has no linkage), so a self-contained fixed-size byte array is required. + */ + template + struct dbo_name_t : cstring_literal { + constexpr dbo_name_t(const char (&cstr)[N]) : cstring_literal{cstr} {} + + constexpr auto name() const noexcept { + return this->cstr; + } + }; + + template + constexpr bool is_dbo_name_v = false; + + template + constexpr bool is_dbo_name_v> = true; + + template + using is_dbo_name = std::bool_constant>; + + /** + * Returns the database object name carried by the `dbo_name_t<…>` element of `annotations`, + * or the type's reflected identifier when no such element is present. + */ + template + constexpr std::string_view resolve_dbo_name(const Tuple& annotations) { + using name_index = find_tuple_element; + + if constexpr (name_index::value < std::tuple_size_v) { + return std::get(annotations).name(); + } else { + return extract_type_identifier(); + } + } + + /** + * Returns a copy of `tuple` with all `dbo_name_t<…>` elements removed. + */ + template + constexpr auto filter_out_dbo_name(Tuple&& tuple) { + using constraints_index_sequence = filter_tuple_sequence_t::template fn>; + return create_from_tuple(std::forward(tuple), constraints_index_sequence{}); + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Database object name annotation factory. + * Use as a class-scope annotation: `struct [[=dbo_name("users")]] User { ... };`. + * Both `make_table()` and `make_view()` consume this annotation; when absent + * the name falls back to `T`'s reflected identifier. + */ + template + constexpr internal::dbo_name_t dbo_name(const char (&dboName)[N]) { + return {dboName}; + } +} +#endif + namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_VIEW /** @@ -13833,19 +13975,23 @@ namespace sqlite_orm::internal { #ifdef SQLITE_ORM_WITH_VIEW #ifdef SQLITE_ORM_REFLECTION_SUPPORTED namespace sqlite_orm::internal { - template - auto make_view(std::string name, std::index_sequence, Select select) { - constexpr auto memberNames = extract_member_names(); - constexpr auto memberPointers = extract_member_pointers(); - - using view_type = - query_view(memberNames)), std::get(memberPointers)))...>; + template + auto make_reflected_view(Select select) { + std::string viewName{resolve_dbo_name(extract_type_annotations())}; + static /*gcc*/ constexpr auto members = extract_members(); + + auto columns = [](std::index_sequence) static { + return std::tuple { + []() static { + return sqlite_orm::make_column(std::string(std::meta::identifier_of(member)), + splice_member_pointer()); + }.template operator()()... + }; + }(std::make_index_sequence{}); - return view_type{std::move(name), - std::tuple{make_column(std::string(std::get(memberNames)), std::get(memberPointers))...}, - std::move(select)}; + return [&](std::tuple&& cols) { + return query_view{std::move(viewName), std::move(cols), std::move(select)}; + }(std::move(columns)); } } @@ -13854,31 +14000,31 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * Factory function for a view definition. * * The mapped object type is explicitly specified, columns and their names are deferred from the object type. - * The object type must be an aggregate. + * The object type must be an aggregate. The optional `[[=dbo_name("…")]]` class-scope annotation overrides + * the view name (otherwise the type's reflected identifier is used). */ template requires (internal::is_select_expression_v) { select.highest_level = true; } - return internal::make_view(std::move(name), - std::make_index_sequence()>{}, - std::move(select)); + return make_reflected_view(std::move(select)); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Factory function for a view definition. - * + * * The mapped object type is explicitly specified, columns and their names are deferred from the object type. - * The object type must be an aggregate. + * The object type must be an aggregate. The optional `[[=dbo_name("…")]]` class-scope annotation overrides + * the view name (otherwise the type's reflected identifier is used). */ template - auto make_view(std::string name, Select select) { - return make_view>(std::move(name), std::forward(select)); } #endif } diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 082bd95ea..4481b581b 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -24,7 +24,7 @@ struct DidLogsCollector { std::vector DidLogsCollector::logs; #ifdef SQLITE_ORM_WITH_VIEW -struct UserViewLoggerTests { +struct[[= dbo_name("users_view")]] UserViewLoggerTests { int id = 0; std::string name; }; @@ -96,7 +96,7 @@ TEST_CASE("logger") { make_column("id", &VisitLog::id, primary_key()), make_column("message", &VisitLog::message)), #ifdef SQLITE_ORM_WITH_VIEW - make_view("users_view", select(asterisk())), + make_view(select(asterisk())), #endif will_run_query(willRunQuery), did_run_query(didRunQuery)); diff --git a/tests/schema/view_tests.cpp b/tests/schema/view_tests.cpp index c8566af8c..87a14e88d 100644 --- a/tests/schema/view_tests.cpp +++ b/tests/schema/view_tests.cpp @@ -4,18 +4,42 @@ #ifdef SQLITE_ORM_WITH_VIEW using namespace sqlite_orm; -struct UserViewSchemaTests { - int id = 0; - std::string name; -}; +namespace { + struct[[= dbo_name("user_view")]] UserViewSchemaTests { + int64 id = 0; + std::string name; + }; + + struct UserViewSchemaTestsDefaultName { + int64 id = 0; + std::string name; + }; +} + +TEST_CASE("make_view - name resolution") { + struct User { + int64 id = 0; + std::string name; + }; + + SECTION("[[=dbo_name(...)]] annotation supplies the view name") { + auto view = make_view(select(asterisk())); + REQUIRE(view.name == "user_view"); + } + + SECTION("fallback to type identifier") { + auto view = make_view(select(asterisk())); + REQUIRE(view.name == "UserViewSchemaTestsDefaultName"); + } +} TEST_CASE("view::find_column_name") { struct User { - int id = 0; + int64 id = 0; std::string name; }; - auto view = make_view("user_view", select(asterisk())); + auto view = make_view(select(asterisk())); SECTION("fields") { REQUIRE((view.find_column_name(&UserViewSchemaTests::id) && diff --git a/tests/statement_serializer_tests/schema/view.cpp b/tests/statement_serializer_tests/schema/view.cpp index 6a87220d5..adf2619a9 100644 --- a/tests/statement_serializer_tests/schema/view.cpp +++ b/tests/statement_serializer_tests/schema/view.cpp @@ -5,7 +5,7 @@ using namespace sqlite_orm; using internal::serialize; -struct UserViewSerializerTests { +struct[[= dbo_name("user_view")]] UserViewSerializerTests { int id = 0; std::string name; }; @@ -17,7 +17,7 @@ TEST_CASE("statement_serializer query_view") { }; auto table = make_table("user", make_column("id", &User::id), make_column("name", &User::name)); - auto view = make_view("user_view", select(asterisk(true))); + auto view = make_view(select(asterisk(true))); using db_objects_t = internal::db_objects_tuple; const db_objects_t dbObjects{table, view}; using context_t = internal::serializer_context; diff --git a/tests/static_tests/view_static_tests.cpp b/tests/static_tests/view_static_tests.cpp index 071726ab6..683759687 100644 --- a/tests/static_tests/view_static_tests.cpp +++ b/tests/static_tests/view_static_tests.cpp @@ -6,7 +6,7 @@ using namespace sqlite_orm; using internal::col_index_sequence_of, internal::col_index_sequence_with_field_type; using internal::is_column; -struct UserViewStaticTests { +struct[[= dbo_name("user_view")]] UserViewStaticTests { int id = 0; std::string name; }; @@ -21,7 +21,7 @@ TEST_CASE("view static count_of()") { }; SECTION("traditional") { - auto view = make_view("user_view", select(asterisk())); + auto view = make_view(select(asterisk())); using elements_type = decltype(view.elements); STATIC_REQUIRE(view.count_of() == 2); STATIC_REQUIRE(col_index_sequence_of::size() == 2); @@ -29,7 +29,7 @@ TEST_CASE("view static count_of()") { } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES SECTION("table reference") { - auto view = make_view("user_view", select(asterisk())); + auto view = make_view(select(asterisk())); using elements_type = decltype(view.elements); STATIC_REQUIRE(view.count_of() == 2); STATIC_REQUIRE(col_index_sequence_of::size() == 2); diff --git a/tests/storage_tests.cpp b/tests/storage_tests.cpp index 3e1f21fa8..24a08785c 100644 --- a/tests/storage_tests.cpp +++ b/tests/storage_tests.cpp @@ -266,21 +266,24 @@ TEST_CASE("drop table") { } #ifdef SQLITE_ORM_WITH_VIEW -struct UserViewDropViewTests { - int id = 0; - std::string name; -}; +namespace { + constexpr char usersViewName[] = "users_view"; + + struct[[= dbo_name(usersViewName)]] UserViewDropViewTests { + int id = 0; + std::string name; + }; +} TEST_CASE("drop view") { struct User { int id = 0; std::string name; }; - const std::string usersViewName = "users_view"; auto storage = make_storage({}, make_table("users", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), - make_view(usersViewName, select(asterisk()))); + make_view(select(asterisk()))); REQUIRE_FALSE(storage.view_exists(usersViewName)); storage.sync_schema(); diff --git a/tests/view_tests.cpp b/tests/view_tests.cpp index 8efbc9ad5..4a572d075 100644 --- a/tests/view_tests.cpp +++ b/tests/view_tests.cpp @@ -5,7 +5,7 @@ #ifdef SQLITE_ORM_WITH_VIEW using namespace sqlite_orm; -struct UserViewTests { +struct[[= dbo_name("user_view")]] UserViewTests { int id = 0; std::string name; @@ -18,7 +18,7 @@ struct UserViewTests { #endif }; -struct UserView2Tests { +struct[[= dbo_name("user_view")]] UserView2Tests { std::string name; #ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED @@ -42,7 +42,7 @@ TEST_CASE("sql view") { auto storage = make_storage( "", make_table("user", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), - make_view("user_view", select(asterisk()))); + make_view(select(asterisk()))); storage.sync_schema(); @@ -67,8 +67,7 @@ TEST_CASE("sql view") { auto storage = make_storage( "", make_table("user", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), - make_view("user_view", - with(users_cte().as(select(asterisk())), select(asterisk())))); + make_view(with(users_cte().as(select(asterisk())), select(asterisk())))); storage.sync_schema(); @@ -104,7 +103,7 @@ TEST_CASE("sync sql view") { auto storage = make_storage( storagePath, make_table("user", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), - make_view("user_view", select(&User::name))); + make_view(select(&User::name))); auto syncResult = storage.sync_schema(); REQUIRE(syncResult.at("user_view") == sync_schema_result::new_table_created); @@ -117,7 +116,7 @@ TEST_CASE("sync sql view") { auto storage = make_storage( storagePath, make_table("user", make_column("id", &User::id, primary_key()), make_column("name", &User::name)), - make_view("user_view", select(asterisk()))); + make_view(select(asterisk()))); // simulate should detect the change auto simulateResult = storage.sync_schema_simulate(); From 4027035cad91539296fe8c84b2623a147f6aaf17 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 1 May 2026 19:12:12 +0300 Subject: [PATCH 39/49] Adjust testing enabled query view --- tests/logger_tests.cpp | 44 +++++++++++-------- tests/schema/view_tests.cpp | 2 + .../schema/view.cpp | 2 + tests/static_tests/view_static_tests.cpp | 2 + tests/storage_tests.cpp | 2 + tests/view_tests.cpp | 2 + 6 files changed, 36 insertions(+), 18 deletions(-) diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 4481b581b..b77d3620c 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -3,32 +3,36 @@ using namespace sqlite_orm; -struct WillLogsCollector { - static std::vector logs; +namespace { + struct WillLogsCollector { + static std::vector logs; - void operator()(const std::string_view log) { - this->logs.push_back(std::string(log)); - } -}; + void operator()(const std::string_view log) { + this->logs.push_back(std::string(log)); + } + }; -std::vector WillLogsCollector::logs; + std::vector WillLogsCollector::logs; -struct DidLogsCollector { - static std::vector logs; + struct DidLogsCollector { + static std::vector logs; - void operator()(const std::string_view log) { - this->logs.push_back(std::string(log)); - } -}; + void operator()(const std::string_view log) { + this->logs.push_back(std::string(log)); + } + }; -std::vector DidLogsCollector::logs; + std::vector DidLogsCollector::logs; #ifdef SQLITE_ORM_WITH_VIEW -struct[[= dbo_name("users_view")]] UserViewLoggerTests { - int id = 0; - std::string name; -}; +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED + struct[[= dbo_name("users_view")]] UserViewLoggerTests { + int id = 0; + std::string name; + }; +#endif #endif +} TEST_CASE("logger") { using Logs = std::vector; using Callback = std::function; @@ -96,7 +100,9 @@ TEST_CASE("logger") { make_column("id", &VisitLog::id, primary_key()), make_column("message", &VisitLog::message)), #ifdef SQLITE_ORM_WITH_VIEW +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED make_view(select(asterisk())), +#endif #endif will_run_query(willRunQuery), did_run_query(didRunQuery)); @@ -176,6 +182,7 @@ TEST_CASE("logger") { pushExpected(expected); } #ifdef SQLITE_ORM_WITH_VIEW +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED SECTION("drop_view") { storage.drop_view("users_view"); pushExpected(R"(DROP VIEW "users_view")"); @@ -188,6 +195,7 @@ TEST_CASE("logger") { storage.drop_view_if_exists(value); pushExpected(expected); } +#endif #endif SECTION("vacuum") { storage.vacuum(); diff --git a/tests/schema/view_tests.cpp b/tests/schema/view_tests.cpp index 87a14e88d..289e3dfea 100644 --- a/tests/schema/view_tests.cpp +++ b/tests/schema/view_tests.cpp @@ -2,6 +2,7 @@ #include #ifdef SQLITE_ORM_WITH_VIEW +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED using namespace sqlite_orm; namespace { @@ -49,3 +50,4 @@ TEST_CASE("view::find_column_name") { } } #endif +#endif diff --git a/tests/statement_serializer_tests/schema/view.cpp b/tests/statement_serializer_tests/schema/view.cpp index adf2619a9..135d83d58 100644 --- a/tests/statement_serializer_tests/schema/view.cpp +++ b/tests/statement_serializer_tests/schema/view.cpp @@ -2,6 +2,7 @@ #include #ifdef SQLITE_ORM_WITH_VIEW +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED using namespace sqlite_orm; using internal::serialize; @@ -41,3 +42,4 @@ TEST_CASE("statement_serializer query_view") { } } #endif +#endif diff --git a/tests/static_tests/view_static_tests.cpp b/tests/static_tests/view_static_tests.cpp index 683759687..e77ec4fac 100644 --- a/tests/static_tests/view_static_tests.cpp +++ b/tests/static_tests/view_static_tests.cpp @@ -2,6 +2,7 @@ #include #ifdef SQLITE_ORM_WITH_VIEW +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED using namespace sqlite_orm; using internal::col_index_sequence_of, internal::col_index_sequence_with_field_type; using internal::is_column; @@ -38,3 +39,4 @@ TEST_CASE("view static count_of()") { #endif } #endif +#endif diff --git a/tests/storage_tests.cpp b/tests/storage_tests.cpp index 24a08785c..bc4265eca 100644 --- a/tests/storage_tests.cpp +++ b/tests/storage_tests.cpp @@ -266,6 +266,7 @@ TEST_CASE("drop table") { } #ifdef SQLITE_ORM_WITH_VIEW +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED namespace { constexpr char usersViewName[] = "users_view"; @@ -296,6 +297,7 @@ TEST_CASE("drop view") { REQUIRE_NOTHROW(storage.drop_view_if_exists(usersViewName)); } #endif +#endif TEST_CASE("drop index") { struct User { diff --git a/tests/view_tests.cpp b/tests/view_tests.cpp index 4a572d075..259d5058e 100644 --- a/tests/view_tests.cpp +++ b/tests/view_tests.cpp @@ -3,6 +3,7 @@ #include // std::remove #ifdef SQLITE_ORM_WITH_VIEW +#ifdef SQLITE_ORM_REFLECTION_SUPPORTED using namespace sqlite_orm; struct[[= dbo_name("user_view")]] UserViewTests { @@ -137,3 +138,4 @@ TEST_CASE("sync sql view") { std::remove(storagePath); } #endif +#endif From 5c7e8ecceee0cee8d626f61d69280c5bc83035f7 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 1 May 2026 20:37:32 +0300 Subject: [PATCH 40/49] Corrected no-warning option --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8b599becd..a215835df 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -84,7 +84,7 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") # disabled due to unit tests testing deprecated features -Wno-deprecated-declarations # disabled due to unit tests testing overaligned structs - -Winterference-size + -Wno-interference-size # perfectly valid to have missing field initializers in library's test code -Wno-missing-field-initializers # suppress warnings about unused variables in test code From 5398c1dda2ece9df522e97bb39c8b984654c86e1 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 1 May 2026 20:50:08 +0300 Subject: [PATCH 41/49] Corrected a few mixed up things again --- dev/storage_base.h | 2 +- include/sqlite_orm/sqlite_orm.h | 2 +- tests/logger_tests.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index 2d575574d..33f658609 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -406,7 +406,7 @@ namespace sqlite_orm::internal { * double operator()(double arg) const { * return std::sqrt(arg); * } - * + * * static const char* name() { * return "SQRT"; * } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index f6500e954..81c896644 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -20164,7 +20164,7 @@ namespace sqlite_orm::internal { * double operator()(double arg) const { * return std::sqrt(arg); * } - * + * * static const char* name() { * return "SQRT"; * } diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index b77d3620c..f94f59458 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -250,7 +250,7 @@ TEST_CASE("logger") { SECTION("db_release_memory") { std::ignore = storage.db_release_memory(); } - SECTION("trigger_names") { + SECTION("table_names") { std::ignore = storage.table_names(); pushExpected("SELECT name FROM sqlite_master WHERE type='table'"); } @@ -258,7 +258,7 @@ TEST_CASE("logger") { std::ignore = storage.view_names(); pushExpected("SELECT name FROM sqlite_master WHERE type='view'"); } - SECTION("table_names") { + SECTION("trigger_names") { std::ignore = storage.trigger_names(); pushExpected("SELECT name FROM sqlite_master WHERE type='trigger'"); } From 68382d8f2ea6bff9788e4229e44f0cb0e77c1c8b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 1 May 2026 22:05:31 +0300 Subject: [PATCH 42/49] Capture only the variables that are needed --- dev/schema/view.h | 2 +- include/sqlite_orm/sqlite_orm.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/schema/view.h b/dev/schema/view.h index c29912091..d220bb666 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -60,7 +60,7 @@ namespace sqlite_orm::internal { }; }(std::make_index_sequence{}); - return [&](std::tuple&& cols) { + return [&viewName, &select](std::tuple&& cols) { return query_view{std::move(viewName), std::move(cols), std::move(select)}; }(std::move(columns)); } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 81c896644..ac7badfff 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -13989,7 +13989,7 @@ namespace sqlite_orm::internal { }; }(std::make_index_sequence{}); - return [&](std::tuple&& cols) { + return [&viewName, &select](std::tuple&& cols) { return query_view{std::move(viewName), std::move(cols), std::move(select)}; }(std::move(columns)); } From fcbfb1100fb815e122dcafef39014c1be54db0ca Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 2 May 2026 17:27:27 +0300 Subject: [PATCH 43/49] Considered base members for reflected views/tables --- dev/functional/meta_util.h | 26 ++++++++++---- include/sqlite_orm/sqlite_orm.h | 26 ++++++++++---- tests/schema/view_tests.cpp | 27 ++++++++++---- .../schema/view.cpp | 12 ++++--- tests/static_tests/view_static_tests.cpp | 21 ++++++++--- tests/view_tests.cpp | 36 ++++++++++--------- 6 files changed, 100 insertions(+), 48 deletions(-) diff --git a/dev/functional/meta_util.h b/dev/functional/meta_util.h index fde2a0982..f04ae3ee4 100644 --- a/dev/functional/meta_util.h +++ b/dev/functional/meta_util.h @@ -3,7 +3,7 @@ #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_REFLECTION_SUPPORTED #include // std::array -#include // std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of, std::meta::annotations_of +#include // std::define_static_array, std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of, std::meta::annotations_of #include // std::tuple #include // std::index_sequence, std::make_index_sequence #endif @@ -12,17 +12,29 @@ #ifdef SQLITE_ORM_REFLECTION_SUPPORTED namespace sqlite_orm::internal { /** - * Reflects the non-static data members of `T` and returns them as a fixed-size array - * of `std::meta::info` reflections. + * Reflects the non-static data members of `T` and its base classes + * and returns them as a fixed-size span of `std::meta::info` reflections. */ template consteval auto extract_members() { constexpr auto ctx = std::meta::access_context::current(); - constexpr size_t N = nonstatic_data_members_of(^^T, ctx).size(); - return [&ctx](std::index_sequence) consteval { - return std::array{nonstatic_data_members_of(^^T, ctx)[I]...}; - }(std::make_index_sequence{}); + constexpr auto collect = [](this const auto& self) -> std::vector { + std::vector result; + + // Recurse into direct base classes first (preserves layout order) + template for (constexpr std::meta::info base : std::define_static_array(bases_of(^^U, ctx))) { + using base_type = typename[:type_of(base):]; + result.append_range(self.template operator()()); + } + + // Then this class's own non-static data members + result.append_range(nonstatic_data_members_of(^^U, ctx)); + + return result; + }; + + return std::define_static_array(collect.template operator()()); } /** diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index ac7badfff..a31b5ce72 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -13778,7 +13778,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifndef SQLITE_ORM_IMPORT_STD_MODULE #ifdef SQLITE_ORM_REFLECTION_SUPPORTED #include // std::array -#include // std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of, std::meta::annotations_of +#include // std::define_static_array, std::meta::access_context, std::meta::nonstatic_data_members_of, std::meta::identifier_of, std::meta::annotations_of #include // std::tuple #include // std::index_sequence, std::make_index_sequence #endif @@ -13787,17 +13787,29 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ORM_REFLECTION_SUPPORTED namespace sqlite_orm::internal { /** - * Reflects the non-static data members of `T` and returns them as a fixed-size array - * of `std::meta::info` reflections. + * Reflects the non-static data members of `T` and its base classes + * and returns them as a fixed-size span of `std::meta::info` reflections. */ template consteval auto extract_members() { constexpr auto ctx = std::meta::access_context::current(); - constexpr size_t N = nonstatic_data_members_of(^^T, ctx).size(); - return [&ctx](std::index_sequence) consteval { - return std::array{nonstatic_data_members_of(^^T, ctx)[I]...}; - }(std::make_index_sequence{}); + constexpr auto collect = [](this const auto& self) -> std::vector { + std::vector result; + + // Recurse into direct base classes first (preserves layout order) + template for (constexpr std::meta::info base: std::define_static_array(bases_of(^^U, ctx))) { + using base_type = typename[:type_of(base):]; + result.append_range(self.template operator()()); + } + + // Then this class's own non-static data members + result.append_range(nonstatic_data_members_of(^^U, ctx)); + + return result; + }; + + return std::define_static_array(collect.template operator()()); } /** diff --git a/tests/schema/view_tests.cpp b/tests/schema/view_tests.cpp index 289e3dfea..f2f70cd13 100644 --- a/tests/schema/view_tests.cpp +++ b/tests/schema/view_tests.cpp @@ -17,7 +17,7 @@ namespace { }; } -TEST_CASE("make_view - name resolution") { +TEST_CASE("view make_view name resolution") { struct User { int64 id = 0; std::string name; @@ -40,13 +40,26 @@ TEST_CASE("view::find_column_name") { std::string name; }; - auto view = make_view(select(asterisk())); + SECTION("direct") { + auto view = make_view(select(asterisk())); + + SECTION("fields") { + REQUIRE((view.find_column_name(&UserViewSchemaTests::id) && + *view.find_column_name(&UserViewSchemaTests::id) == "id")); + REQUIRE((view.find_column_name(&UserViewSchemaTests::name) && + *view.find_column_name(&UserViewSchemaTests::name) == "name")); + } + } + SECTION("derived") { + struct DerivedUserView : UserViewSchemaTests {}; + auto view = make_view(select(asterisk())); - SECTION("fields") { - REQUIRE((view.find_column_name(&UserViewSchemaTests::id) && - *view.find_column_name(&UserViewSchemaTests::id) == "id")); - REQUIRE((view.find_column_name(&UserViewSchemaTests::name) && - *view.find_column_name(&UserViewSchemaTests::name) == "name")); + SECTION("fields") { + REQUIRE((view.find_column_name(&UserViewSchemaTests::id) && + *view.find_column_name(&UserViewSchemaTests::id) == "id")); + REQUIRE((view.find_column_name(&UserViewSchemaTests::name) && + *view.find_column_name(&UserViewSchemaTests::name) == "name")); + } } } #endif diff --git a/tests/statement_serializer_tests/schema/view.cpp b/tests/statement_serializer_tests/schema/view.cpp index 135d83d58..31eb011f4 100644 --- a/tests/statement_serializer_tests/schema/view.cpp +++ b/tests/statement_serializer_tests/schema/view.cpp @@ -6,12 +6,14 @@ using namespace sqlite_orm; using internal::serialize; -struct[[= dbo_name("user_view")]] UserViewSerializerTests { - int id = 0; - std::string name; -}; +namespace { + struct[[= dbo_name("user_view")]] UserViewSerializerTests { + int id = 0; + std::string name; + }; +} -TEST_CASE("statement_serializer query_view") { +TEST_CASE("view statement_serializer") { struct User { int id = 0; std::string name; diff --git a/tests/static_tests/view_static_tests.cpp b/tests/static_tests/view_static_tests.cpp index e77ec4fac..62b3cca89 100644 --- a/tests/static_tests/view_static_tests.cpp +++ b/tests/static_tests/view_static_tests.cpp @@ -7,13 +7,15 @@ using namespace sqlite_orm; using internal::col_index_sequence_of, internal::col_index_sequence_with_field_type; using internal::is_column; -struct[[= dbo_name("user_view")]] UserViewStaticTests { - int id = 0; - std::string name; -}; +namespace { + struct[[= dbo_name("user_view")]] UserViewStaticTests { + int id = 0; + std::string name; + }; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES -constexpr orm_table_reference auto user_view = c(); + constexpr orm_table_reference auto user_view = c(); #endif +} TEST_CASE("view static count_of()") { struct User { @@ -28,6 +30,15 @@ TEST_CASE("view static count_of()") { STATIC_REQUIRE(col_index_sequence_of::size() == 2); STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); } + SECTION("derived") { + struct DerivedUserView : UserViewStaticTests {}; + + auto view = make_view(select(asterisk())); + using elements_type = decltype(view.elements); + STATIC_REQUIRE(view.count_of() == 2); + STATIC_REQUIRE(col_index_sequence_of::size() == 2); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES SECTION("table reference") { auto view = make_view(select(asterisk())); diff --git a/tests/view_tests.cpp b/tests/view_tests.cpp index 259d5058e..276493dd5 100644 --- a/tests/view_tests.cpp +++ b/tests/view_tests.cpp @@ -6,32 +6,34 @@ #ifdef SQLITE_ORM_REFLECTION_SUPPORTED using namespace sqlite_orm; -struct[[= dbo_name("user_view")]] UserViewTests { - int id = 0; - std::string name; +namespace { + struct[[= dbo_name("user_view")]] UserViewTests { + int id = 0; + std::string name; #ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED - bool operator==(const UserViewTests&) const = default; + bool operator==(const UserViewTests&) const = default; #else - bool operator==(const UserViewTests& right) const { - return id == right.id && name == right.name; - } + bool operator==(const UserViewTests& right) const { + return id == right.id && name == right.name; + } #endif -}; + }; -struct[[= dbo_name("user_view")]] UserView2Tests { - std::string name; + struct[[= dbo_name("user_view")]] UserView2Tests { + std::string name; #ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED - bool operator==(const UserView2Tests&) const = default; + bool operator==(const UserView2Tests&) const = default; #else - bool operator==(const UserView2Tests& right) const { - return name == right.name; - } + bool operator==(const UserView2Tests& right) const { + return name == right.name; + } #endif -}; + }; +} -TEST_CASE("sql view") { +TEST_CASE("view") { using Catch::Matchers::UnorderedEquals; struct User { @@ -90,7 +92,7 @@ TEST_CASE("sql view") { } } -TEST_CASE("sync sql view") { +TEST_CASE("view sync") { struct User { int id = 0; std::string name; From c3b847bc9b3b2ee7ae4518ce54df64374504e30d Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 2 May 2026 17:48:08 +0300 Subject: [PATCH 44/49] github actions: try C++26/gcc-16 tests --- .github/workflows/ci.yaml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e762b7372..e36e6f0e0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -67,7 +67,7 @@ jobs: uses: actions/checkout@v4 - name: Setup vcpkg - uses: lukka/run-vcpkg@v11 + uses: lukka/run-vcpkg@v11.6 with: vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }} vcpkgDirectory: ${{ runner.temp }}/vcpkg @@ -111,6 +111,15 @@ jobs: install_compiler: true compiler_package: g++-10 + - name: "gcc-16, C++26" + os: ubuntu-24.04 + cc: gcc-16 + cxx: g++-16 + cxx_standard: "-DSQLITE_ORM_ENABLE_CXX_26=ON" + install_compiler: true + compiler_package: g++-16 + compiler_ppa: ppa:ubuntu-toolchain-r/test + name: Linux - ${{ matrix.name }} env: @@ -124,11 +133,14 @@ jobs: - name: Install compiler if: matrix.install_compiler run: | + if [ -n "${{ matrix.compiler_ppa }}" ]; then + sudo add-apt-repository -y ${{ matrix.compiler_ppa }} + fi sudo apt-get update sudo apt-get install -y ${{ matrix.compiler_package }} - name: Setup vcpkg - uses: lukka/run-vcpkg@v11 + uses: lukka/run-vcpkg@v11.6 with: vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }} vcpkgDirectory: ${{ runner.temp }}/vcpkg @@ -167,7 +179,7 @@ jobs: uses: actions/checkout@v4 - name: Setup vcpkg - uses: lukka/run-vcpkg@v11 + uses: lukka/run-vcpkg@v11.6 with: vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }} vcpkgDirectory: ${{ runner.temp }}/vcpkg From 1ced217438342f4cdf6e8abdc879f8e222fd1efa Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 2 May 2026 20:57:08 +0300 Subject: [PATCH 45/49] Addressed points raised in review --- dev/storage_base.h | 71 +++++++++++++-------------------- include/sqlite_orm/sqlite_orm.h | 71 +++++++++++++-------------------- tests/schema/view_tests.cpp | 24 +++++------ 3 files changed, 64 insertions(+), 102 deletions(-) diff --git a/dev/storage_base.h b/dev/storage_base.h index 33f658609..c1ecfbaae 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -159,9 +159,9 @@ namespace sqlite_orm::internal { * Calls `DROP VIEW "viewName"`. * More info: https://www.sqlite.org/lang_droptable.html */ - void drop_view(const std::string& tableName) { + void drop_view(const std::string& viewName) { auto connection = this->get_connection(); - this->drop_view_internal(connection.get(), tableName, false); + this->drop_view_internal(connection.get(), viewName, false); } /** @@ -169,9 +169,9 @@ namespace sqlite_orm::internal { * Calls `DROP VIEW IF EXISTS "viewName"`. * More info: https://www.sqlite.org/lang_droptable.html */ - void drop_view_if_exists(const std::string& tableName) { + void drop_view_if_exists(const std::string& viewName) { auto connection = this->get_connection(); - this->drop_view_internal(connection.get(), tableName, true); + this->drop_view_internal(connection.get(), viewName, true); } /** @@ -210,58 +210,24 @@ namespace sqlite_orm::internal { */ bool table_exists(const std::string& tableName) { auto connection = this->get_connection(); - return this->table_exists(connection.get(), tableName); + return this->object_exists(connection.get(), "table", tableName); } bool table_exists(sqlite3* db, const std::string& tableName) const { - bool result = false; - std::string sql; - { - std::stringstream ss; - ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << quote_string_literal("table") - << " AND name = " << quote_string_literal(tableName) << std::flush; - sql = ss.str(); - } - this->executor.perform_exec( - db, - sql, - [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { - auto& res = *(bool*)userData; - res = !!atoi(argv[0]); - return 0; - }, - &result); - return result; + return this->object_exists(db, "table", tableName); } /** * Directly checks the actual database whether the specified view exists, bypassing the library's 'storage' mapping. * @return true if view with the specified name exists in the database, false otherwise. */ - bool view_exists(const std::string& tableName) { + bool view_exists(const std::string& viewName) { auto connection = this->get_connection(); - return this->view_exists(connection.get(), tableName); + return this->object_exists(connection.get(), "view", viewName); } - bool view_exists(sqlite3* db, const std::string& tableName) const { - bool result = false; - std::string sql; - { - std::stringstream ss; - ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << quote_string_literal("view") - << " AND name = " << quote_string_literal(tableName) << std::flush; - sql = ss.str(); - } - this->executor.perform_exec( - db, - sql, - [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { - auto& res = *(bool*)userData; - res = !!atoi(argv[0]); - return 0; - }, - &result); - return result; + bool view_exists(sqlite3* db, const std::string& viewName) const { + return this->object_exists(db, "view", viewName); } void add_generated_cols(std::vector& columnsToAdd, @@ -1151,6 +1117,23 @@ namespace sqlite_orm::internal { this->executor.perform_void_exec(db, ss.str().c_str()); } + bool object_exists(sqlite3* db, const std::string& type, const std::string& name) const { + bool result = false; + std::stringstream ss; + ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << quote_string_literal(type) + << " AND name = " << quote_string_literal(name) << std::flush; + this->executor.perform_exec( + db, + ss.str(), + [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { + auto& res = *(bool*)userData; + res = !!atoi(argv[0]); + return 0; + }, + &result); + return result; + } + std::string retrieve_object_sql(sqlite3* db, const std::string& type, const std::string& name) const { std::string result; std::stringstream ss; diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index a31b5ce72..1f6dc59a5 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -19929,9 +19929,9 @@ namespace sqlite_orm::internal { * Calls `DROP VIEW "viewName"`. * More info: https://www.sqlite.org/lang_droptable.html */ - void drop_view(const std::string& tableName) { + void drop_view(const std::string& viewName) { auto connection = this->get_connection(); - this->drop_view_internal(connection.get(), tableName, false); + this->drop_view_internal(connection.get(), viewName, false); } /** @@ -19939,9 +19939,9 @@ namespace sqlite_orm::internal { * Calls `DROP VIEW IF EXISTS "viewName"`. * More info: https://www.sqlite.org/lang_droptable.html */ - void drop_view_if_exists(const std::string& tableName) { + void drop_view_if_exists(const std::string& viewName) { auto connection = this->get_connection(); - this->drop_view_internal(connection.get(), tableName, true); + this->drop_view_internal(connection.get(), viewName, true); } /** @@ -19980,58 +19980,24 @@ namespace sqlite_orm::internal { */ bool table_exists(const std::string& tableName) { auto connection = this->get_connection(); - return this->table_exists(connection.get(), tableName); + return this->object_exists(connection.get(), "table", tableName); } bool table_exists(sqlite3* db, const std::string& tableName) const { - bool result = false; - std::string sql; - { - std::stringstream ss; - ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << quote_string_literal("table") - << " AND name = " << quote_string_literal(tableName) << std::flush; - sql = ss.str(); - } - this->executor.perform_exec( - db, - sql, - [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { - auto& res = *(bool*)userData; - res = !!atoi(argv[0]); - return 0; - }, - &result); - return result; + return this->object_exists(db, "table", tableName); } /** * Directly checks the actual database whether the specified view exists, bypassing the library's 'storage' mapping. * @return true if view with the specified name exists in the database, false otherwise. */ - bool view_exists(const std::string& tableName) { + bool view_exists(const std::string& viewName) { auto connection = this->get_connection(); - return this->view_exists(connection.get(), tableName); + return this->object_exists(connection.get(), "view", viewName); } - bool view_exists(sqlite3* db, const std::string& tableName) const { - bool result = false; - std::string sql; - { - std::stringstream ss; - ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << quote_string_literal("view") - << " AND name = " << quote_string_literal(tableName) << std::flush; - sql = ss.str(); - } - this->executor.perform_exec( - db, - sql, - [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { - auto& res = *(bool*)userData; - res = !!atoi(argv[0]); - return 0; - }, - &result); - return result; + bool view_exists(sqlite3* db, const std::string& viewName) const { + return this->object_exists(db, "view", viewName); } void add_generated_cols(std::vector& columnsToAdd, @@ -20921,6 +20887,23 @@ namespace sqlite_orm::internal { this->executor.perform_void_exec(db, ss.str().c_str()); } + bool object_exists(sqlite3* db, const std::string& type, const std::string& name) const { + bool result = false; + std::stringstream ss; + ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << quote_string_literal(type) + << " AND name = " << quote_string_literal(name) << std::flush; + this->executor.perform_exec( + db, + ss.str(), + [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { + auto& res = *(bool*)userData; + res = !!atoi(argv[0]); + return 0; + }, + &result); + return result; + } + std::string retrieve_object_sql(sqlite3* db, const std::string& type, const std::string& name) const { std::string result; std::stringstream ss; diff --git a/tests/schema/view_tests.cpp b/tests/schema/view_tests.cpp index f2f70cd13..03c6895d0 100644 --- a/tests/schema/view_tests.cpp +++ b/tests/schema/view_tests.cpp @@ -40,26 +40,22 @@ TEST_CASE("view::find_column_name") { std::string name; }; - SECTION("direct") { + SECTION("fields, direct") { auto view = make_view(select(asterisk())); - SECTION("fields") { - REQUIRE((view.find_column_name(&UserViewSchemaTests::id) && - *view.find_column_name(&UserViewSchemaTests::id) == "id")); - REQUIRE((view.find_column_name(&UserViewSchemaTests::name) && - *view.find_column_name(&UserViewSchemaTests::name) == "name")); - } + REQUIRE((view.find_column_name(&UserViewSchemaTests::id) && + *view.find_column_name(&UserViewSchemaTests::id) == "id")); + REQUIRE((view.find_column_name(&UserViewSchemaTests::name) && + *view.find_column_name(&UserViewSchemaTests::name) == "name")); } - SECTION("derived") { + SECTION("fields, derived") { struct DerivedUserView : UserViewSchemaTests {}; auto view = make_view(select(asterisk())); - SECTION("fields") { - REQUIRE((view.find_column_name(&UserViewSchemaTests::id) && - *view.find_column_name(&UserViewSchemaTests::id) == "id")); - REQUIRE((view.find_column_name(&UserViewSchemaTests::name) && - *view.find_column_name(&UserViewSchemaTests::name) == "name")); - } + REQUIRE((view.find_column_name(&UserViewSchemaTests::id) && + *view.find_column_name(&UserViewSchemaTests::id) == "id")); + REQUIRE((view.find_column_name(&UserViewSchemaTests::name) && + *view.find_column_name(&UserViewSchemaTests::name) == "name")); } } #endif From 234e5f99301e16162c7587b78fcc35ffed54c36b Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 2 May 2026 21:04:46 +0300 Subject: [PATCH 46/49] Use github action lukka/run-vcpkg with only the major version 11 again --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e36e6f0e0..3c014e19a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -67,7 +67,7 @@ jobs: uses: actions/checkout@v4 - name: Setup vcpkg - uses: lukka/run-vcpkg@v11.6 + uses: lukka/run-vcpkg@v11 with: vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }} vcpkgDirectory: ${{ runner.temp }}/vcpkg @@ -140,7 +140,7 @@ jobs: sudo apt-get install -y ${{ matrix.compiler_package }} - name: Setup vcpkg - uses: lukka/run-vcpkg@v11.6 + uses: lukka/run-vcpkg@v11 with: vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }} vcpkgDirectory: ${{ runner.temp }}/vcpkg @@ -179,7 +179,7 @@ jobs: uses: actions/checkout@v4 - name: Setup vcpkg - uses: lukka/run-vcpkg@v11.6 + uses: lukka/run-vcpkg@v11 with: vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }} vcpkgDirectory: ${{ runner.temp }}/vcpkg From d8d39ed130bbedfebe6991205f2fba1af7a54363 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sat, 2 May 2026 21:06:59 +0300 Subject: [PATCH 47/49] Renamed `dbo_name_t` -> `dbo_name_literal` --- dev/schema/dbo_name.h | 12 ++++++------ include/sqlite_orm/sqlite_orm.h | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dev/schema/dbo_name.h b/dev/schema/dbo_name.h index 5d21fc330..366f66e79 100644 --- a/dev/schema/dbo_name.h +++ b/dev/schema/dbo_name.h @@ -27,8 +27,8 @@ namespace sqlite_orm::internal { * has no linkage), so a self-contained fixed-size byte array is required. */ template - struct dbo_name_t : cstring_literal { - constexpr dbo_name_t(const char (&cstr)[N]) : cstring_literal{cstr} {} + struct dbo_name_literal : cstring_literal { + constexpr dbo_name_literal(const char (&cstr)[N]) : cstring_literal{cstr} {} constexpr auto name() const noexcept { return this->cstr; @@ -39,13 +39,13 @@ namespace sqlite_orm::internal { constexpr bool is_dbo_name_v = false; template - constexpr bool is_dbo_name_v> = true; + constexpr bool is_dbo_name_v> = true; template using is_dbo_name = std::bool_constant>; /** - * Returns the database object name carried by the `dbo_name_t<…>` element of `annotations`, + * Returns the database object name carried by the `dbo_name_literal<…>` element of `annotations`, * or the type's reflected identifier when no such element is present. */ template @@ -60,7 +60,7 @@ namespace sqlite_orm::internal { } /** - * Returns a copy of `tuple` with all `dbo_name_t<…>` elements removed. + * Returns a copy of `tuple` with all `dbo_name_literal<…>` elements removed. */ template constexpr auto filter_out_dbo_name(Tuple&& tuple) { @@ -77,7 +77,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * the name falls back to `T`'s reflected identifier. */ template - constexpr internal::dbo_name_t dbo_name(const char (&dboName)[N]) { + constexpr internal::dbo_name_literal dbo_name(const char (&dboName)[N]) { return {dboName}; } } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 1f6dc59a5..a8db4dde1 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -13902,8 +13902,8 @@ namespace sqlite_orm::internal { * has no linkage), so a self-contained fixed-size byte array is required. */ template - struct dbo_name_t : cstring_literal { - constexpr dbo_name_t(const char (&cstr)[N]) : cstring_literal{cstr} {} + struct dbo_name_literal : cstring_literal { + constexpr dbo_name_literal(const char (&cstr)[N]) : cstring_literal{cstr} {} constexpr auto name() const noexcept { return this->cstr; @@ -13914,13 +13914,13 @@ namespace sqlite_orm::internal { constexpr bool is_dbo_name_v = false; template - constexpr bool is_dbo_name_v> = true; + constexpr bool is_dbo_name_v> = true; template using is_dbo_name = std::bool_constant>; /** - * Returns the database object name carried by the `dbo_name_t<…>` element of `annotations`, + * Returns the database object name carried by the `dbo_name_literal<…>` element of `annotations`, * or the type's reflected identifier when no such element is present. */ template @@ -13935,7 +13935,7 @@ namespace sqlite_orm::internal { } /** - * Returns a copy of `tuple` with all `dbo_name_t<…>` elements removed. + * Returns a copy of `tuple` with all `dbo_name_literal<…>` elements removed. */ template constexpr auto filter_out_dbo_name(Tuple&& tuple) { @@ -13952,7 +13952,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * the name falls back to `T`'s reflected identifier. */ template - constexpr internal::dbo_name_t dbo_name(const char (&dboName)[N]) { + constexpr internal::dbo_name_literal dbo_name(const char (&dboName)[N]) { return {dboName}; } } From 1bc8fbd77708c8e0171cb5d26af58445f68e54c4 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 10 May 2026 14:47:43 +0300 Subject: [PATCH 48/49] Updated wording of function documentation --- dev/conditions.h | 4 ++-- dev/storage_base.h | 12 ++++++------ include/sqlite_orm/sqlite_orm.h | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/dev/conditions.h b/dev/conditions.h index 0b2eddcfc..fce081091 100644 --- a/dev/conditions.h +++ b/dev/conditions.h @@ -1021,8 +1021,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. */ [[deprecated("Use the hidden FTS5 rank column instead")]] - inline internal::order_by_t order_by(internal::rank_t o) { - return {std::move(o)}; + inline internal::order_by_t order_by(internal::rank_t expression) { + return {std::move(expression)}; } /** diff --git a/dev/storage_base.h b/dev/storage_base.h index c1ecfbaae..3b1754d10 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -311,27 +311,27 @@ namespace sqlite_orm::internal { #endif /** - * Returns the names of existing permanent view in the database. Doesn't check storage itself - works only with + * Returns the names of existing permanent views in the database. Doesn't check storage itself - works only with * actual database. - * @return Returns list of tables in database. + * @return Returns a list of views in the database. */ std::vector view_names() { return this->object_names("view"); } /** - * Returns existing permanent table names in database. Doesn't check storage itself - works only with + * Returns the names of existing permanent tables in the database. Doesn't check storage itself - works only with * actual database. - * @return Returns list of tables in database. + * @return Returns a list of tables in the database. */ std::vector table_names() { return this->object_names("table"); } /** - * Returns existing permanent trigger names in database. Doesn't check storage itself - works only with + * Returns the names of existing permanent triggers in the database. Doesn't check storage itself - works only with * actual database. - * @return Returns list of triggers in database. + * @return Returns a list of triggers in the database. */ std::vector trigger_names() { return this->object_names("trigger"); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 2ccbda7f7..10cf6d297 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -6519,8 +6519,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * [Deprecation notice] This expression factory function is deprecated and will be removed in v1.11. */ [[deprecated("Use the hidden FTS5 rank column instead")]] - inline internal::order_by_t order_by(internal::rank_t o) { - return {std::move(o)}; + inline internal::order_by_t order_by(internal::rank_t expression) { + return {std::move(expression)}; } /** @@ -20089,27 +20089,27 @@ namespace sqlite_orm::internal { #endif /** - * Returns the names of existing permanent view in the database. Doesn't check storage itself - works only with + * Returns the names of existing permanent views in the database. Doesn't check storage itself - works only with * actual database. - * @return Returns list of tables in database. + * @return Returns a list of views in the database. */ std::vector view_names() { return this->object_names("view"); } /** - * Returns existing permanent table names in database. Doesn't check storage itself - works only with + * Returns the names of existing permanent tables in the database. Doesn't check storage itself - works only with * actual database. - * @return Returns list of tables in database. + * @return Returns a list of tables in the database. */ std::vector table_names() { return this->object_names("table"); } /** - * Returns existing permanent trigger names in database. Doesn't check storage itself - works only with + * Returns the names of existing permanent triggers in the database. Doesn't check storage itself - works only with * actual database. - * @return Returns list of triggers in database. + * @return Returns a list of triggers in the database. */ std::vector trigger_names() { return this->object_names("trigger"); From d70537567df3d2428d2b3b017dbc4a592fb2c5f9 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Sun, 10 May 2026 16:41:59 +0300 Subject: [PATCH 49/49] Added string literal operator template for the query view name annotation --- dev/schema/dbo_name.h | 19 ++++++++++--- dev/schema/view.h | 8 +++--- examples/view.cpp | 8 +++--- include/sqlite_orm/sqlite_orm.h | 27 ++++++++++++++----- tests/logger_tests.cpp | 2 +- tests/schema/view_tests.cpp | 14 ++++++++-- .../schema/view.cpp | 2 +- tests/static_tests/view_static_tests.cpp | 15 ++++++----- tests/storage_tests.cpp | 2 +- tests/view_tests.cpp | 10 +++---- 10 files changed, 73 insertions(+), 34 deletions(-) diff --git a/dev/schema/dbo_name.h b/dev/schema/dbo_name.h index 366f66e79..5f409d6be 100644 --- a/dev/schema/dbo_name.h +++ b/dev/schema/dbo_name.h @@ -70,14 +70,27 @@ namespace sqlite_orm::internal { } SQLITE_ORM_EXPORT namespace sqlite_orm { + inline namespace literals { + /** + * Database object name annotation factory. + * Use as a class-scope annotation: + * `struct [[="users"_dbo_name]] User { ... };` + * `struct [[= sqlite_orm::operator""_dbo_name<"users">()]] User { ... };` + * `make_view()` consumes this annotation. + */ + template + [[nodiscard]] consteval auto operator""_dbo_name() { + return dboName; + } + } + /** * Database object name annotation factory. * Use as a class-scope annotation: `struct [[=dbo_name("users")]] User { ... };`. - * Both `make_table()` and `make_view()` consume this annotation; when absent - * the name falls back to `T`'s reflected identifier. + * `make_view()` consumes this annotation. */ template - constexpr internal::dbo_name_literal dbo_name(const char (&dboName)[N]) { + consteval internal::dbo_name_literal dbo_name(const char (&dboName)[N]) { return {dboName}; } } diff --git a/dev/schema/view.h b/dev/schema/view.h index d220bb666..2a5ab5905 100644 --- a/dev/schema/view.h +++ b/dev/schema/view.h @@ -33,10 +33,10 @@ namespace sqlite_orm::internal { }; template - inline constexpr bool is_view_v = polyfill::is_specialization_of_v; + constexpr bool is_view_v = polyfill::is_specialization_of_v; #else template - inline constexpr bool is_view_v = false; + constexpr bool is_view_v = false; #endif template @@ -71,7 +71,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * Factory function for a view definition. * * The mapped object type is explicitly specified, columns and their names are deferred from the object type. - * The object type must be an aggregate. The optional `[[=dbo_name("…")]]` class-scope annotation overrides + * The object type must be an aggregate. The optional `[[="…"_dbo_name]]` class-scope annotation overrides * the view name (otherwise the type's reflected identifier is used). */ template @@ -90,7 +90,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * Factory function for a view definition. * * The mapped object type is explicitly specified, columns and their names are deferred from the object type. - * The object type must be an aggregate. The optional `[[=dbo_name("…")]]` class-scope annotation overrides + * The object type must be an aggregate. The optional `[[="…"_dbo_name]]` class-scope annotation overrides * the view name (otherwise the type's reflected identifier is used). */ template diff --git a/examples/view.cpp b/examples/view.cpp index 1f32577d2..b70d84db2 100644 --- a/examples/view.cpp +++ b/examples/view.cpp @@ -45,21 +45,21 @@ struct Department { // The fields are automatically mapped through C++ reflection // View 1: High earners (employees earning more than 60000) -struct[[= dbo_name("high_earners")]] HighEarner { +struct[[= "high_earners"_dbo_name]] HighEarner { int64 id; std::string name; double salary; }; // View 2: Department summary with employee count and average salary -struct[[= dbo_name("department_summary")]] DepartmentSummary { +struct[[= "department_summary"_dbo_name]] DepartmentSummary { std::string department_name; int employee_count; double avg_salary; }; // View 3: Complete employee information with department details (join result) -struct[[= dbo_name("employee_details")]] EmployeeDetail { +struct[[= "employee_details"_dbo_name]] EmployeeDetail { int64 id; std::string employee_name; double salary; @@ -84,7 +84,7 @@ inline auto initStorage(const std::string& path) { // Define views - notice how we only specify the SELECT statement. // The column mappings and view name are derived from the view object type // (column names from non-static data members; view name from the optional - // `[[=dbo_name("…")]]` annotation, falling back to the type's identifier). + // `[[="…"_dbo_name]]` annotation, falling back to the type's identifier). // View 1: Filter high earners make_view( diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index d8e84c963..4a14523fd 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -13953,14 +13953,27 @@ namespace sqlite_orm::internal { } SQLITE_ORM_EXPORT namespace sqlite_orm { + inline namespace literals { + /** + * Database object name annotation factory. + * Use as a class-scope annotation: + * `struct [[="users"_dbo_name]] User { ... };` + * `struct [[= sqlite_orm::operator""_dbo_name<"users">()]] User { ... };` + * `make_view()` consumes this annotation. + */ + template + [[nodiscard]] consteval auto operator""_dbo_name() { + return dboName; + } + } + /** * Database object name annotation factory. * Use as a class-scope annotation: `struct [[=dbo_name("users")]] User { ... };`. - * Both `make_table()` and `make_view()` consume this annotation; when absent - * the name falls back to `T`'s reflected identifier. + * `make_view()` consumes this annotation. */ template - constexpr internal::dbo_name_literal dbo_name(const char (&dboName)[N]) { + consteval internal::dbo_name_literal dbo_name(const char (&dboName)[N]) { return {dboName}; } } @@ -13982,10 +13995,10 @@ namespace sqlite_orm::internal { }; template - inline constexpr bool is_view_v = polyfill::is_specialization_of_v; + constexpr bool is_view_v = polyfill::is_specialization_of_v; #else template - inline constexpr bool is_view_v = false; + constexpr bool is_view_v = false; #endif template @@ -14020,7 +14033,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * Factory function for a view definition. * * The mapped object type is explicitly specified, columns and their names are deferred from the object type. - * The object type must be an aggregate. The optional `[[=dbo_name("…")]]` class-scope annotation overrides + * The object type must be an aggregate. The optional `[[="…"_dbo_name]]` class-scope annotation overrides * the view name (otherwise the type's reflected identifier is used). */ template @@ -14039,7 +14052,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { * Factory function for a view definition. * * The mapped object type is explicitly specified, columns and their names are deferred from the object type. - * The object type must be an aggregate. The optional `[[=dbo_name("…")]]` class-scope annotation overrides + * The object type must be an aggregate. The optional `[[="…"_dbo_name]]` class-scope annotation overrides * the view name (otherwise the type's reflected identifier is used). */ template diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index f94f59458..0b7508145 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -26,7 +26,7 @@ namespace { #ifdef SQLITE_ORM_WITH_VIEW #ifdef SQLITE_ORM_REFLECTION_SUPPORTED - struct[[= dbo_name("users_view")]] UserViewLoggerTests { + struct[[= "users_view"_dbo_name]] UserViewLoggerTests { int id = 0; std::string name; }; diff --git a/tests/schema/view_tests.cpp b/tests/schema/view_tests.cpp index 03c6895d0..ec56414dc 100644 --- a/tests/schema/view_tests.cpp +++ b/tests/schema/view_tests.cpp @@ -6,7 +6,12 @@ using namespace sqlite_orm; namespace { - struct[[= dbo_name("user_view")]] UserViewSchemaTests { + struct[[= "user_view"_dbo_name]] UserViewSchemaTests { + int64 id = 0; + std::string name; + }; + + struct[[= dbo_name("user_view")]] UserViewSchemaTests2 { int64 id = 0; std::string name; }; @@ -23,11 +28,16 @@ TEST_CASE("view make_view name resolution") { std::string name; }; - SECTION("[[=dbo_name(...)]] annotation supplies the view name") { + SECTION("annotation supplies the view name") { auto view = make_view(select(asterisk())); REQUIRE(view.name == "user_view"); } + SECTION("annotation supplies the view name 2") { + auto view = make_view(select(asterisk())); + REQUIRE(view.name == "user_view"); + } + SECTION("fallback to type identifier") { auto view = make_view(select(asterisk())); REQUIRE(view.name == "UserViewSchemaTestsDefaultName"); diff --git a/tests/statement_serializer_tests/schema/view.cpp b/tests/statement_serializer_tests/schema/view.cpp index 31eb011f4..2d2509629 100644 --- a/tests/statement_serializer_tests/schema/view.cpp +++ b/tests/statement_serializer_tests/schema/view.cpp @@ -7,7 +7,7 @@ using namespace sqlite_orm; using internal::serialize; namespace { - struct[[= dbo_name("user_view")]] UserViewSerializerTests { + struct[[= "user_view"_dbo_name]] UserViewSerializerTests { int id = 0; std::string name; }; diff --git a/tests/static_tests/view_static_tests.cpp b/tests/static_tests/view_static_tests.cpp index 62b3cca89..91a1ad8e7 100644 --- a/tests/static_tests/view_static_tests.cpp +++ b/tests/static_tests/view_static_tests.cpp @@ -8,9 +8,12 @@ using internal::col_index_sequence_of, internal::col_index_sequence_with_field_t using internal::is_column; namespace { - struct[[= dbo_name("user_view")]] UserViewStaticTests { - int id = 0; + struct[[= "user_view"_dbo_name]] UserViewStaticTests { + int64 id = 0; std::string name; + + private: + std::string _privateDummy; }; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES constexpr orm_table_reference auto user_view = c(); @@ -19,7 +22,7 @@ namespace { TEST_CASE("view static count_of()") { struct User { - int id = 0; + int64 id = 0; std::string name; }; @@ -28,7 +31,7 @@ TEST_CASE("view static count_of()") { using elements_type = decltype(view.elements); STATIC_REQUIRE(view.count_of() == 2); STATIC_REQUIRE(col_index_sequence_of::size() == 2); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); } SECTION("derived") { struct DerivedUserView : UserViewStaticTests {}; @@ -37,7 +40,7 @@ TEST_CASE("view static count_of()") { using elements_type = decltype(view.elements); STATIC_REQUIRE(view.count_of() == 2); STATIC_REQUIRE(col_index_sequence_of::size() == 2); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES SECTION("table reference") { @@ -45,7 +48,7 @@ TEST_CASE("view static count_of()") { using elements_type = decltype(view.elements); STATIC_REQUIRE(view.count_of() == 2); STATIC_REQUIRE(col_index_sequence_of::size() == 2); - STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); + STATIC_REQUIRE(col_index_sequence_with_field_type::size() == 1); } #endif } diff --git a/tests/storage_tests.cpp b/tests/storage_tests.cpp index bc4265eca..7e93edc75 100644 --- a/tests/storage_tests.cpp +++ b/tests/storage_tests.cpp @@ -271,7 +271,7 @@ namespace { constexpr char usersViewName[] = "users_view"; struct[[= dbo_name(usersViewName)]] UserViewDropViewTests { - int id = 0; + int64 id = 0; std::string name; }; } diff --git a/tests/view_tests.cpp b/tests/view_tests.cpp index 276493dd5..b01dcf77c 100644 --- a/tests/view_tests.cpp +++ b/tests/view_tests.cpp @@ -7,8 +7,8 @@ using namespace sqlite_orm; namespace { - struct[[= dbo_name("user_view")]] UserViewTests { - int id = 0; + struct[[= "user_view"_dbo_name]] UserViewTests { + int64 id = 0; std::string name; #ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED @@ -20,7 +20,7 @@ namespace { #endif }; - struct[[= dbo_name("user_view")]] UserView2Tests { + struct[[= "user_view"_dbo_name]] UserView2Tests { std::string name; #ifdef SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED @@ -37,7 +37,7 @@ TEST_CASE("view") { using Catch::Matchers::UnorderedEquals; struct User { - int id = 0; + int64 id = 0; std::string name; }; @@ -94,7 +94,7 @@ TEST_CASE("view") { TEST_CASE("view sync") { struct User { - int id = 0; + int64 id = 0; std::string name; };