Skip to content

Commit

Permalink
Ability to specify connection control options when making 'storage'
Browse files Browse the repository at this point in the history
A user-provided 'on open' handler and connection control options can now be specified in a declarative way when making the 'storage' object.
  • Loading branch information
trueqbit committed Feb 10, 2025
1 parent 23f66e0 commit 14a2aa0
Show file tree
Hide file tree
Showing 9 changed files with 343 additions and 55 deletions.
12 changes: 9 additions & 3 deletions dev/connection_holder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 4 additions & 0 deletions dev/functional/cxx_core_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 13 additions & 1 deletion dev/functional/mpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ namespace sqlite_orm {
* Commonly used named abbreviation for `check_if<std::is_same, Type>`.
*/
template<class Type>
using check_if_is_type = mpl::bind_front_fn<std::is_same, Type>;
using check_if_is_type = check_if<std::is_same, Type>;

/*
* Quoted trait metafunction that checks if a type's template matches the specified template
Expand All @@ -463,6 +463,18 @@ namespace sqlite_orm {
using check_if_is_template =
mpl::pass_extracted_fn_to<mpl::bind_front_fn<std::is_same, mpl::quote_fn<Template>>>;

/*
* Quoted trait metafunction that checks if a type names a nested type determined by `Op`.
*/
template<template<typename...> class Op>
using check_if_names = mpl::bind_front_higherorder_fn<polyfill::is_detected, Op>;

/*
* Quoted trait metafunction that checks if a type does not name a nested type determined by `Op`.
*/
template<template<typename...> class Op>
using check_if_lacks = mpl::not_<check_if_names<Op>>;

/*
* Quoted metafunction that finds the index of the given type in a tuple.
*/
Expand Down
71 changes: 67 additions & 4 deletions dev/storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,56 @@ namespace sqlite_orm {
polyfill::void_t<indirectly_test_preparable<decltype(std::declval<S>().prepare(std::declval<E>()))>>> =
true;

#ifdef SQLITE_ORM_CTAD_SUPPORTED
template<class PropsTpl>
on_open on_open_spec_or_default(PropsTpl& properties) {
if constexpr (tuple_has_type<PropsTpl, on_open>::value) {
return std::move(std::get<on_open>(properties));
} else {
return {};
}
}

template<class PropsTpl>
connection_control connection_control_or_default(PropsTpl& properties) {
if constexpr (tuple_has_type<PropsTpl, connection_control>::value) {
return std::move(std::get<connection_control>(properties));
} else {
return {};
}
}
#else
template<class PropsTpl>
on_open on_open_spec_or_default(PropsTpl&) {
return {};
}

template<class PropsTpl>
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<class... DBO>
struct storage_t : storage_base {
using self = storage_t<DBO...>;
using self = storage_t;
using db_objects_type = db_objects_tuple<DBO...>;

/**
* @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<class PropsTpl>
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;

Expand Down Expand Up @@ -1702,17 +1737,45 @@ namespace sqlite_orm {
}
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
}; // struct storage_t

#ifdef SQLITE_ORM_CTAD_SUPPORTED
template<typename T>
using storage_prop_tag_t = typename T::storage_prop_tag;

template<class Elements>
using prop_index_sequence = filter_tuple_sequence_t<Elements, check_if_names<storage_prop_tag_t>::template fn>;

template<class Elements>
using dbo_index_sequence = filter_tuple_sequence_t<Elements, check_if_lacks<storage_prop_tag_t>::template fn>;

template<class... DBO, class PropsTpl>
storage_t<DBO...> make_storage(std::string filename, std::tuple<DBO...> 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<class... Spec>
auto make_storage(std::string filename, Spec... arguments) {
using namespace ::sqlite_orm::internal;

std::tuple args{std::forward<Spec>(arguments)...};
return make_storage(std::move(filename),
create_from_tuple<std::tuple>(std::move(args), dbo_index_sequence<decltype(args)>{}),
create_from_tuple<std::tuple>(std::move(args), prop_index_sequence<decltype(args)>{}));
}
#else
template<class... DBO>
internal::storage_t<DBO...> make_storage(std::string filename, DBO... dbObjects) {
return {std::move(filename), internal::db_objects_tuple<DBO...>{std::forward<DBO>(dbObjects)...}};
return {std::move(filename), {std::forward<DBO>(dbObjects)...}, std::tuple<>{}};
}
#endif

/**
* sqlite3_threadsafe() interface.
Expand Down
65 changes: 48 additions & 17 deletions dev/storage_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<void(sqlite3*)> onOpen) : onOpen{std::move(onOpen)} {}
#endif

std::function<void(sqlite3*)> onOpen;
};

struct connection_control {
using storage_prop_tag = int;

bool openForever = false;
};
}

namespace sqlite_orm {
namespace internal {

struct storage_base {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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<class F>
void delete_scalar_function() {
Expand All @@ -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<orm_scalar_function auto f>
void delete_scalar_function() {
Expand All @@ -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<orm_quoted_scalar_function auto quotedF>
void delete_scalar_function() {
Expand All @@ -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<class F>
void delete_aggregate_function() {
Expand Down Expand Up @@ -661,30 +681,41 @@ 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<connection_holder>(
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<connection_holder>(
*other.connection,
std::bind(&storage_base::on_open_internal, this, std::placeholders::_1))),
cachedForeignKeysCount(other.cachedForeignKeysCount) {
if (this->inMemory) {
this->connection->retain();
}
if (this->isOpenedForever) {
this->connection->retain();
}
}

~storage_base() {
Expand Down Expand Up @@ -719,15 +750,15 @@ namespace sqlite_orm {
perform_exec(db, "PRAGMA foreign_keys", extract_single_value<bool>, &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);
}
Expand Down
20 changes: 19 additions & 1 deletion dev/tuple_helper/tuple_transformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<class R, class Tpl, class Projection = polyfill::identity>
constexpr R create_from_tuple(Tpl&& tpl, Projection project = {}) {
Expand All @@ -112,5 +112,23 @@ namespace sqlite_orm {
std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tpl>>::value>{},
std::forward<Projection>(project));
}

#ifdef SQLITE_ORM_CTAD_SUPPORTED
template<template<typename...> class R, class Tpl, size_t... Idx, class Projection = polyfill::identity>
constexpr auto create_from_tuple(Tpl&& tpl, std::index_sequence<Idx...>, Projection project = {}) {
return R{polyfill::invoke(project, std::get<Idx>(std::forward<Tpl>(tpl)))...};
}

/*
* Similar to `create_from_tuple()`, but the result type is specified as a template class.
*/
template<template<typename...> class R, class Tpl, class Projection = polyfill::identity>
constexpr auto create_from_tuple(Tpl&& tpl, Projection project = {}) {
return create_from_tuple<R>(
std::forward<Tpl>(tpl),
std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tpl>>::value>{},
std::forward<Projection>(project));
}
#endif
}
}
Loading

0 comments on commit 14a2aa0

Please sign in to comment.