4#include "Stroika/Foundation/StroikaPreComp.h"
10#include "Stroika/Foundation/Common/GUID.h"
14#include "Stroika/Foundation/Execution/Exceptions.h"
22using namespace Characters;
23using namespace Containers;
24using namespace Common;
26using namespace DataExchange;
27using namespace Database;
28using namespace Database::Document::SQLite;
29using namespace Execution;
37using Database::Document::kID;
38using Database::Document::kOnlyIDs;
45#if qStroika_HasComponent_sqlite
47 struct ModuleShutdown_ {
50 Verify (::sqlite3_shutdown () == SQLITE_OK);
53 [[noreturn]]
void ThrowSQLiteError_ (
int errCode, sqlite3* sqliteConnection)
55 Require (errCode != SQLITE_OK);
56 optional<String> errMsgDetails;
57 if (sqliteConnection !=
nullptr) {
58 errMsgDetails = String::FromUTF8 (::sqlite3_errmsg (sqliteConnection));
69 case SQLITE_CONSTRAINT: {
74 static const auto kEx_ =
Exception{
"SQLITE_CONSTRAINT"sv};
79 static const auto kEx_ =
Exception{
"SQLITE_TOOBIG"sv};
86 case SQLITE_READONLY: {
87 static const auto kEx_ =
Exception{
"SQLITE_READONLY"sv};
95 static const auto kEx_ =
Exception{
"SQLITE_MISUSE"sv};
104 static const auto kEx_ =
Exception{
"SQLITE_ERROR"sv};
109 DbgTrace (
"SQLITE_NOMEM translated to bad_alloc"_f);
114 Throw (
Exception{
"SQLite Error: {} (code {})"_f(errMsgDetails, errCode)});
120 void ThrowSQLiteErrorIfNotOK_ (
int errCode, sqlite3* sqliteConnection)
122 static_assert (SQLITE_OK == 0);
123 if (errCode != SQLITE_OK) [[unlikely]] {
124 ThrowSQLiteError_ (errCode, sqliteConnection);
131 template <invocable<
int,
char**,
char**> CB>
132 struct SQLiteCallback_ {
135 using STATIC_FUNCTION_TYPE = int (*) (
void*, int,
char**,
char**);
137 SQLiteCallback_ (CB&& cb)
138 : fCallback_{forward<CB> (cb)}
141 STATIC_FUNCTION_TYPE GetStaticFunction ()
153 void ThrowIfCapturedException ()
155 if (fCapturedException_ !=
nullptr) [[unlikely]] {
156 rethrow_exception (fCapturedException_);
162 exception_ptr fCapturedException_;
171 static int STATICFUNC_ (
void* SQLiteCallbackData,
int argc,
char** argv,
char** azColName)
noexcept
173 SQLiteCallback_* sqc =
reinterpret_cast<SQLiteCallback_*
> (SQLiteCallbackData);
174 if (sqc->fCapturedException_ ==
nullptr) [[likely]] {
176 return sqc->fCallback_ (argc, argv, azColName);
179 sqc->fCapturedException_ = current_exception ();
187 struct MyPreparedStatement_ {
188 MyPreparedStatement_ () =
default;
189 MyPreparedStatement_ (::sqlite3* db,
const String& statement)
192#if USE_NOISY_TRACE_IN_THIS_MODULE_
195 const char* pzTail =
nullptr;
196 string utfStatement = statement.
AsUTF8<
string> ();
197 ThrowSQLiteErrorIfNotOK_ (::sqlite3_prepare_v2 (db, utfStatement.c_str (), -1, &fObj_, &pzTail), db);
198 Assert (pzTail !=
nullptr);
199 Require (*pzTail ==
'\0');
202 MyPreparedStatement_ (
const MyPreparedStatement_&) =
delete;
203 MyPreparedStatement_ (MyPreparedStatement_&&) noexcept = default;
204 ~MyPreparedStatement_ ()
206 if (fObj_ !=
nullptr) {
207 (void)::sqlite3_finalize (fObj_);
210 MyPreparedStatement_& operator= (
const MyPreparedStatement_&) =
delete;
211 MyPreparedStatement_& operator= (MyPreparedStatement_&& rhs)
noexcept
217 operator ::sqlite3_stmt* ()
const
223 ::sqlite3_stmt* fObj_{
nullptr};
225 static_assert (movable<MyPreparedStatement_>);
226 static_assert (not copyable<MyPreparedStatement_>);
228 String ExtractColumnText_ (::sqlite3_stmt* statement,
unsigned int col)
231 Require (col <
static_cast<unsigned int> (::sqlite3_column_count (statement)));
232 const char* t =
reinterpret_cast<const char*
> (::sqlite3_column_text (statement,
static_cast<int> (col)));
233 if (t ==
nullptr) [[unlikely]] {
234 switch (
int colType = ::sqlite3_column_type (statement, col)) {
243 return String::FromUTF8 (t);
253 struct VerifyFlags_ {
256 Assert (CompiledOptions::kThe.ENABLE_NORMALIZE == !!::sqlite3_compileoption_used (
"ENABLE_NORMALIZE"));
257 Assert (CompiledOptions::kThe.THREADSAFE == !!::sqlite3_compileoption_used (
"THREADSAFE"));
258#if SQLITE_VERSION_NUMBER < 3038000
259 Assert (::sqlite3_compileoption_used (
"ENABLE_JSON1"));
261 Assert (!::sqlite3_compileoption_used (
"OMIT_JSON1"));
271 tuple<optional<String>, optional<Filter>> Partition_ (
const optional<Filter>& filter)
283 bool transferred =
false;
284 if (
const Document::FilterElements::Equals* eqOp = get_if<Document::FilterElements::Equals> (&op)) {
286 if (not whereClause.
empty ()) {
287 whereClause <<
" AND "sv;
290 if (eqOp->fLHS == Database::Document::kID) {
291 whereClause <<
"{} = '{}'"_f(Database::Document::kID, rhsValue->As<
String> ());
296 if (rhsValue->GetType () == VariantValue::eString or rhsValue->GetType () == VariantValue::eDate or
297 rhsValue->GetType () == VariantValue::eDateTime) {
298 vSQL =
"'"sv + vSQL +
"'"sv;
300 whereClause <<
"json_extract(json, '$.{}') = {}"_f(
String{eqOp->fLHS}, vSQL);
305 if (not transferred) {
309 if (not whereClause.
empty ()) {
311 return make_tuple (whereClause, clientSideOps.
empty () ? optional<Filter>{} : make_optional (
Filter{clientSideOps}));
314 return make_tuple (nullopt, filter);
316 return make_tuple (nullopt, nullopt);
325 tuple<optional<tuple<String, Sequence<String>>>, optional<Projection>> Partition_ (
const optional<Projection>& p)
331 tuple<Document::Projection::Flag, Set<String>> fields = p->GetFields ();
333 if (get<Document::Projection::Flag> (fields) == Document::Projection::Flag::eInclude) {
336 for (
String f : get<1> (fields)) {
337 String mongoFieldName = f;
338 Require (f != Document::kID);
339 if (fieldNames.
empty ()) {
340 projectionQuery <<
"json_extract(json,'"sv;
343 projectionQuery <<
","sv;
345 projectionQuery <<
"$."sv << mongoFieldName;
348 if (not fieldNames.
empty ()) {
349 projectionQuery <<
"')"sv;
351 return make_tuple (make_tuple (projectionQuery, fieldNames), nullopt);
354 return make_tuple (nullopt, p);
357 return make_tuple (nullopt, nullopt);
361 Document::Document ExtractRowValueAfterStep_ (::sqlite3_stmt* statement,
unsigned int dataCol,
const IDType&
id,
363 const optional<Projection>& remainingProjection)
369 static const auto kJSONReader_ = Variant::JSON::Reader{};
370 String colText = ExtractColumnText_ (statement, dataCol);
373 if (sqliteProjection == nullopt) {
377 auto arrayOfFieldNames = get<Sequence<String>> (*sqliteProjection);
378 if (arrayOfFieldNames.size () == 1) {
382 Assert (valueReadBackFromDB.GetType () == VariantValue::eArray);
387 Assert (nameI == arrayOfFieldNames.end ());
390 if ((sqliteProjection and get<
Sequence<String>> (*sqliteProjection).
Contains (Document::kID)) or remainingProjection == nullopt or
391 remainingProjection->Includes (Document::kID)) {
392 dr.
Add (Document::kID,
id);
394 if (remainingProjection) {
395 dr = remainingProjection->
Apply (dr);
402 using Connection::Options;
404 template <InternallySynchronized SYNC_STYLE>
406 conditional_t<SYNC_STYLE == InternallySynchronized::eNotKnownInternallySynchronized, Debug::AssertExternallySynchronizedChecker, recursive_mutex>;
407 static_assert (Common::StdCompat::BasicLockable<MyMaybeLock_<InternallySynchronized::eNotKnownInternallySynchronized>>);
408 static_assert (Common::StdCompat::BasicLockable<MyMaybeLock_<InternallySynchronized::eInternallySynchronized>>);
410 template <Execution::InternallySynchronized SYNC_STYLE>
411 struct ConnectionRep_ final : Database::Document::SQLite::Connection::IRep {
413 const Options fOptions_;
414 const bool fAllowUserDefinedRowID_{
false};
418 shared_ptr<ConnectionRep_> fConnectionRep_;
421 MyPreparedStatement_ fAddStatement_{};
422 MyPreparedStatement_ fGetOneStatement_{};
423 MyPreparedStatement_ fRemoveStatement_{};
424 MyPreparedStatement_ fUpdateStatement_{};
426 CollectionRep_ (
const shared_ptr<ConnectionRep_>& connectionRep,
const String& collectionName)
427 : fConnectionRep_{connectionRep}
428 , fTableName_{collectionName}
431 virtual ~CollectionRep_ () =
default;
432 virtual String GetName ()
const override
438#if USE_NOISY_TRACE_IN_THIS_MODULE_
441 Require (not v.
ContainsKey (Database::Document::kID) or fConnectionRep_->fOptions_.fAddAllowsExternallySpecifiedIDs);
442 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
444 return fConnectionRep_->WrapExecute_ (
451 if (fConnectionRep_->fAllowUserDefinedRowID_) {
452 if (auto o = v.Lookup (Database::Document::kID)) {
453 id = o->As<String> ();
454 jsonValue2Write.RemoveIf (Database::Document::kID);
457 id = Common::GUID::GenerateNew ().As<String> ();
460 Assert (not jsonValue2Write.
ContainsKey (Database::Document::kID));
470 if (fAddStatement_ ==
nullptr) [[unlikely]] {
471 if (fConnectionRep_->fAllowUserDefinedRowID_) {
473 MyPreparedStatement_{fConnectionRep_->fDB_,
"insert into \"{}\" (id, json) values(?,?);"_f(fTableName_)};
476 fAddStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_,
"insert into \"{}\" (json) values(?);"_f(fTableName_)};
479 static const auto kJSONWriter_ = Variant::JSON::Writer{};
480 string jsonText = kJSONWriter_.WriteAsString (
VariantValue{jsonValue2Write}).AsUTF8<string> ();
481 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fAddStatement_), fConnectionRep_->fDB_);
482 if (fConnectionRep_->fAllowUserDefinedRowID_) {
483 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fAddStatement_, 1, id->AsUTF8<
string> ().c_str (),
484 static_cast<int> (id->AsUTF8 ().length ()), SQLITE_TRANSIENT),
485 fConnectionRep_->fDB_);
487 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fAddStatement_, fConnectionRep_->fAllowUserDefinedRowID_ ? 2 : 1,
488 jsonText.c_str (), static_cast<int> (jsonText.length ()), SQLITE_TRANSIENT),
489 fConnectionRep_->fDB_);
490 int rc = ::sqlite3_step (fAddStatement_);
491 if (rc != SQLITE_DONE) {
492 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
494 return fConnectionRep_->fAllowUserDefinedRowID_ ? *id :
"{}"_f(::sqlite3_last_insert_rowid (fConnectionRep_->fDB_));
498 virtual optional<Document::Document> Get (
const IDType&
id,
const optional<Projection>& projection)
override
500#if USE_NOISY_TRACE_IN_THIS_MODULE_
501 TraceContextBumper ctx{
"SQLite::CollectionRep_::Get",
"id={}, projection={}"_f, id, projection};
503 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
504 return fConnectionRep_->WrapExecute_ (
507 optional<Projection> projectionWithoutID = projection;
508 bool includeIDInProjection =
true;
509 if (projectionWithoutID) {
510 auto [flags, fieldNames] = projectionWithoutID->GetFields ();
512 case Document::Projection::Flag::eInclude: {
513 if (fieldNames.
Contains (Document::kID)) {
514 fieldNames.Remove (Document::kID);
515 projectionWithoutID = Projection{flags, fieldNames};
518 includeIDInProjection =
false;
521 case Document::Projection::Flag::eOmit: {
522 if (fieldNames.
Contains (Document::kID)) {
523 includeIDInProjection =
false;
524 projectionWithoutID =
Projection{flags, fieldNames};
529 auto [sqliteProjection, remainingAfterProjection] = Partition_ (projectionWithoutID);
530 optional<MyPreparedStatement_> sqliteProjectionStatement;
531 bool ignoreResult =
false;
532 if (sqliteProjection) {
534 if (get<String> (*sqliteProjection).empty ()) {
535 sqliteProjectionStatement.emplace (fConnectionRep_->fDB_,
"select NULL from \"{}\" where id=?;"_f(fTableName_));
539 sqliteProjectionStatement.emplace (
540 fConnectionRep_->fDB_,
"select {} from \"{}\" where id=?;"_f(get<String> (*sqliteProjection), fTableName_));
543 else if (fGetOneStatement_ ==
nullptr) [[unlikely]] {
544 fGetOneStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_,
"select json from \"{}\" where id=?;"_f(fTableName_)};
546 ::sqlite3_stmt* useStatment = sqliteProjectionStatement.has_value () ? *sqliteProjectionStatement : fGetOneStatement_;
549 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (useStatment), fConnectionRep_->fDB_);
550 string idAsUTFSTR =
id.AsUTF8<
string> ();
551 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (useStatment, 1, idAsUTFSTR.c_str (),
552 static_cast<int> (idAsUTFSTR.length ()), SQLITE_TRANSIENT),
553 fConnectionRep_->fDB_);
555 int rc = ::sqlite3_step (useStatment);
556 optional<Document::Document> result;
557 if (rc == SQLITE_ROW) [[likely]] {
562 result = ExtractRowValueAfterStep_ (useStatment, 0u,
id, sqliteProjection, remainingAfterProjection);
564 rc = ::sqlite3_step (useStatment);
566 if (rc != SQLITE_DONE) [[unlikely]] {
567 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
569 if (includeIDInProjection and result.has_value ()) {
570 result->Add (Document::kID,
id);
578#if USE_NOISY_TRACE_IN_THIS_MODULE_
579 TraceContextBumper ctx{
"SQLite::CollectionRep_::GetAll",
"filter={}, projection={}"_f, filter, projection};
581 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
582 return fConnectionRep_->WrapExecute_ (
586 if (filter == nullopt and projection == nullopt) {
587 MyPreparedStatement_ statement{fConnectionRep_->fDB_,
"select id,json from \"{}\";"_f(fTableName_)};
588 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (statement), fConnectionRep_->fDB_);
590 while ((rc = ::sqlite3_step (statement)) == SQLITE_ROW) {
591 static const auto kJSONReader_ = Variant::JSON::Reader{};
592 String id = ExtractColumnText_ (statement, 0u);
593 VariantValue valueReadBackFromDB = kJSONReader_.Read (ExtractColumnText_ (statement, 1u));
595 vDoc.
Add (Document::kID,
id);
598 if (rc != SQLITE_DONE) [[unlikely]] {
599 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
602 else if (filter == nullopt and projection == kOnlyIDs) {
603 MyPreparedStatement_ statement{fConnectionRep_->fDB_,
"select id from \"{}\";"_f(fTableName_)};
604 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (statement), fConnectionRep_->fDB_);
606 while ((rc = ::sqlite3_step (statement)) == SQLITE_ROW) {
607 String id = ExtractColumnText_ (statement, 0u);
609 vDoc.
Add (Document::kID,
id);
612 if (rc != SQLITE_DONE) [[unlikely]] {
613 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
618 auto [sqliteWhereClause, remainingFilter] = Partition_ (filter);
624 auto [sqliteProjection, remainingAfterProjection] =
625 remainingFilter ? make_tuple (nullopt, projection) : Partition_ (projection);
627 MyPreparedStatement_ statement{
628 fConnectionRep_->fDB_,
629 "select id,{} from \"{}\" {};"_f(sqliteProjection == nullopt ?
"json"_k : get<
String> (*sqliteProjection), fTableName_,
630 sqliteWhereClause == nullopt ?
"" : (
"where "_k + *sqliteWhereClause))};
632 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (statement), fConnectionRep_->fDB_);
634 while ((rc = ::sqlite3_step (statement)) == SQLITE_ROW) {
635 String id = ExtractColumnText_ (statement, 0u);
636 Document::Document vDoc = ExtractRowValueAfterStep_ (statement, 1u,
id, sqliteProjection, remainingAfterProjection);
637 if (remainingFilter == nullopt or remainingFilter->Matches (vDoc)) {
638 if (remainingAfterProjection) {
639 vDoc = remainingAfterProjection->
Apply (vDoc);
644 if (rc != SQLITE_DONE) [[unlikely]] {
645 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
654#if USE_NOISY_TRACE_IN_THIS_MODULE_
657 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
658 fConnectionRep_->WrapExecute_ (
661 if (onlyTheseFields) {
666 Document::Document d2Update = onlyTheseFields ? Memory::ValueOfOrThrow (Get (
id, nullopt)) : uploadDoc;
668 if (onlyTheseFields) {
669 d2Update.
AddAll (uploadDoc);
675 if (fUpdateStatement_ ==
nullptr) [[unlikely]] {
676 fUpdateStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_,
"update \"{}\" SET json=? where id=?;"_f(fTableName_)};
678 static const auto kJSONWriter_ = Variant::JSON::Writer{};
679 string r = kJSONWriter_.WriteAsString (
VariantValue{d2Update}).AsUTF8<string> ();
680 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fUpdateStatement_), fConnectionRep_->fDB_);
681 string idText =
id.AsUTF8<
string> ();
682 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fUpdateStatement_, 1, r.c_str (),
static_cast<int> (r.length ()), SQLITE_TRANSIENT),
683 fConnectionRep_->fDB_);
684 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fUpdateStatement_, 2, idText.c_str (),
685 static_cast<int> (idText.length ()), SQLITE_TRANSIENT),
686 fConnectionRep_->fDB_);
687 int rc = ::sqlite3_step (fUpdateStatement_);
688 if (rc != SQLITE_DONE) {
689 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
694 virtual void Remove (
const IDType&
id)
override
696#if USE_NOISY_TRACE_IN_THIS_MODULE_
699 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
700 if (fRemoveStatement_ ==
nullptr) [[unlikely]] {
701 fRemoveStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_,
"delete from \"{}\" where id=?;"_f(fTableName_)};
703 fConnectionRep_->WrapExecute_ (
705 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fRemoveStatement_), fConnectionRep_->fDB_);
706 string idText =
id.AsUTF8<
string> ();
707 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fRemoveStatement_, 1, idText.c_str (),
708 static_cast<int> (idText.length ()), SQLITE_TRANSIENT),
709 fConnectionRep_->fDB_);
710 int rc = ::sqlite3_step (fRemoveStatement_);
711 if (rc != SQLITE_DONE) {
712 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
719 ConnectionRep_ (
const Options& options)
721 , fAllowUserDefinedRowID_{options.fAddAllowsExternallySpecifiedIDs}
727 switch (options.fThreadingMode.value_or (Options::kDefault_ThreadingMode)) {
728 case Options::ThreadingMode::eSingleThread:
730 case Options::ThreadingMode::eMultiThread:
731 Require (CompiledOptions::kThe.THREADSAFE);
732 Require (::sqlite3_threadsafe ());
733 flags |= SQLITE_OPEN_NOMUTEX;
735 case Options::ThreadingMode::eSerialized:
736 Require (CompiledOptions::kThe.THREADSAFE);
737 Require (::sqlite3_threadsafe ());
738 flags |= SQLITE_OPEN_FULLMUTEX;
742 if (options.fImmutable) {
745 Require (options.fReadOnly);
747 flags |= options.fReadOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
751 [[maybe_unused]]
int n{};
752 if (options.fDBPath) {
755 if (options.fTemporaryDB) {
758 if (options.fInMemoryDB) {
763 if (options.fDBPath) {
764 uriArg = options.fDBPath->generic_string ();
765 if (uriArg[0] ==
':') {
766 uriArg =
"./" + uriArg;
769 if (options.fTemporaryDB) {
773 Require (not options.fTemporaryDB->empty ());
775 if (options.fInMemoryDB) {
782 flags |= SQLITE_OPEN_MEMORY;
783 flags |= SQLITE_OPEN_URI;
784 flags |= SQLITE_OPEN_SHAREDCACHE;
785 Require (not options.fReadOnly);
786 Require (options.fCreateDBPathIfDoesNotExist);
787 uriArg = options.fInMemoryDB->AsNarrowSDKString ();
788 if (uriArg.empty ()) {
792 u8string safeCharURI = IO::Network::UniformResourceIdentification::PCTEncode (u8string{uriArg.begin (), uriArg.end ()}, {});
793 uriArg =
"file:" +
string{safeCharURI.begin (), safeCharURI.end ()} +
"?mode=memory&cache=shared";
799 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, flags, options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_CANTOPEN) {
800 if (options.fCreateDBPathIfDoesNotExist) {
801 if (fDB_ !=
nullptr) {
802 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
805 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, SQLITE_OPEN_CREATE | flags,
806 options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_OK) {
811 if (e != SQLITE_OK) [[unlikely]] {
812 [[maybe_unused]]
auto&& cleanup =
Finally ([
this] ()
noexcept {
813 if (fDB_ !=
nullptr) {
814 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
817 ThrowSQLiteError_ (e, fDB_);
819 SetBusyTimeout (options.fBusyTimeout.value_or (Options::kBusyTimeout_Default));
820 if (options.fJournalMode) {
821 SetJournalMode (*options.fJournalMode);
828 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
830 virtual shared_ptr<const EngineProperties> GetEngineProperties ()
const override
833 virtual String GetEngineName ()
const override
838 static const shared_ptr<const EngineProperties> kProps_ = Memory::MakeSharedPtr<const MyEngineProperties_> ();
841 virtual Database::Document::Connection::Options GetOptions ()
const override
845 virtual uintmax_t GetSpaceConsumed ()
const override
848 auto incSize = [&] (
const filesystem::path& p) {
850 uintmax_t sz = filesystem::file_size (p, ec);
855 if (fOptions_.fDBPath) {
856 incSize (*fOptions_.fDBPath);
857 incSize (filesystem::path{*fOptions_.fDBPath}.concat (
"-journal"));
858 incSize (filesystem::path{*fOptions_.fDBPath}.concat (
"-shm"));
859 incSize (filesystem::path{*fOptions_.fDBPath}.concat (
"-wal"));
869 return WrapExecute_ (
872 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
874 results.
Add (String::FromUTF8 (argv[0]));
878 int execResult = ::sqlite3_exec (fDB_,
"SELECT name FROM sqlite_master WHERE type='table';",
879 callback.GetStaticFunction (), callback.GetData (),
nullptr);
880 callback.ThrowIfCapturedException ();
881 ThrowSQLiteErrorIfNotOK_ (execResult, fDB_);
888 return WrapExecute_ (
891 if (fAllowUserDefinedRowID_) {
892 Exec (
"create table if not exists \"{}\" (id TEXT PRIMARY KEY, json NOT NULL) WITHOUT ROWID;"_f(name));
895 Exec (
"create table if not exists \"{}\" (id INTEGER PRIMARY KEY, json NOT NULL);"_f(name));
898 Memory::MakeSharedPtr<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
902 virtual void DropCollection (
const String& name)
override
904 WrapExecute_ ([&] () { Exec (
"drop table \"{}\";"_f(name)); }, name,
true);
908 scoped_lock declareContext{fMaybeLock_};
909 Require (GetCollections ().Contains (name));
911 Memory::MakeSharedPtr<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
915 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
916 return Database::Document::SQLite::Transaction{conn};
918 virtual void Exec (
const String& sql)
override
920 scoped_lock declareContext{fMaybeLock_};
923 int e = ::sqlite3_exec (fDB_, sql.
AsUTF8<
string> ().c_str (),
nullptr,
nullptr,
nullptr);
924 if (e != SQLITE_OK) [[unlikely]] {
925 ThrowSQLiteErrorIfNotOK_ (e, fDB_);
930 virtual Duration GetBusyTimeout ()
const override
932 scoped_lock declareContext{fMaybeLock_};
934 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
936 Assert (::strcmp (azColName[0],
"timeout") == 0);
937 int val = ::atoi (argv[0]);
942 int execResult = ::sqlite3_exec (fDB_,
"pragma busy_timeout;", callback.GetStaticFunction (), callback.GetData (),
nullptr);
943 callback.ThrowIfCapturedException ();
944 ThrowSQLiteErrorIfNotOK_ (execResult, fDB_);
946 return Duration{double (*d) / 1000.0};
948 virtual void SetBusyTimeout (
const Duration& timeout)
override
950 scoped_lock declareContext{fMaybeLock_};
951 ThrowSQLiteErrorIfNotOK_ (::sqlite3_busy_timeout (fDB_, (
int)(timeout.
As<
float> () * 1000)), fDB_);
953 virtual JournalModeType GetJournalMode ()
const override
956 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
958 Assert (::strcmp (azColName[0],
"journal_mode") == 0);
962 int execResult = ::sqlite3_exec (fDB_,
"pragma journal_mode;", callback.GetStaticFunction (), callback.GetData (),
nullptr);
963 callback.ThrowIfCapturedException ();
964 ThrowSQLiteErrorIfNotOK_ (execResult, fDB_);
966 if (d ==
"delete"sv) {
967 return JournalModeType::eDelete;
969 if (d ==
"truncate"sv) {
970 return JournalModeType::eTruncate;
972 if (d ==
"persist"sv) {
973 return JournalModeType::ePersist;
975 if (d ==
"memory"sv) {
976 return JournalModeType::eMemory;
979 return JournalModeType::eWAL;
982 return JournalModeType::eWAL2;
985 return JournalModeType::eOff;
988 return JournalModeType::eDelete;
990 virtual void SetJournalMode (JournalModeType journalMode)
override
992 scoped_lock declareContext{fMaybeLock_};
993 switch (journalMode) {
994 case JournalModeType::eDelete:
995 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'delete';",
nullptr, 0,
nullptr), fDB_);
997 case JournalModeType::eTruncate:
998 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'truncate';",
nullptr, 0,
nullptr), fDB_);
1000 case JournalModeType::ePersist:
1001 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'persist';",
nullptr, 0,
nullptr), fDB_);
1003 case JournalModeType::eMemory:
1004 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'memory';",
nullptr, 0,
nullptr), fDB_);
1006 case JournalModeType::eWAL:
1007 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'wal';",
nullptr, 0,
nullptr), fDB_);
1009 case JournalModeType::eWAL2:
1010 if (GetJournalMode () == JournalModeType::eWAL) {
1011 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'delete';",
nullptr, 0,
nullptr), fDB_);
1013 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'wal2';",
nullptr, 0,
nullptr), fDB_);
1015 case JournalModeType::eOff:
1016 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'off';",
nullptr, 0,
nullptr), fDB_);
1021 template <
typename FUN>
1022 inline auto WrapExecute_ (FUN&& f,
const optional<String>& collectionName,
bool write) -> invoke_result_t<FUN>
1024 return Document::Connection::Private_::WrapLoggingExecuteHelper_ (forward<FUN> (f),
this, fOptions_, collectionName, write);
1036Document::SQLite::Connection::Ptr::Ptr (
const shared_ptr<IRep>& src)
1038 , busyTimeout{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
1039 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
1041 return thisObj->operator->()->GetBusyTimeout ();
1043 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
auto* property,
auto timeout) {
1044 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
1046 thisObj->operator->()->SetBusyTimeout (timeout);
1048 , journalMode{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
const auto* property) {
1049 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
1051 return thisObj->operator->()->GetJournalMode ();
1053 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
auto* property,
auto journalMode) {
1054 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
1056 thisObj->operator->()->SetJournalMode (journalMode);
1059#if qStroika_Foundation_Debug_AssertExternallySynchronizedChecker_Enabled
1060 if (src !=
nullptr) {
1061 _fAssertExternallySynchronizedChecker.SetAssertExternallySynchronizedCheckerContext (src->fAssertExternallySynchronizedChecker.GetSharedContext ());
1071auto Document::SQLite::Connection::New (
const Options& options) -> Ptr
1073 switch (options.fInternallySynchronizedLetter) {
1074 case Execution::eInternallySynchronized:
1075 return Ptr{Memory::MakeSharedPtr<ConnectionRep_<Execution::eInternallySynchronized>> (options)};
1076 case Execution::eNotKnownInternallySynchronized:
1077 return Ptr{Memory::MakeSharedPtr<ConnectionRep_<Execution::eNotKnownInternallySynchronized>> (options)};
1090 MyRep_ (
const Connection::Ptr& db, Flag f)
1091 : fConnectionPtr_{db}
1094 case Flag::eDeferred:
1095 db->Exec (
"BEGIN DEFERRED TRANSACTION;"sv);
1097 case Flag::eExclusive:
1098 db->Exec (
"BEGIN EXCLUSIVE TRANSACTION;"sv);
1100 case Flag::eImmediate:
1101 db->Exec (
"BEGIN IMMEDIATE TRANSACTION;"sv);
1107 virtual void Commit ()
override
1109 Require (not fCompleted_);
1111 fConnectionPtr_->Exec (
"COMMIT TRANSACTION;"sv);
1115 Require (not fCompleted_);
1117 fConnectionPtr_->Exec (
"ROLLBACK TRANSACTION;"sv);
1122 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
1124 Connection::Ptr fConnectionPtr_;
1125 bool fCompleted_{
false};
1127Transaction::Transaction (
const Connection::Ptr& db, Flag f)
1128 : inherited{make_unique<MyRep_> (db, f)}
#define RequireNotReached()
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
#define WeakAssertNotImplemented()
#define RequireNotNull(p)
#define AssertNotReached()
variant< Equals > Operation
#define Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE
[[msvc::no_unique_address]] isn't always broken in MSVC. Annotate with this on things where its not b...
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual bool empty() const noexcept
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual T AsUTF8() const
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
nonvirtual bool ContainsKey(ArgByValueType< key_type > key) const
nonvirtual unsigned int AddAll(ITERABLE_OF_ADDABLE &&items, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
nonvirtual bool RemoveIf(ArgByValueType< key_type > key)
Remove the given item, if it exists. Return true if found and removed.
nonvirtual void RemoveAll()
RemoveAll removes all, or all matching (predicate, iterator range, equals comparer or whatever) items...
nonvirtual Iterable< key_type > Keys() const
nonvirtual void RetainAll(const ITERABLE_OF_KEY_TYPE &items)
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual void push_back(ArgByValueType< value_type > item)
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
nonvirtual RETURNTYPE As() const
EngineProperties captures the features associated with a given database engine (being talked to throu...
define a (simple) projection on a document, subsetting the fields of that document.
virtual Disposition GetDisposition() const =0
virtual void Rollback()=0
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Duration is a chrono::duration<double> (=.
nonvirtual bool Contains(ArgByValueType< T > element, EQUALS_COMPARER &&equalsComparer=EQUALS_COMPARER{}) const
nonvirtual size_t size() const
Returns the number of items contained.
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement) const
Run the argument function (or lambda) on each element of the container.
nonvirtual bool empty() const
Returns true iff size() == 0.
An Iterator<T> is a copyable object which allows traversing the contents of some container.
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
void ThrowError(error_code ec)
throw a SystemErrorException with the given error_code - or std::bad_alloc for errc::not_enough_memor...