diff --git a/dev/connection_holder.h b/dev/connection_holder.h index fad35c96a..59d093858 100644 --- a/dev/connection_holder.h +++ b/dev/connection_holder.h @@ -3,6 +3,7 @@ #include #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include +#include // std::function #include // std::string #endif @@ -12,25 +13,38 @@ namespace sqlite_orm { namespace internal { struct connection_holder { - connection_holder(std::string filename) : filename(std::move(filename)) {} + connection_holder(std::string filename, std::function didOpenDb) : + _didOpenDb{std::move(didOpenDb)}, filename(std::move(filename)) {} + + connection_holder(const connection_holder&) = delete; + + connection_holder(const connection_holder& other, std::function didOpenDb) : + _didOpenDb{std::move(didOpenDb)}, filename{other.filename} {} void retain() { // first one opens the connection. // we presume that the connection is opened once in a single-threaded context [also open forever]. // therefore we can just use an atomic increment but don't need sequencing due to `prevCount > 0`. - if (this->_retain_count.fetch_add(1, std::memory_order_relaxed) == 0) { - int rc = sqlite3_open(this->filename.c_str(), &this->db); + if (_retainCount.fetch_add(1, std::memory_order_relaxed) == 0) { + int rc = sqlite3_open_v2(this->filename.c_str(), + &this->db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + nullptr); if (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY /*possible, but unexpected*/ { throw_translated_sqlite_error(this->db); } + + if (_didOpenDb) { + _didOpenDb(this->db); + } } } void release() { // last one closes the connection. // we assume that this might happen by any thread, therefore the counter must serve as a synchronization point. - if (this->_retain_count.fetch_sub(1, std::memory_order_acq_rel) == 1) { - int rc = sqlite3_close(this->db); + if (_retainCount.fetch_sub(1, std::memory_order_acq_rel) == 1) { + int rc = sqlite3_close_v2(this->db); if (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY { throw_translated_sqlite_error(this->db); } else { @@ -48,16 +62,20 @@ namespace sqlite_orm { * @attention While retrieving the reference count value is atomic it makes only sense at single-threaded points in code. */ int retain_count() const { - return this->_retain_count.load(std::memory_order_relaxed); + return _retainCount.load(std::memory_order_relaxed); } - const std::string filename; - protected: sqlite3* db = nullptr; private: - std::atomic_int _retain_count{}; + std::atomic_int _retainCount{}; + + private: + const std::function _didOpenDb; + + public: + const std::string filename; }; struct connection_ref { diff --git a/dev/functional/cxx_core_features.h b/dev/functional/cxx_core_features.h index 93977a4f2..7bb4d2a06 100644 --- a/dev/functional/cxx_core_features.h +++ b/dev/functional/cxx_core_features.h @@ -49,9 +49,12 @@ #define SQLITE_ORM_STRUCTURED_BINDINGS_SUPPORTED #endif +#if __cpp_deduction_guides >= 201703L +#define SQLITE_ORM_CTAD_SUPPORTED +#endif + #if __cpp_generic_lambdas >= 201707L #define SQLITE_ORM_EXPLICIT_GENERIC_LAMBDA_SUPPORTED -#else #endif #if __cpp_init_captures >= 201803L diff --git a/dev/functional/mpl.h b/dev/functional/mpl.h index f2d196dee..108f3ccd6 100644 --- a/dev/functional/mpl.h +++ b/dev/functional/mpl.h @@ -453,7 +453,7 @@ namespace sqlite_orm { * Commonly used named abbreviation for `check_if`. */ template - using check_if_is_type = mpl::bind_front_fn; + using check_if_is_type = check_if; /* * Quoted trait metafunction that checks if a type's template matches the specified template @@ -463,6 +463,18 @@ namespace sqlite_orm { using check_if_is_template = mpl::pass_extracted_fn_to>>; + /* + * Quoted trait metafunction that checks if a type names a nested type determined by `Op`. + */ + template class Op> + using check_if_names = mpl::bind_front_higherorder_fn; + + /* + * Quoted trait metafunction that checks if a type does not name a nested type determined by `Op`. + */ + template class Op> + using check_if_lacks = mpl::not_>; + /* * Quoted metafunction that finds the index of the given type in a tuple. */ diff --git a/dev/pragma.h b/dev/pragma.h index d1868fae2..aef4dc9e4 100644 --- a/dev/pragma.h +++ b/dev/pragma.h @@ -258,11 +258,12 @@ namespace sqlite_orm { } void set_pragma_impl(const std::string& query, sqlite3* db = nullptr) { - auto con = this->get_connection(); - if (db == nullptr) { - db = con.get(); + if (db) { + perform_void_exec(db, query); + } else { + auto con = this->get_connection(); + perform_void_exec(con.get(), query); } - perform_void_exec(db, query); } }; } diff --git a/dev/storage.h b/dev/storage.h index b9bedbf82..04011a9b8 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -81,21 +81,39 @@ namespace sqlite_orm { polyfill::void_t().prepare(std::declval()))>>> = true; + template + decltype(auto) storage_opt_or_default(OptionsTpl& options) { +#ifdef SQLITE_ORM_CTAD_SUPPORTED + if constexpr (tuple_has_type::value) { + return std::move(std::get(options)); + } else { + return Opt{}; + } +#else + return Opt{}; +#endif + } + /** * Storage class itself. Create an instanse to use it as an interfacto to sqlite db by calling `make_storage` * function. */ template struct storage_t : storage_base { - using self = storage_t; + using self_type = storage_t; using db_objects_type = db_objects_tuple; /** * @param filename database filename. * @param dbObjects db_objects_tuple */ - storage_t(std::string filename, db_objects_type dbObjects) : - storage_base{std::move(filename), foreign_keys_count(dbObjects)}, db_objects{std::move(dbObjects)} {} + template + storage_t(std::string filename, db_objects_type dbObjects, OptionsTpl options) : + storage_base{std::move(filename), + storage_opt_or_default(options), + storage_opt_or_default(options), + foreign_keys_count(dbObjects)}, + db_objects{std::move(dbObjects)} {} storage_t(const storage_t&) = default; @@ -114,7 +132,7 @@ namespace sqlite_orm { * * Hence, friend was replaced by `obtain_db_objects()` and `pick_const_impl()`. */ - friend const db_objects_type& obtain_db_objects(const self& storage) noexcept { + friend const db_objects_type& obtain_db_objects(const self_type& storage) noexcept { return storage.db_objects; } @@ -246,7 +264,7 @@ namespace sqlite_orm { public: template, class... Args> - mapped_view iterate(Args&&... args) { + mapped_view iterate(Args&&... args) { this->assert_mapped_type(); auto con = this->get_connection(); @@ -781,7 +799,7 @@ namespace sqlite_orm { std::enable_if_t::value && !is_mapped::value, bool> = true> std::string dump(E&& expression, bool parametrized = false) const { - static_assert(is_preparable_v, "Expression must be a high-level statement"); + static_assert(is_preparable_v, "Expression must be a high-level statement"); decltype(auto) e2 = static_if::value>( [](auto expression) -> auto { @@ -1702,17 +1720,50 @@ namespace sqlite_orm { } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED }; // struct storage_t + +#ifdef SQLITE_ORM_CTAD_SUPPORTED + template + using dbo_index_sequence = filter_tuple_sequence_t::template fn>; + + template + using opt_index_sequence = filter_tuple_sequence_t::template fn>; + + template + storage_t make_storage(std::string filename, std::tuple dbObjects, OptionsTpl options) { + return {std::move(filename), std::move(dbObjects), std::move(options)}; + } +#endif } } SQLITE_ORM_EXPORT namespace sqlite_orm { +#ifdef SQLITE_ORM_CTAD_SUPPORTED + /* + * Factory function for a storage instance, from a database file, a set of database object definitions + * and option storage options like connection control options and an 'on open' callback. + * + * E.g. + * auto storage = make_storage("", connection_control{.open_forever = true}, on_open([](sqlite3* db) {})); + */ + template + auto make_storage(std::string filename, Spec... specifications) { + using namespace ::sqlite_orm::internal; + + std::tuple specTuple{std::forward(specifications)...}; + return internal::make_storage( + std::move(filename), + create_from_tuple(std::move(specTuple), dbo_index_sequence{}), + create_from_tuple(std::move(specTuple), opt_index_sequence{})); + } +#else /* - * Factory function for a storage, from a database file and a bunch of database object definitions. + * Factory function for a storage instance, from a database file and a bunch of database object definitions. */ template internal::storage_t make_storage(std::string filename, DBO... dbObjects) { - return {std::move(filename), internal::db_objects_tuple{std::forward(dbObjects)...}}; + return {std::move(filename), {std::forward(dbObjects)...}, std::tuple<>{}}; } +#endif /** * sqlite3_threadsafe() interface. diff --git a/dev/storage_base.h b/dev/storage_base.h index cfc0638ad..9ee3c0a1a 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -14,7 +14,7 @@ #include // std::list #include // std::make_unique, std::unique_ptr #include // std::map -#include // std::is_same +#include // std::is_same, std::is_aggregate #include // std::find_if, std::ranges::find #endif @@ -34,9 +34,9 @@ #include "udf_proxy.h" #include "serializing_util.h" #include "table_info.h" +#include "storage_options.h" namespace sqlite_orm { - namespace internal { struct storage_base { @@ -289,20 +289,19 @@ namespace sqlite_orm { * needed and closes when it is not needed. This function breaks this rule. In memory storage always * keeps connection opened so calling this for in-memory storage changes nothing. * Note about multithreading: in multithreading context avoiding using this function for not in-memory - * storage may lead to data races. If you have data races in such a configuration try to call `open_forever` + * storage may lead to data races. If you have data races in such a configuration try to call `open_forever()` * before accessing your storage - it may fix data races. */ void open_forever() { - this->isOpenedForever = true; - this->connection->retain(); - if (1 == this->connection->retain_count()) { - this->on_open_internal(this->connection->get()); + if (!this->isOpenedForever) { + this->isOpenedForever = true; + this->connection->retain(); } } /** * Create an application-defined scalar SQL function. - * Can be called at any time no matter whether the database connection is opened or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * 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. @@ -345,7 +344,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Create an application-defined scalar function. - * Can be called at any time no matter whether the database connection is opened or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * 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. @@ -360,7 +359,7 @@ namespace sqlite_orm { /** * Create an application-defined scalar function. - * Can be called at any time no matter whether the database connection is opened or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * 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; @@ -401,7 +400,7 @@ namespace sqlite_orm { /** * Create an application-defined aggregate SQL function. - * Can be called at any time no matter whether the database connection is opened or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * 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. @@ -450,7 +449,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Create an application-defined aggregate function. - * Can be called at any time no matter whether the database connection is opened or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * 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. @@ -465,7 +464,7 @@ namespace sqlite_orm { /** * Delete a scalar function you created before. - * Can be called at any time no matter whether the database connection is open or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. */ template void delete_scalar_function() { @@ -477,7 +476,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Delete a scalar function you created before. - * Can be called at any time no matter whether the database connection is open or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. */ template void delete_scalar_function() { @@ -486,7 +485,7 @@ namespace sqlite_orm { /** * Delete a quoted scalar function you created before. - * Can be called at any time no matter whether the database connection is open or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. */ template void delete_scalar_function() { @@ -496,7 +495,7 @@ namespace sqlite_orm { /** * Delete aggregate function you created before. - * Can be called at any time no matter whether the database connection is open or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. */ template void delete_aggregate_function() { @@ -610,7 +609,7 @@ namespace sqlite_orm { } backup_t make_backup_to(const std::string& filename) { - auto holder = std::make_unique(filename); + auto holder = std::make_unique(filename, nullptr); connection_ref conRef{*holder}; return {conRef, "main", this->get_connection(), "main", std::move(holder)}; } @@ -620,7 +619,7 @@ namespace sqlite_orm { } backup_t make_backup_from(const std::string& filename) { - auto holder = std::make_unique(filename); + auto holder = std::make_unique(filename, nullptr); connection_ref conRef{*holder}; return {this->get_connection(), "main", conRef, "main", std::move(holder)}; } @@ -664,26 +663,39 @@ namespace sqlite_orm { } protected: - storage_base(std::string filename, int foreignKeysCount) : + storage_base(std::string filename, + connection_control connectionCtrl, + on_open_spec onOpenSpec, + int foreignKeysCount) : + on_open{std::move(onOpenSpec.onOpen)}, isOpenedForever{connectionCtrl.open_forever}, pragma(std::bind(&storage_base::get_connection, this)), limit(std::bind(&storage_base::get_connection, this)), inMemory(filename.empty() || filename == ":memory:"), - connection(std::make_unique(std::move(filename))), + connection(std::make_unique( + std::move(filename), + std::bind(&storage_base::on_open_internal, this, std::placeholders::_1))), cachedForeignKeysCount(foreignKeysCount) { if (this->inMemory) { this->connection->retain(); - this->on_open_internal(this->connection->get()); + } + if (this->isOpenedForever) { + this->connection->retain(); } } storage_base(const storage_base& other) : on_open(other.on_open), pragma(std::bind(&storage_base::get_connection, this)), limit(std::bind(&storage_base::get_connection, this)), inMemory(other.inMemory), - connection(std::make_unique(other.connection->filename)), + isOpenedForever{other.isOpenedForever}, + connection(std::make_unique( + *other.connection, + std::bind(&storage_base::on_open_internal, this, std::placeholders::_1))), cachedForeignKeysCount(other.cachedForeignKeysCount) { if (this->inMemory) { this->connection->retain(); - this->on_open_internal(this->connection->get()); + } + if (this->isOpenedForever) { + this->connection->retain(); } } @@ -698,18 +710,12 @@ namespace sqlite_orm { void begin_transaction_internal(const std::string& query) { this->connection->retain(); - if (1 == this->connection->retain_count()) { - this->on_open_internal(this->connection->get()); - } sqlite3* db = this->connection->get(); perform_void_exec(db, query); } connection_ref get_connection() { connection_ref res{*this->connection}; - if (1 == this->connection->retain_count()) { - this->on_open_internal(this->connection->get()); - } return res; } @@ -725,17 +731,17 @@ namespace sqlite_orm { perform_exec(db, "PRAGMA foreign_keys", extract_single_value, &result); return result; } - #endif - void on_open_internal(sqlite3* db) { + void on_open_internal(sqlite3* db) { #if SQLITE_VERSION_NUMBER >= 3006019 if (this->cachedForeignKeysCount) { this->foreign_keys(db, true); } #endif + if (this->pragma.synchronous_ != -1) { - this->pragma.synchronous(this->pragma.synchronous_); + this->pragma.set_pragma("synchronous", this->pragma.synchronous_, db); } if (this->pragma.journal_mode_ != -1) { @@ -995,8 +1001,8 @@ namespace sqlite_orm { }); #endif if (dbColumnInfoIt != dbTableInfo.end()) { - auto& dbColumnInfo = *dbColumnInfoIt; - auto columnsAreEqual = + table_xinfo& dbColumnInfo = *dbColumnInfoIt; + bool columnsAreEqual = dbColumnInfo.name == storageColumnInfo.name && dbColumnInfo.notnull == storageColumnInfo.notnull && (!dbColumnInfo.dflt_value.empty()) == (!storageColumnInfo.dflt_value.empty()) && @@ -1007,8 +1013,7 @@ namespace sqlite_orm { break; } dbTableInfo.erase(dbColumnInfoIt); - storageTableInfo.erase(storageTableInfo.begin() + - static_cast(storageColumnInfoIndex)); + storageTableInfo.erase(storageTableInfo.begin() + storageColumnInfoIndex); --storageColumnInfoIndex; } else { columnsToAdd.push_back(&storageColumnInfo); diff --git a/dev/storage_impl.h b/dev/storage_impl.h index 81537fdcc..b90efd1e5 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -70,17 +70,15 @@ namespace sqlite_orm { constexpr decltype(auto) materialize_column_pointer(const DBOs&, const column_pointer>&) { using table_type = storage_pick_table_t; - using cte_mapper_type = cte_mapper_type_t; + using cte_colrefs_tuple = typename cte_mapper_type_t::final_colrefs_tuple; + using cte_fields_type = typename cte_mapper_type_t::fields_type; // lookup ColAlias in the final column references - using colalias_index = - find_tuple_type>; - static_assert(colalias_index::value < std::tuple_size_v, + using colalias_index = find_tuple_type>; + static_assert(colalias_index::value < std::tuple_size_v, "No such column mapped into the CTE."); - return &aliased_field< - ColAlias, - std::tuple_element_t>::field; + return &aliased_field>::field; } #endif @@ -104,14 +102,13 @@ namespace sqlite_orm { constexpr decltype(auto) find_column_name(const DBOs& dboObjects, const column_pointer>&) { using table_type = storage_pick_table_t; - using cte_mapper_type = cte_mapper_type_t; + using cte_colrefs_tuple = typename cte_mapper_type_t::final_colrefs_tuple; using column_index_sequence = filter_tuple_sequence_t, is_column>; // note: even though the columns contain the [`aliased_field<>::*`] we perform the lookup using the column references. // lookup ColAlias in the final column references - using colalias_index = - find_tuple_type>; - static_assert(colalias_index::value < std::tuple_size_v, + using colalias_index = find_tuple_type>; + 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; diff --git a/dev/storage_options.h b/dev/storage_options.h new file mode 100644 index 000000000..54f310087 --- /dev/null +++ b/dev/storage_options.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#ifdef SQLITE_ORM_IMPORT_STD_MODULE +#include +#else +#include // std::is_aggregate +#include // std::move +#include // std::function +#endif + +namespace sqlite_orm { + namespace internal { + template + using storage_opt_tag_t = typename T::storage_opt_tag; + + struct on_open_spec { + using storage_opt_tag = int; + + std::function onOpen; + }; + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Database connection control options to be passed to `make_storage()`. + */ + struct connection_control { + /// Whether to open the database once and for all. + /// Required if using a 'storage' instance from multiple threads. + bool open_forever = false; + + using storage_opt_tag = int; + }; +#if __cpp_lib_is_aggregate >= 201703L + // design choice: must be an aggregate that can be constructed using designated initializers + static_assert(std::is_aggregate_v); +#endif + +#ifdef SQLITE_ORM_CTAD_SUPPORTED + /** + * Callback function to be passed to `make_storage()`. + * The provided function is called immdediately after the database connection has been established and set up. + */ + inline internal::on_open_spec on_open(std::function onOpen) { + return {std::move(onOpen)}; + } +#endif +} diff --git a/dev/tuple_helper/tuple_transformer.h b/dev/tuple_helper/tuple_transformer.h index 55d940d5a..57fef52d5 100644 --- a/dev/tuple_helper/tuple_transformer.h +++ b/dev/tuple_helper/tuple_transformer.h @@ -103,7 +103,7 @@ namespace sqlite_orm { } /* - * Like `std::make_from_tuple`, but using a projection on the tuple elements. + * Like `std::make_from_tuple()`, but using a projection on the tuple elements. */ template constexpr R create_from_tuple(Tpl&& tpl, Projection project = {}) { @@ -112,5 +112,23 @@ namespace sqlite_orm { std::make_index_sequence>::value>{}, std::forward(project)); } + +#ifdef SQLITE_ORM_CTAD_SUPPORTED + template class R, class Tpl, size_t... Idx, class Projection = polyfill::identity> + constexpr auto create_from_tuple(Tpl&& tpl, std::index_sequence, Projection project = {}) { + return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; + } + + /* + * Similar to `create_from_tuple()`, but the result type is specified as a template class. + */ + template class R, class Tpl, class Projection = polyfill::identity> + constexpr auto create_from_tuple(Tpl&& tpl, Projection project = {}) { + return create_from_tuple( + std::forward(tpl), + std::make_index_sequence>::value>{}, + std::forward(project)); + } +#endif } } diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index a9b89b850..d965117b4 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -98,9 +98,12 @@ using std::nullptr_t; #define SQLITE_ORM_STRUCTURED_BINDINGS_SUPPORTED #endif +#if __cpp_deduction_guides >= 201703L +#define SQLITE_ORM_CTAD_SUPPORTED +#endif + #if __cpp_generic_lambdas >= 201707L #define SQLITE_ORM_EXPLICIT_GENERIC_LAMBDA_SUPPORTED -#else #endif #if __cpp_init_captures >= 201803L @@ -1235,7 +1238,7 @@ namespace sqlite_orm { * Commonly used named abbreviation for `check_if`. */ template - using check_if_is_type = mpl::bind_front_fn; + using check_if_is_type = check_if; /* * Quoted trait metafunction that checks if a type's template matches the specified template @@ -1245,6 +1248,18 @@ namespace sqlite_orm { using check_if_is_template = mpl::pass_extracted_fn_to>>; + /* + * Quoted trait metafunction that checks if a type names a nested type determined by `Op`. + */ + template class Op> + using check_if_names = mpl::bind_front_higherorder_fn; + + /* + * Quoted trait metafunction that checks if a type does not name a nested type determined by `Op`. + */ + template class Op> + using check_if_lacks = mpl::not_>; + /* * Quoted metafunction that finds the index of the given type in a tuple. */ @@ -1636,7 +1651,7 @@ namespace sqlite_orm { } /* - * Like `std::make_from_tuple`, but using a projection on the tuple elements. + * Like `std::make_from_tuple()`, but using a projection on the tuple elements. */ template constexpr R create_from_tuple(Tpl&& tpl, Projection project = {}) { @@ -1645,6 +1660,24 @@ namespace sqlite_orm { std::make_index_sequence>::value>{}, std::forward(project)); } + +#ifdef SQLITE_ORM_CTAD_SUPPORTED + template class R, class Tpl, size_t... Idx, class Projection = polyfill::identity> + constexpr auto create_from_tuple(Tpl&& tpl, std::index_sequence, Projection project = {}) { + return R{polyfill::invoke(project, std::get(std::forward(tpl)))...}; + } + + /* + * Similar to `create_from_tuple()`, but the result type is specified as a template class. + */ + template class R, class Tpl, class Projection = polyfill::identity> + constexpr auto create_from_tuple(Tpl&& tpl, Projection project = {}) { + return create_from_tuple( + std::forward(tpl), + std::make_index_sequence>::value>{}, + std::forward(project)); + } +#endif } } @@ -12663,17 +12696,15 @@ namespace sqlite_orm { constexpr decltype(auto) materialize_column_pointer(const DBOs&, const column_pointer>&) { using table_type = storage_pick_table_t; - using cte_mapper_type = cte_mapper_type_t; + using cte_colrefs_tuple = typename cte_mapper_type_t::final_colrefs_tuple; + using cte_fields_type = typename cte_mapper_type_t::fields_type; // lookup ColAlias in the final column references - using colalias_index = - find_tuple_type>; - static_assert(colalias_index::value < std::tuple_size_v, + using colalias_index = find_tuple_type>; + static_assert(colalias_index::value < std::tuple_size_v, "No such column mapped into the CTE."); - return &aliased_field< - ColAlias, - std::tuple_element_t>::field; + return &aliased_field>::field; } #endif @@ -12697,14 +12728,13 @@ namespace sqlite_orm { constexpr decltype(auto) find_column_name(const DBOs& dboObjects, const column_pointer>&) { using table_type = storage_pick_table_t; - using cte_mapper_type = cte_mapper_type_t; + using cte_colrefs_tuple = typename cte_mapper_type_t::final_colrefs_tuple; using column_index_sequence = filter_tuple_sequence_t, is_column>; // note: even though the columns contain the [`aliased_field<>::*`] we perform the lookup using the column references. // lookup ColAlias in the final column references - using colalias_index = - find_tuple_type>; - static_assert(colalias_index::value < std::tuple_size_v, + using colalias_index = find_tuple_type>; + 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; @@ -13876,6 +13906,7 @@ namespace sqlite_orm { #include #ifndef SQLITE_ORM_IMPORT_STD_MODULE #include +#include // std::function #include // std::string #endif @@ -13885,25 +13916,38 @@ namespace sqlite_orm { namespace internal { struct connection_holder { - connection_holder(std::string filename) : filename(std::move(filename)) {} + connection_holder(std::string filename, std::function didOpenDb) : + _didOpenDb{std::move(didOpenDb)}, filename(std::move(filename)) {} + + connection_holder(const connection_holder&) = delete; + + connection_holder(const connection_holder& other, std::function didOpenDb) : + _didOpenDb{std::move(didOpenDb)}, filename{other.filename} {} void retain() { // first one opens the connection. // we presume that the connection is opened once in a single-threaded context [also open forever]. // therefore we can just use an atomic increment but don't need sequencing due to `prevCount > 0`. - if (this->_retain_count.fetch_add(1, std::memory_order_relaxed) == 0) { - int rc = sqlite3_open(this->filename.c_str(), &this->db); + if (_retainCount.fetch_add(1, std::memory_order_relaxed) == 0) { + int rc = sqlite3_open_v2(this->filename.c_str(), + &this->db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + nullptr); if (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY /*possible, but unexpected*/ { throw_translated_sqlite_error(this->db); } + + if (_didOpenDb) { + _didOpenDb(this->db); + } } } void release() { // last one closes the connection. // we assume that this might happen by any thread, therefore the counter must serve as a synchronization point. - if (this->_retain_count.fetch_sub(1, std::memory_order_acq_rel) == 1) { - int rc = sqlite3_close(this->db); + if (_retainCount.fetch_sub(1, std::memory_order_acq_rel) == 1) { + int rc = sqlite3_close_v2(this->db); if (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY { throw_translated_sqlite_error(this->db); } else { @@ -13921,16 +13965,20 @@ namespace sqlite_orm { * @attention While retrieving the reference count value is atomic it makes only sense at single-threaded points in code. */ int retain_count() const { - return this->_retain_count.load(std::memory_order_relaxed); + return _retainCount.load(std::memory_order_relaxed); } - const std::string filename; - protected: sqlite3* db = nullptr; private: - std::atomic_int _retain_count{}; + std::atomic_int _retainCount{}; + + private: + const std::function _didOpenDb; + + public: + const std::string filename; }; struct connection_ref { @@ -16191,7 +16239,7 @@ inline constexpr bool std::ranges::enable_borrowed_range // std::list #include // std::make_unique, std::unique_ptr #include // std::map -#include // std::is_same +#include // std::is_same, std::is_aggregate #include // std::find_if, std::ranges::find #endif @@ -16949,11 +16997,12 @@ namespace sqlite_orm { } void set_pragma_impl(const std::string& query, sqlite3* db = nullptr) { - auto con = this->get_connection(); - if (db == nullptr) { - db = con.get(); + if (db) { + perform_void_exec(db, query); + } else { + auto con = this->get_connection(); + perform_void_exec(con.get(), query); } - perform_void_exec(db, query); } }; } @@ -17707,8 +17756,58 @@ namespace sqlite_orm { // #include "table_info.h" +// #include "storage_options.h" + +#include +#ifdef SQLITE_ORM_IMPORT_STD_MODULE +#include +#else +#include // std::is_aggregate +#include // std::move +#include // std::function +#endif + namespace sqlite_orm { + namespace internal { + template + using storage_opt_tag_t = typename T::storage_opt_tag; + + struct on_open_spec { + using storage_opt_tag = int; + + std::function onOpen; + }; + } +} + +SQLITE_ORM_EXPORT namespace sqlite_orm { + /** + * Database connection control options to be passed to `make_storage()`. + */ + struct connection_control { + /// Whether to open the database once and for all. + /// Required if using a 'storage' instance from multiple threads. + bool open_forever = false; + + using storage_opt_tag = int; + }; +#if __cpp_lib_is_aggregate >= 201703L + // design choice: must be an aggregate that can be constructed using designated initializers + static_assert(std::is_aggregate_v); +#endif +#ifdef SQLITE_ORM_CTAD_SUPPORTED + /** + * Callback function to be passed to `make_storage()`. + * The provided function is called immdediately after the database connection has been established and set up. + */ + inline internal::on_open_spec on_open(std::function onOpen) { + return {std::move(onOpen)}; + } +#endif +} + +namespace sqlite_orm { namespace internal { struct storage_base { @@ -17961,20 +18060,19 @@ namespace sqlite_orm { * needed and closes when it is not needed. This function breaks this rule. In memory storage always * keeps connection opened so calling this for in-memory storage changes nothing. * Note about multithreading: in multithreading context avoiding using this function for not in-memory - * storage may lead to data races. If you have data races in such a configuration try to call `open_forever` + * storage may lead to data races. If you have data races in such a configuration try to call `open_forever()` * before accessing your storage - it may fix data races. */ void open_forever() { - this->isOpenedForever = true; - this->connection->retain(); - if (1 == this->connection->retain_count()) { - this->on_open_internal(this->connection->get()); + if (!this->isOpenedForever) { + this->isOpenedForever = true; + this->connection->retain(); } } /** * Create an application-defined scalar SQL function. - * Can be called at any time no matter whether the database connection is opened or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * 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. @@ -18017,7 +18115,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Create an application-defined scalar function. - * Can be called at any time no matter whether the database connection is opened or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * 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. @@ -18032,7 +18130,7 @@ namespace sqlite_orm { /** * Create an application-defined scalar function. - * Can be called at any time no matter whether the database connection is opened or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * 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; @@ -18073,7 +18171,7 @@ namespace sqlite_orm { /** * Create an application-defined aggregate SQL function. - * Can be called at any time no matter whether the database connection is opened or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * 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. @@ -18122,7 +18220,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Create an application-defined aggregate function. - * Can be called at any time no matter whether the database connection is opened or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * 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. @@ -18137,7 +18235,7 @@ namespace sqlite_orm { /** * Delete a scalar function you created before. - * Can be called at any time no matter whether the database connection is open or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. */ template void delete_scalar_function() { @@ -18149,7 +18247,7 @@ namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES /** * Delete a scalar function you created before. - * Can be called at any time no matter whether the database connection is open or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. */ template void delete_scalar_function() { @@ -18158,7 +18256,7 @@ namespace sqlite_orm { /** * Delete a quoted scalar function you created before. - * Can be called at any time no matter whether the database connection is open or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. */ template void delete_scalar_function() { @@ -18168,7 +18266,7 @@ namespace sqlite_orm { /** * Delete aggregate function you created before. - * Can be called at any time no matter whether the database connection is open or not. + * Can be called at any time (in a single-threaded context) no matter whether the database connection is open or not. */ template void delete_aggregate_function() { @@ -18282,7 +18380,7 @@ namespace sqlite_orm { } backup_t make_backup_to(const std::string& filename) { - auto holder = std::make_unique(filename); + auto holder = std::make_unique(filename, nullptr); connection_ref conRef{*holder}; return {conRef, "main", this->get_connection(), "main", std::move(holder)}; } @@ -18292,7 +18390,7 @@ namespace sqlite_orm { } backup_t make_backup_from(const std::string& filename) { - auto holder = std::make_unique(filename); + auto holder = std::make_unique(filename, nullptr); connection_ref conRef{*holder}; return {this->get_connection(), "main", conRef, "main", std::move(holder)}; } @@ -18336,26 +18434,39 @@ namespace sqlite_orm { } protected: - storage_base(std::string filename, int foreignKeysCount) : + storage_base(std::string filename, + connection_control connectionCtrl, + on_open_spec onOpenSpec, + int foreignKeysCount) : + on_open{std::move(onOpenSpec.onOpen)}, isOpenedForever{connectionCtrl.open_forever}, pragma(std::bind(&storage_base::get_connection, this)), limit(std::bind(&storage_base::get_connection, this)), inMemory(filename.empty() || filename == ":memory:"), - connection(std::make_unique(std::move(filename))), + connection(std::make_unique( + std::move(filename), + std::bind(&storage_base::on_open_internal, this, std::placeholders::_1))), cachedForeignKeysCount(foreignKeysCount) { if (this->inMemory) { this->connection->retain(); - this->on_open_internal(this->connection->get()); + } + if (this->isOpenedForever) { + this->connection->retain(); } } storage_base(const storage_base& other) : on_open(other.on_open), pragma(std::bind(&storage_base::get_connection, this)), limit(std::bind(&storage_base::get_connection, this)), inMemory(other.inMemory), - connection(std::make_unique(other.connection->filename)), + isOpenedForever{other.isOpenedForever}, + connection(std::make_unique( + *other.connection, + std::bind(&storage_base::on_open_internal, this, std::placeholders::_1))), cachedForeignKeysCount(other.cachedForeignKeysCount) { if (this->inMemory) { this->connection->retain(); - this->on_open_internal(this->connection->get()); + } + if (this->isOpenedForever) { + this->connection->retain(); } } @@ -18370,18 +18481,12 @@ namespace sqlite_orm { void begin_transaction_internal(const std::string& query) { this->connection->retain(); - if (1 == this->connection->retain_count()) { - this->on_open_internal(this->connection->get()); - } sqlite3* db = this->connection->get(); perform_void_exec(db, query); } connection_ref get_connection() { connection_ref res{*this->connection}; - if (1 == this->connection->retain_count()) { - this->on_open_internal(this->connection->get()); - } return res; } @@ -18397,17 +18502,17 @@ namespace sqlite_orm { perform_exec(db, "PRAGMA foreign_keys", extract_single_value, &result); return result; } - #endif - void on_open_internal(sqlite3* db) { + void on_open_internal(sqlite3* db) { #if SQLITE_VERSION_NUMBER >= 3006019 if (this->cachedForeignKeysCount) { this->foreign_keys(db, true); } #endif + if (this->pragma.synchronous_ != -1) { - this->pragma.synchronous(this->pragma.synchronous_); + this->pragma.set_pragma("synchronous", this->pragma.synchronous_, db); } if (this->pragma.journal_mode_ != -1) { @@ -18667,8 +18772,8 @@ namespace sqlite_orm { }); #endif if (dbColumnInfoIt != dbTableInfo.end()) { - auto& dbColumnInfo = *dbColumnInfoIt; - auto columnsAreEqual = + table_xinfo& dbColumnInfo = *dbColumnInfoIt; + bool columnsAreEqual = dbColumnInfo.name == storageColumnInfo.name && dbColumnInfo.notnull == storageColumnInfo.notnull && (!dbColumnInfo.dflt_value.empty()) == (!storageColumnInfo.dflt_value.empty()) && @@ -18679,8 +18784,7 @@ namespace sqlite_orm { break; } dbTableInfo.erase(dbColumnInfoIt); - storageTableInfo.erase(storageTableInfo.begin() + - static_cast(storageColumnInfoIndex)); + storageTableInfo.erase(storageTableInfo.begin() + storageColumnInfoIndex); --storageColumnInfoIndex; } else { columnsToAdd.push_back(&storageColumnInfo); @@ -22438,21 +22542,39 @@ namespace sqlite_orm { polyfill::void_t().prepare(std::declval()))>>> = true; + template + decltype(auto) storage_opt_or_default(OptionsTpl& options) { +#ifdef SQLITE_ORM_CTAD_SUPPORTED + if constexpr (tuple_has_type::value) { + return std::move(std::get(options)); + } else { + return Opt{}; + } +#else + return Opt{}; +#endif + } + /** * Storage class itself. Create an instanse to use it as an interfacto to sqlite db by calling `make_storage` * function. */ template struct storage_t : storage_base { - using self = storage_t; + using self_type = storage_t; using db_objects_type = db_objects_tuple; /** * @param filename database filename. * @param dbObjects db_objects_tuple */ - storage_t(std::string filename, db_objects_type dbObjects) : - storage_base{std::move(filename), foreign_keys_count(dbObjects)}, db_objects{std::move(dbObjects)} {} + template + storage_t(std::string filename, db_objects_type dbObjects, OptionsTpl options) : + storage_base{std::move(filename), + storage_opt_or_default(options), + storage_opt_or_default(options), + foreign_keys_count(dbObjects)}, + db_objects{std::move(dbObjects)} {} storage_t(const storage_t&) = default; @@ -22471,7 +22593,7 @@ namespace sqlite_orm { * * Hence, friend was replaced by `obtain_db_objects()` and `pick_const_impl()`. */ - friend const db_objects_type& obtain_db_objects(const self& storage) noexcept { + friend const db_objects_type& obtain_db_objects(const self_type& storage) noexcept { return storage.db_objects; } @@ -22603,7 +22725,7 @@ namespace sqlite_orm { public: template, class... Args> - mapped_view iterate(Args&&... args) { + mapped_view iterate(Args&&... args) { this->assert_mapped_type(); auto con = this->get_connection(); @@ -23138,7 +23260,7 @@ namespace sqlite_orm { std::enable_if_t::value && !is_mapped::value, bool> = true> std::string dump(E&& expression, bool parametrized = false) const { - static_assert(is_preparable_v, "Expression must be a high-level statement"); + static_assert(is_preparable_v, "Expression must be a high-level statement"); decltype(auto) e2 = static_if::value>( [](auto expression) -> auto { @@ -24059,17 +24181,50 @@ namespace sqlite_orm { } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED }; // struct storage_t + +#ifdef SQLITE_ORM_CTAD_SUPPORTED + template + using dbo_index_sequence = filter_tuple_sequence_t::template fn>; + + template + using opt_index_sequence = filter_tuple_sequence_t::template fn>; + + template + storage_t make_storage(std::string filename, std::tuple dbObjects, OptionsTpl options) { + return {std::move(filename), std::move(dbObjects), std::move(options)}; + } +#endif } } SQLITE_ORM_EXPORT namespace sqlite_orm { +#ifdef SQLITE_ORM_CTAD_SUPPORTED /* - * Factory function for a storage, from a database file and a bunch of database object definitions. + * Factory function for a storage instance, from a database file, a set of database object definitions + * and option storage options like connection control options and an 'on open' callback. + * + * E.g. + * auto storage = make_storage("", connection_control{.open_forever = true}, on_open([](sqlite3* db) {})); + */ + template + auto make_storage(std::string filename, Spec... specifications) { + using namespace ::sqlite_orm::internal; + + std::tuple specTuple{std::forward(specifications)...}; + return internal::make_storage( + std::move(filename), + create_from_tuple(std::move(specTuple), dbo_index_sequence{}), + create_from_tuple(std::move(specTuple), opt_index_sequence{})); + } +#else + /* + * Factory function for a storage instance, from a database file and a bunch of database object definitions. */ template internal::storage_t make_storage(std::string filename, DBO... dbObjects) { - return {std::move(filename), internal::db_objects_tuple{std::forward(dbObjects)...}}; + return {std::move(filename), {std::forward(dbObjects)...}, std::tuple<>{}}; } +#endif /** * sqlite3_threadsafe() interface. diff --git a/tests/filename.cpp b/tests/filename.cpp index e779ab836..de4f5559f 100644 --- a/tests/filename.cpp +++ b/tests/filename.cpp @@ -4,15 +4,15 @@ using namespace sqlite_orm; TEST_CASE("filename") { - { + SECTION("empty") { auto storage = make_storage(""); REQUIRE(storage.filename() == ""); } - { + SECTION("memory") { auto storage = make_storage(":memory:"); REQUIRE(storage.filename() == ":memory:"); } - { + SECTION("file name") { auto storage = make_storage("myDatabase.sqlite"); REQUIRE(storage.filename() == "myDatabase.sqlite"); } diff --git a/tests/static_tests/functional/tuple_transform.cpp b/tests/static_tests/functional/tuple_transform.cpp index d67539098..26bcab109 100644 --- a/tests/static_tests/functional/tuple_transform.cpp +++ b/tests/static_tests/functional/tuple_transform.cpp @@ -49,6 +49,8 @@ TEST_CASE("tuple_helper static") { #if __cpp_lib_constexpr_algorithms >= 201806L STATIC_REQUIRE(create_from_tuple>(std::make_tuple(1, 2), polyfill::identity{}) == std::array{1, 2}); + STATIC_REQUIRE(create_from_tuple(std::make_tuple(1, 2), polyfill::identity{}) == + std::tuple{1, 2}); #endif #if defined(SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED) && (__cpp_lib_constexpr_functional >= 201907L) STATIC_REQUIRE(recombine_tuple(tuple_maker{}, std::make_tuple(1, 2), polyfill::identity{}, 3) == diff --git a/tests/storage_tests.cpp b/tests/storage_tests.cpp index c46cd28ad..c3844ab53 100644 --- a/tests/storage_tests.cpp +++ b/tests/storage_tests.cpp @@ -4,6 +4,61 @@ using namespace sqlite_orm; +#ifdef SQLITE_ORM_CTAD_SUPPORTED +TEST_CASE("connection control") { + const auto openForever = GENERATE(false, true); + SECTION("") { + bool onOpenCalled = false; + int nOnOpenCalled = 0; + SECTION("empty") { + auto storage = + make_storage("", connection_control{openForever}, on_open([&onOpenCalled, &nOnOpenCalled](sqlite3* db) { + onOpenCalled = db || false; + ++nOnOpenCalled; + })); + REQUIRE(storage.is_opened()); + REQUIRE(onOpenCalled); + REQUIRE(nOnOpenCalled == 1); + } +#if __cpp_designated_initializers >= 201707L + SECTION("empty C++20") { + auto storage = make_storage("", + connection_control{.open_forever = openForever}, + on_open([&onOpenCalled, &nOnOpenCalled](sqlite3* db) { + onOpenCalled = db || false; + ++nOnOpenCalled; + })); + REQUIRE(storage.is_opened()); + REQUIRE(onOpenCalled); + REQUIRE(nOnOpenCalled == 1); + } +#endif + SECTION("memory") { + auto storage = make_storage(":memory:", + connection_control{openForever}, + on_open([&onOpenCalled, &nOnOpenCalled](sqlite3* db) { + onOpenCalled = db || false; + ++nOnOpenCalled; + })); + REQUIRE(storage.is_opened()); + REQUIRE(onOpenCalled); + REQUIRE(nOnOpenCalled == 1); + } + SECTION("file name") { + auto storage = make_storage("myDatabase.sqlite", + connection_control{openForever}, + on_open([&onOpenCalled, &nOnOpenCalled](sqlite3* db) { + onOpenCalled = db || false; + ++nOnOpenCalled; + })); + REQUIRE(storage.is_opened() == openForever); + REQUIRE(onOpenCalled == openForever); + REQUIRE(nOnOpenCalled == int(openForever)); + } + } +} +#endif + TEST_CASE("Current time/date/timestamp") { auto storage = make_storage(""); SECTION("time") {