diff --git a/dev/connection_holder.h b/dev/connection_holder.h index 68d8621d..cbb245d0 100644 --- a/dev/connection_holder.h +++ b/dev/connection_holder.h @@ -66,8 +66,11 @@ namespace sqlite_orm { // first one opens and sets up the connection. - if (int rc = sqlite3_open(this->filename.c_str(), &this->db); rc != SQLITE_OK) - [[unlikely]] /*possible, but unexpected*/ { + if (int rc = sqlite3_open_v2(this->filename.c_str(), + &this->db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + nullptr); + rc != SQLITE_OK) [[unlikely]] /*possible, but unexpected*/ { throw_translated_sqlite_error(this->db); } @@ -143,7 +146,10 @@ namespace sqlite_orm { // 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 (_retainCount.fetch_add(1, std::memory_order_relaxed) == 0) { - int rc = sqlite3_open(this->filename.c_str(), &this->db); + 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); } diff --git a/dev/functional/cxx_core_features.h b/dev/functional/cxx_core_features.h index 90969821..779737eb 100644 --- a/dev/functional/cxx_core_features.h +++ b/dev/functional/cxx_core_features.h @@ -53,6 +53,10 @@ #define SQLITE_ORM_ALIGNED_NEW_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 #endif diff --git a/dev/functional/mpl.h b/dev/functional/mpl.h index f2d196de..108f3ccd 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/storage.h b/dev/storage.h index b9bedbf8..57848e13 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -81,21 +81,56 @@ namespace sqlite_orm { polyfill::void_t().prepare(std::declval()))>>> = true; +#ifdef SQLITE_ORM_CTAD_SUPPORTED + template + on_open on_open_spec_or_default(PropsTpl& properties) { + if constexpr (tuple_has_type::value) { + return std::move(std::get(properties)); + } else { + return {}; + } + } + + template + connection_control connection_control_or_default(PropsTpl& properties) { + if constexpr (tuple_has_type::value) { + return std::move(std::get(properties)); + } else { + return {}; + } + } +#else + template + on_open on_open_spec_or_default(PropsTpl&) { + return {}; + } + + template + connection_control connection_control_or_default(PropsTpl&) { + return {}; + } +#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 = 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, PropsTpl properties) : + storage_base{std::move(filename), + on_open_spec_or_default(properties), + connection_control_or_default(properties), + foreign_keys_count(dbObjects)}, + db_objects{std::move(dbObjects)} {} storage_t(const storage_t&) = default; @@ -1702,17 +1737,45 @@ namespace sqlite_orm { } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED }; // struct storage_t + +#ifdef SQLITE_ORM_CTAD_SUPPORTED + template + using storage_prop_tag_t = typename T::storage_prop_tag; + + template + using prop_index_sequence = filter_tuple_sequence_t::template fn>; + + template + using dbo_index_sequence = filter_tuple_sequence_t::template fn>; + + template + storage_t make_storage(std::string filename, std::tuple dbObjects, PropsTpl properties) { + return {std::move(filename), std::move(dbObjects), std::move(properties)}; + } +#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. */ + template + auto make_storage(std::string filename, Spec... arguments) { + using namespace ::sqlite_orm::internal; + + std::tuple args{std::forward(arguments)...}; + return make_storage(std::move(filename), + create_from_tuple(std::move(args), dbo_index_sequence{}), + create_from_tuple(std::move(args), prop_index_sequence{})); + } +#else 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 da4a6be8..c36df1ac 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -35,8 +35,26 @@ #include "serializing_util.h" #include "table_info.h" -namespace sqlite_orm { +SQLITE_ORM_EXPORT namespace sqlite_orm { + struct on_open { + using storage_prop_tag = int; + +#ifndef SQLITE_ORM_AGGREGATE_PAREN_INIT_SUPPORTED + on_open() = default; + on_open(std::function onOpen) : onOpen{std::move(onOpen)} {} +#endif + std::function onOpen; + }; + + struct connection_control { + using storage_prop_tag = int; + + bool openForever = false; + }; +} + +namespace sqlite_orm { namespace internal { struct storage_base { @@ -289,17 +307,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 (!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. @@ -342,7 +362,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. @@ -357,7 +377,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; @@ -398,7 +418,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. @@ -447,7 +467,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. @@ -462,7 +482,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() { @@ -474,7 +494,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() { @@ -483,7 +503,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() { @@ -493,7 +513,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() { @@ -661,23 +681,31 @@ namespace sqlite_orm { } protected: - storage_base(std::string filename, int foreignKeysCount) : + storage_base(std::string filename, + sqlite_orm::on_open onOpenSpec, + connection_control connectionCtrl, + int foreignKeysCount) : + on_open{std::move(onOpenSpec.onOpen)}, isOpenedForever{connectionCtrl.openForever}, 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), - inMemory, + inMemory || isOpenedForever, std::bind(&storage_base::on_open_internal, this, std::placeholders::_1))), cachedForeignKeysCount(foreignKeysCount) { if (this->inMemory) { this->connection->retain(); } + 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), + isOpenedForever{other.isOpenedForever}, connection(std::make_unique( *other.connection, std::bind(&storage_base::on_open_internal, this, std::placeholders::_1))), @@ -685,6 +713,9 @@ namespace sqlite_orm { if (this->inMemory) { this->connection->retain(); } + if (this->isOpenedForever) { + this->connection->retain(); + } } ~storage_base() { @@ -719,15 +750,15 @@ 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.set_pragma("synchronous", this->pragma.synchronous_, db); } diff --git a/dev/tuple_helper/tuple_transformer.h b/dev/tuple_helper/tuple_transformer.h index 55d940d5..57fef52d 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 a8869322..ee3ca0fc 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -102,6 +102,10 @@ using std::nullptr_t; #define SQLITE_ORM_ALIGNED_NEW_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 #endif @@ -1242,7 +1246,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 @@ -1252,6 +1256,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. */ @@ -1643,7 +1659,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 = {}) { @@ -1652,6 +1668,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 } } @@ -13969,8 +14003,11 @@ namespace sqlite_orm { // first one opens and sets up the connection. - if (int rc = sqlite3_open(this->filename.c_str(), &this->db); rc != SQLITE_OK) - [[unlikely]] /*possible, but unexpected*/ { + if (int rc = sqlite3_open_v2(this->filename.c_str(), + &this->db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + nullptr); + rc != SQLITE_OK) [[unlikely]] /*possible, but unexpected*/ { throw_translated_sqlite_error(this->db); } @@ -14046,7 +14083,10 @@ namespace sqlite_orm { // 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 (_retainCount.fetch_add(1, std::memory_order_relaxed) == 0) { - int rc = sqlite3_open(this->filename.c_str(), &this->db); + 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); } @@ -17877,8 +17917,26 @@ namespace sqlite_orm { // #include "table_info.h" -namespace sqlite_orm { +SQLITE_ORM_EXPORT namespace sqlite_orm { + struct on_open { + using storage_prop_tag = int; + +#ifndef SQLITE_ORM_AGGREGATE_PAREN_INIT_SUPPORTED + on_open() = default; + on_open(std::function onOpen) : onOpen{std::move(onOpen)} {} +#endif + + std::function onOpen; + }; + + struct connection_control { + using storage_prop_tag = int; + bool openForever = false; + }; +} + +namespace sqlite_orm { namespace internal { struct storage_base { @@ -18131,17 +18189,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 (!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. @@ -18184,7 +18244,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. @@ -18199,7 +18259,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; @@ -18240,7 +18300,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. @@ -18289,7 +18349,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. @@ -18304,7 +18364,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() { @@ -18316,7 +18376,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() { @@ -18325,7 +18385,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() { @@ -18335,7 +18395,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() { @@ -18503,23 +18563,31 @@ namespace sqlite_orm { } protected: - storage_base(std::string filename, int foreignKeysCount) : + storage_base(std::string filename, + sqlite_orm::on_open onOpenSpec, + connection_control connectionCtrl, + int foreignKeysCount) : + on_open{std::move(onOpenSpec.onOpen)}, isOpenedForever{connectionCtrl.openForever}, 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), - inMemory, + inMemory || isOpenedForever, std::bind(&storage_base::on_open_internal, this, std::placeholders::_1))), cachedForeignKeysCount(foreignKeysCount) { if (this->inMemory) { this->connection->retain(); } + 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), + isOpenedForever{other.isOpenedForever}, connection(std::make_unique( *other.connection, std::bind(&storage_base::on_open_internal, this, std::placeholders::_1))), @@ -18527,6 +18595,9 @@ namespace sqlite_orm { if (this->inMemory) { this->connection->retain(); } + if (this->isOpenedForever) { + this->connection->retain(); + } } ~storage_base() { @@ -18561,15 +18632,15 @@ 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.set_pragma("synchronous", this->pragma.synchronous_, db); } @@ -22602,21 +22673,56 @@ namespace sqlite_orm { polyfill::void_t().prepare(std::declval()))>>> = true; +#ifdef SQLITE_ORM_CTAD_SUPPORTED + template + on_open on_open_spec_or_default(PropsTpl& properties) { + if constexpr (tuple_has_type::value) { + return std::move(std::get(properties)); + } else { + return {}; + } + } + + template + connection_control connection_control_or_default(PropsTpl& properties) { + if constexpr (tuple_has_type::value) { + return std::move(std::get(properties)); + } else { + return {}; + } + } +#else + template + on_open on_open_spec_or_default(PropsTpl&) { + return {}; + } + + template + connection_control connection_control_or_default(PropsTpl&) { + return {}; + } +#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 = 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, PropsTpl properties) : + storage_base{std::move(filename), + on_open_spec_or_default(properties), + connection_control_or_default(properties), + foreign_keys_count(dbObjects)}, + db_objects{std::move(dbObjects)} {} storage_t(const storage_t&) = default; @@ -24223,17 +24329,45 @@ namespace sqlite_orm { } #endif // SQLITE_ORM_OPTIONAL_SUPPORTED }; // struct storage_t + +#ifdef SQLITE_ORM_CTAD_SUPPORTED + template + using storage_prop_tag_t = typename T::storage_prop_tag; + + template + using prop_index_sequence = filter_tuple_sequence_t::template fn>; + + template + using dbo_index_sequence = filter_tuple_sequence_t::template fn>; + + template + storage_t make_storage(std::string filename, std::tuple dbObjects, PropsTpl properties) { + return {std::move(filename), std::move(dbObjects), std::move(properties)}; + } +#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. */ + template + auto make_storage(std::string filename, Spec... arguments) { + using namespace ::sqlite_orm::internal; + + std::tuple args{std::forward(arguments)...}; + return make_storage(std::move(filename), + create_from_tuple(std::move(args), dbo_index_sequence{}), + create_from_tuple(std::move(args), prop_index_sequence{})); + } +#else 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 e779ab83..de4f5559 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/storage_tests.cpp b/tests/storage_tests.cpp index c46cd28a..570285a0 100644 --- a/tests/storage_tests.cpp +++ b/tests/storage_tests.cpp @@ -4,6 +4,26 @@ using namespace sqlite_orm; +#ifdef SQLITE_ORM_CTAD_SUPPORTED +TEST_CASE("connection control") { + const auto openForever = GENERATE(false, true); + SECTION("") { + SECTION("empty") { + auto storage = make_storage("", connection_control{openForever}, on_open([](sqlite3*) {})); + REQUIRE(storage.is_opened()); + } + SECTION("memory") { + auto storage = make_storage(":memory:", connection_control{openForever}, on_open([](sqlite3*) {})); + REQUIRE(storage.is_opened()); + } + SECTION("file name") { + auto storage = make_storage("myDatabase.sqlite", connection_control{openForever}, on_open([](sqlite3*) {})); + REQUIRE(storage.is_opened() == openForever); + } + } +} +#endif + TEST_CASE("Current time/date/timestamp") { auto storage = make_storage(""); SECTION("time") {