4#include "Stroika/Foundation/StroikaPreComp.h"
10#include "Stroika/Foundation/Common/GUID.h"
21using namespace Characters;
22using namespace Containers;
23using namespace Common;
25using namespace DataExchange;
26using namespace Database;
27using namespace Database::Document::SQLite;
28using namespace Execution;
36using Database::Document::kID;
37using Database::Document::kOnlyIDs;
44#if qStroika_HasComponent_sqlite
46 struct ModuleShutdown_ {
49 Verify (::sqlite3_shutdown () == SQLITE_OK);
52 [[noreturn]]
void ThrowSQLiteError_ (
int errCode, sqlite3* sqliteConnection)
54 Require (errCode != SQLITE_OK);
55 optional<String> errMsgDetails;
56 if (sqliteConnection !=
nullptr) {
57 errMsgDetails = String::FromUTF8 (::sqlite3_errmsg (sqliteConnection));
62 Throw (system_error{make_error_code (errc::device_or_resource_busy)});
66 Throw (system_error{make_error_code (errc::device_or_resource_busy)});
68 case SQLITE_CONSTRAINT: {
73 static const auto kEx_ =
Exception{
"SQLITE_CONSTRAINT"sv};
78 static const auto kEx_ =
Exception{
"SQLITE_TOOBIG"sv};
83 Throw (system_error{make_error_code (errc::no_space_on_device)});
85 case SQLITE_READONLY: {
86 static const auto kEx_ =
Exception{
"SQLITE_READONLY"sv};
94 static const auto kEx_ =
Exception{
"SQLITE_MISUSE"sv};
103 static const auto kEx_ =
Exception{
"SQLITE_ERROR"sv};
108 DbgTrace (
"SQLITE_NOMEM translated to bad_alloc"_f);
113 Throw (
Exception{
"SQLite Error: {} (code {})"_f(errMsgDetails, errCode)});
119 void ThrowSQLiteErrorIfNotOK_ (
int errCode, sqlite3* sqliteConnection)
121 static_assert (SQLITE_OK == 0);
122 if (errCode != SQLITE_OK) [[unlikely]] {
123 ThrowSQLiteError_ (errCode, sqliteConnection);
130 template <invocable<
int,
char**,
char**> CB>
131 struct SQLiteCallback_ {
134 using STATIC_FUNCTION_TYPE = int (*) (
void*, int,
char**,
char**);
136 SQLiteCallback_ (CB&& cb)
137 : fCallback_{forward<CB> (cb)}
140 STATIC_FUNCTION_TYPE GetStaticFunction ()
150 static int STATICFUNC_ (
void* SQLiteCallbackData,
int argc,
char** argv,
char** azColName)
152 SQLiteCallback_* sqc =
reinterpret_cast<SQLiteCallback_*
> (SQLiteCallbackData);
153 return sqc->fCallback_ (argc, argv, azColName);
158 struct MyPreparedStatement_ {
159 MyPreparedStatement_ () =
default;
160 MyPreparedStatement_ (::sqlite3* db,
const String& statement)
163#if USE_NOISY_TRACE_IN_THIS_MODULE_
166 const char* pzTail =
nullptr;
167 string utfStatement = statement.
AsUTF8<
string> ();
168 ThrowSQLiteErrorIfNotOK_ (::sqlite3_prepare_v2 (db, utfStatement.c_str (), -1, &fObj_, &pzTail), db);
169 Assert (pzTail !=
nullptr);
170 Require (*pzTail ==
'\0');
173 MyPreparedStatement_ (
const MyPreparedStatement_&) =
delete;
174 MyPreparedStatement_ (MyPreparedStatement_&&) noexcept = default;
175 ~MyPreparedStatement_ ()
177 if (fObj_ !=
nullptr) {
178 (void)::sqlite3_finalize (fObj_);
181 MyPreparedStatement_& operator= (
const MyPreparedStatement_&) =
delete;
182 MyPreparedStatement_& operator= (MyPreparedStatement_&& rhs)
noexcept
188 operator ::sqlite3_stmt* ()
const
194 ::sqlite3_stmt* fObj_{
nullptr};
196 static_assert (movable<MyPreparedStatement_>);
197 static_assert (not copyable<MyPreparedStatement_>);
199 String ExtractColumnText_ (::sqlite3_stmt* statement,
unsigned int col)
202 Require (col <
static_cast<unsigned int> (::sqlite3_column_count (statement)));
203 const char* t =
reinterpret_cast<const char*
> (::sqlite3_column_text (statement,
static_cast<int> (col)));
204 if (t ==
nullptr) [[unlikely]] {
205 switch (
int colType = ::sqlite3_column_type (statement, col)) {
214 return String::FromUTF8 (t);
224 struct VerifyFlags_ {
227 Assert (CompiledOptions::kThe.ENABLE_NORMALIZE == !!::sqlite3_compileoption_used (
"ENABLE_NORMALIZE"));
228 Assert (CompiledOptions::kThe.THREADSAFE == !!::sqlite3_compileoption_used (
"THREADSAFE"));
229#if SQLITE_VERSION_NUMBER < 3038000
230 Assert (::sqlite3_compileoption_used (
"ENABLE_JSON1"));
232 Assert (!::sqlite3_compileoption_used (
"OMIT_JSON1"));
242 tuple<optional<String>, optional<Filter>> Partition_ (
const optional<Filter>& filter)
254 bool transferred =
false;
255 if (
const Document::FilterElements::Equals* eqOp = get_if<Document::FilterElements::Equals> (&op)) {
257 if (not whereClause.
empty ()) {
258 whereClause <<
" AND "sv;
261 if (eqOp->fLHS == Database::Document::kID) {
262 whereClause <<
"{} = '{}'"_f(Database::Document::kID, rhsValue->As<
String> ());
267 if (rhsValue->GetType () == VariantValue::eString or rhsValue->GetType () == VariantValue::eDate or
268 rhsValue->GetType () == VariantValue::eDateTime) {
269 vSQL =
"'"sv + vSQL +
"'"sv;
271 whereClause <<
"json_extract(json, '$.{}') = {}"_f(
String{eqOp->fLHS}, vSQL);
276 if (not transferred) {
280 if (not whereClause.
empty ()) {
282 return make_tuple (whereClause, clientSideOps.
empty () ? optional<Filter>{} : make_optional (
Filter{clientSideOps}));
285 return make_tuple (nullopt, filter);
287 return make_tuple (nullopt, nullopt);
296 tuple<optional<tuple<String, Sequence<String>>>, optional<Projection>> Partition_ (
const optional<Projection>& p)
302 tuple<Document::Projection::Flag, Set<String>> fields = p->GetFields ();
304 if (get<Document::Projection::Flag> (fields) == Document::Projection::Flag::eInclude) {
307 for (
String f : get<1> (fields)) {
308 String mongoFieldName = f;
309 Require (f != Document::kID);
310 if (fieldNames.
empty ()) {
311 projectionQuery <<
"json_extract(json,'"sv;
314 projectionQuery <<
","sv;
316 projectionQuery <<
"$."sv << mongoFieldName;
319 if (not fieldNames.
empty ()) {
320 projectionQuery <<
"')"sv;
322 return make_tuple (make_tuple (projectionQuery, fieldNames), nullopt);
325 return make_tuple (nullopt, p);
328 return make_tuple (nullopt, nullopt);
332 Document::Document ExtractRowValueAfterStep_ (::sqlite3_stmt* statement,
unsigned int dataCol,
const IDType&
id,
334 const optional<Projection>& remainingProjection)
340 static const auto kJSONReader_ = Variant::JSON::Reader{};
341 String colText = ExtractColumnText_ (statement, dataCol);
344 if (sqliteProjection == nullopt) {
348 auto arrayOfFieldNames = get<Sequence<String>> (*sqliteProjection);
349 if (arrayOfFieldNames.size () == 1) {
353 Assert (valueReadBackFromDB.GetType () == VariantValue::eArray);
358 Assert (nameI == arrayOfFieldNames.end ());
361 if ((sqliteProjection and get<
Sequence<String>> (*sqliteProjection).
Contains (Document::kID)) or remainingProjection == nullopt or
362 remainingProjection->Includes (Document::kID)) {
363 dr.
Add (Document::kID,
id);
365 if (remainingProjection) {
366 dr = remainingProjection->
Apply (dr);
373 using Connection::Options;
375 template <InternallySynchronized SYNC_STYLE>
377 conditional_t<SYNC_STYLE == InternallySynchronized::eNotKnownInternallySynchronized, Debug::AssertExternallySynchronizedMutex, recursive_mutex>;
378 static_assert (Common::StdCompat::BasicLockable<MyMaybeLock_<InternallySynchronized::eNotKnownInternallySynchronized>>);
379 static_assert (Common::StdCompat::BasicLockable<MyMaybeLock_<InternallySynchronized::eInternallySynchronized>>);
381 template <Execution::InternallySynchronized SYNC_STYLE>
382 struct ConnectionRep_ final : Database::Document::SQLite::Connection::IRep {
384 const Options fOptions_;
385 const bool fAllowUserDefinedRowID_{
false};
389 shared_ptr<ConnectionRep_> fConnectionRep_;
392 MyPreparedStatement_ fAddStatement_{};
393 MyPreparedStatement_ fGetOneStatement_{};
394 MyPreparedStatement_ fRemoveStatement_{};
395 MyPreparedStatement_ fUpdateStatement_{};
397 CollectionRep_ (
const shared_ptr<ConnectionRep_>& connectionRep,
const String& collectionName)
398 : fConnectionRep_{connectionRep}
399 , fTableName_{collectionName}
402 virtual ~CollectionRep_ () =
default;
403 virtual String GetName ()
const override
409#if USE_NOISY_TRACE_IN_THIS_MODULE_
412 Require (not v.
ContainsKey (Database::Document::kID) or fConnectionRep_->fOptions_.fAddAllowsExternallySpecifiedIDs);
413 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
415 return fConnectionRep_->WrapExecute_ (
422 if (fConnectionRep_->fAllowUserDefinedRowID_) {
423 if (auto o = v.Lookup (Database::Document::kID)) {
424 id = o->As<String> ();
425 jsonValue2Write.RemoveIf (Database::Document::kID);
428 id = Common::GUID::GenerateNew ().As<String> ();
431 Assert (not jsonValue2Write.
ContainsKey (Database::Document::kID));
441 if (fAddStatement_ ==
nullptr) [[unlikely]] {
442 if (fConnectionRep_->fAllowUserDefinedRowID_) {
444 MyPreparedStatement_{fConnectionRep_->fDB_,
"insert into \"{}\" (id, json) values(?,?);"_f(fTableName_)};
447 fAddStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_,
"insert into \"{}\" (json) values(?);"_f(fTableName_)};
450 static const auto kJSONWriter_ = Variant::JSON::Writer{};
451 string jsonText = kJSONWriter_.WriteAsString (
VariantValue{jsonValue2Write}).AsUTF8<string> ();
452 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fAddStatement_), fConnectionRep_->fDB_);
453 if (fConnectionRep_->fAllowUserDefinedRowID_) {
454 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fAddStatement_, 1, id->AsUTF8<
string> ().c_str (),
455 static_cast<int> (id->AsUTF8 ().length ()), SQLITE_TRANSIENT),
456 fConnectionRep_->fDB_);
458 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fAddStatement_, fConnectionRep_->fAllowUserDefinedRowID_ ? 2 : 1,
459 jsonText.c_str (), static_cast<int> (jsonText.length ()), SQLITE_TRANSIENT),
460 fConnectionRep_->fDB_);
461 int rc = ::sqlite3_step (fAddStatement_);
462 if (rc != SQLITE_DONE) {
463 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
465 return fConnectionRep_->fAllowUserDefinedRowID_ ? *id :
"{}"_f(::sqlite3_last_insert_rowid (fConnectionRep_->fDB_));
469 virtual optional<Document::Document> Get (
const IDType&
id,
const optional<Projection>& projection)
override
471#if USE_NOISY_TRACE_IN_THIS_MODULE_
472 TraceContextBumper ctx{
"SQLite::CollectionRep_::Get",
"id={}, projection={}"_f, id, projection};
474 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
475 return fConnectionRep_->WrapExecute_ (
478 optional<Projection> projectionWithoutID = projection;
479 bool includeIDInProjection =
true;
480 if (projectionWithoutID) {
481 auto [flags, fieldNames] = projectionWithoutID->GetFields ();
483 case Document::Projection::Flag::eInclude: {
484 if (fieldNames.
Contains (Document::kID)) {
485 fieldNames.Remove (Document::kID);
486 projectionWithoutID = Projection{flags, fieldNames};
489 includeIDInProjection =
false;
492 case Document::Projection::Flag::eOmit: {
493 if (fieldNames.
Contains (Document::kID)) {
494 includeIDInProjection =
false;
495 projectionWithoutID =
Projection{flags, fieldNames};
500 auto [sqliteProjection, remainingAfterProjection] = Partition_ (projectionWithoutID);
501 optional<MyPreparedStatement_> sqliteProjectionStatement;
502 bool ignoreResult =
false;
503 if (sqliteProjection) {
505 if (get<String> (*sqliteProjection).empty ()) {
506 sqliteProjectionStatement.emplace (fConnectionRep_->fDB_,
"select NULL from \"{}\" where id=?;"_f(fTableName_));
510 sqliteProjectionStatement.emplace (
511 fConnectionRep_->fDB_,
"select {} from \"{}\" where id=?;"_f(get<String> (*sqliteProjection), fTableName_));
514 else if (fGetOneStatement_ ==
nullptr) [[unlikely]] {
515 fGetOneStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_,
"select json from \"{}\" where id=?;"_f(fTableName_)};
517 ::sqlite3_stmt* useStatment = sqliteProjectionStatement.has_value () ? *sqliteProjectionStatement : fGetOneStatement_;
520 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (useStatment), fConnectionRep_->fDB_);
521 string idAsUTFSTR =
id.AsUTF8<
string> ();
522 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (useStatment, 1, idAsUTFSTR.c_str (),
523 static_cast<int> (idAsUTFSTR.length ()), SQLITE_TRANSIENT),
524 fConnectionRep_->fDB_);
526 int rc = ::sqlite3_step (useStatment);
527 optional<Document::Document> result;
528 if (rc == SQLITE_ROW) [[likely]] {
533 result = ExtractRowValueAfterStep_ (useStatment, 0u,
id, sqliteProjection, remainingAfterProjection);
535 rc = ::sqlite3_step (useStatment);
537 if (rc != SQLITE_DONE) [[unlikely]] {
538 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
540 if (includeIDInProjection and result.has_value ()) {
541 result->Add (Document::kID,
id);
549#if USE_NOISY_TRACE_IN_THIS_MODULE_
550 TraceContextBumper ctx{
"SQLite::CollectionRep_::GetAll",
"filter={}, projection={}"_f, filter, projection};
552 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
553 return fConnectionRep_->WrapExecute_ (
557 if (filter == nullopt and projection == nullopt) {
558 MyPreparedStatement_ statement{fConnectionRep_->fDB_,
"select id,json from \"{}\";"_f(fTableName_)};
559 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (statement), fConnectionRep_->fDB_);
561 while ((rc = ::sqlite3_step (statement)) == SQLITE_ROW) {
562 static const auto kJSONReader_ = Variant::JSON::Reader{};
563 String id = ExtractColumnText_ (statement, 0u);
564 VariantValue valueReadBackFromDB = kJSONReader_.Read (ExtractColumnText_ (statement, 1u));
566 vDoc.
Add (Document::kID,
id);
569 if (rc != SQLITE_DONE) [[unlikely]] {
570 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
573 else if (filter == nullopt and projection == kOnlyIDs) {
574 MyPreparedStatement_ statement{fConnectionRep_->fDB_,
"select id from \"{}\";"_f(fTableName_)};
575 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (statement), fConnectionRep_->fDB_);
577 while ((rc = ::sqlite3_step (statement)) == SQLITE_ROW) {
578 String id = ExtractColumnText_ (statement, 0u);
580 vDoc.
Add (Document::kID,
id);
583 if (rc != SQLITE_DONE) [[unlikely]] {
584 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
589 auto [sqliteWhereClause, remainingFilter] = Partition_ (filter);
595 auto [sqliteProjection, remainingAfterProjection] =
596 remainingFilter ? make_tuple (nullopt, projection) : Partition_ (projection);
598 MyPreparedStatement_ statement{
599 fConnectionRep_->fDB_,
600 "select id,{} from \"{}\" {};"_f(sqliteProjection == nullopt ?
"json"_k : get<
String> (*sqliteProjection), fTableName_,
601 sqliteWhereClause == nullopt ?
"" : (
"where "_k + *sqliteWhereClause))};
603 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (statement), fConnectionRep_->fDB_);
605 while ((rc = ::sqlite3_step (statement)) == SQLITE_ROW) {
606 String id = ExtractColumnText_ (statement, 0u);
607 Document::Document vDoc = ExtractRowValueAfterStep_ (statement, 1u,
id, sqliteProjection, remainingAfterProjection);
608 if (remainingFilter == nullopt or remainingFilter->Matches (vDoc)) {
609 if (remainingAfterProjection) {
610 vDoc = remainingAfterProjection->
Apply (vDoc);
615 if (rc != SQLITE_DONE) [[unlikely]] {
616 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
625#if USE_NOISY_TRACE_IN_THIS_MODULE_
628 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
629 fConnectionRep_->WrapExecute_ (
632 if (onlyTheseFields) {
637 Document::Document d2Update = onlyTheseFields ? Memory::ValueOfOrThrow (Get (
id, nullopt)) : uploadDoc;
639 if (onlyTheseFields) {
640 d2Update.
AddAll (uploadDoc);
646 if (fUpdateStatement_ ==
nullptr) [[unlikely]] {
647 fUpdateStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_,
"update \"{}\" SET json=? where id=?;"_f(fTableName_)};
649 static const auto kJSONWriter_ = Variant::JSON::Writer{};
650 string r = kJSONWriter_.WriteAsString (
VariantValue{d2Update}).AsUTF8<string> ();
651 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fUpdateStatement_), fConnectionRep_->fDB_);
652 string idText =
id.AsUTF8<
string> ();
653 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fUpdateStatement_, 1, r.c_str (),
static_cast<int> (r.length ()), SQLITE_TRANSIENT),
654 fConnectionRep_->fDB_);
655 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fUpdateStatement_, 2, idText.c_str (),
656 static_cast<int> (idText.length ()), SQLITE_TRANSIENT),
657 fConnectionRep_->fDB_);
658 int rc = ::sqlite3_step (fUpdateStatement_);
659 if (rc != SQLITE_DONE) {
660 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
665 virtual void Remove (
const IDType&
id)
override
667#if USE_NOISY_TRACE_IN_THIS_MODULE_
670 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
671 if (fRemoveStatement_ ==
nullptr) [[unlikely]] {
672 fRemoveStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_,
"delete from \"{}\" where id=?;"_f(fTableName_)};
674 fConnectionRep_->WrapExecute_ (
676 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fRemoveStatement_), fConnectionRep_->fDB_);
677 string idText =
id.AsUTF8<
string> ();
678 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fRemoveStatement_, 1, idText.c_str (),
679 static_cast<int> (idText.length ()), SQLITE_TRANSIENT),
680 fConnectionRep_->fDB_);
681 int rc = ::sqlite3_step (fRemoveStatement_);
682 if (rc != SQLITE_DONE) {
683 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
690 ConnectionRep_ (
const Options& options)
692 , fAllowUserDefinedRowID_{options.fAddAllowsExternallySpecifiedIDs}
698 switch (options.fThreadingMode.value_or (Options::kDefault_ThreadingMode)) {
699 case Options::ThreadingMode::eSingleThread:
701 case Options::ThreadingMode::eMultiThread:
702 Require (CompiledOptions::kThe.THREADSAFE);
703 Require (::sqlite3_threadsafe ());
704 flags |= SQLITE_OPEN_NOMUTEX;
706 case Options::ThreadingMode::eSerialized:
707 Require (CompiledOptions::kThe.THREADSAFE);
708 Require (::sqlite3_threadsafe ());
709 flags |= SQLITE_OPEN_FULLMUTEX;
713 if (options.fImmutable) {
716 Require (options.fReadOnly);
718 flags |= options.fReadOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
722 [[maybe_unused]]
int n{};
723 if (options.fDBPath) {
726 if (options.fTemporaryDB) {
729 if (options.fInMemoryDB) {
734 if (options.fDBPath) {
735 uriArg = options.fDBPath->generic_string ();
736 if (uriArg[0] ==
':') {
737 uriArg =
"./" + uriArg;
740 if (options.fTemporaryDB) {
744 Require (not options.fTemporaryDB->empty ());
746 if (options.fInMemoryDB) {
753 flags |= SQLITE_OPEN_MEMORY;
754 flags |= SQLITE_OPEN_URI;
755 flags |= SQLITE_OPEN_SHAREDCACHE;
756 Require (not options.fReadOnly);
757 Require (options.fCreateDBPathIfDoesNotExist);
758 uriArg = options.fInMemoryDB->AsNarrowSDKString ();
759 if (uriArg.empty ()) {
763 u8string safeCharURI = IO::Network::UniformResourceIdentification::PCTEncode (u8string{uriArg.begin (), uriArg.end ()}, {});
764 uriArg =
"file:" +
string{safeCharURI.begin (), safeCharURI.end ()} +
"?mode=memory&cache=shared";
770 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, flags, options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_CANTOPEN) {
771 if (options.fCreateDBPathIfDoesNotExist) {
772 if (fDB_ !=
nullptr) {
773 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
776 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, SQLITE_OPEN_CREATE | flags,
777 options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_OK) {
782 if (e != SQLITE_OK) [[unlikely]] {
783 [[maybe_unused]]
auto&& cleanup =
Finally ([
this] ()
noexcept {
784 if (fDB_ !=
nullptr) {
785 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
788 ThrowSQLiteError_ (e, fDB_);
790 SetBusyTimeout (options.fBusyTimeout.value_or (Options::kBusyTimeout_Default));
791 if (options.fJournalMode) {
792 SetJournalMode (*options.fJournalMode);
799 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
801 virtual shared_ptr<const EngineProperties> GetEngineProperties ()
const override
804 virtual String GetEngineName ()
const override
809 static const shared_ptr<const EngineProperties> kProps_ = Memory::MakeSharedPtr<const MyEngineProperties_> ();
812 virtual Database::Document::Connection::Options GetOptions ()
const override
816 virtual uintmax_t GetSpaceConsumed ()
const override
819 auto incSize = [&] (
const filesystem::path& p) {
821 uintmax_t sz = filesystem::file_size (p, ec);
826 if (fOptions_.fDBPath) {
827 incSize (*fOptions_.fDBPath);
828 incSize (filesystem::path{*fOptions_.fDBPath}.concat (
"-journal"));
829 incSize (filesystem::path{*fOptions_.fDBPath}.concat (
"-shm"));
830 incSize (filesystem::path{*fOptions_.fDBPath}.concat (
"-wal"));
840 return WrapExecute_ (
843 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
845 results.
Add (String::FromUTF8 (argv[0]));
848 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"SELECT name FROM sqlite_master WHERE type='table';",
849 callback.GetStaticFunction (), callback.GetData (),
nullptr),
857 return WrapExecute_ (
860 if (fAllowUserDefinedRowID_) {
861 Exec (
"create table if not exists \"{}\" (id TEXT PRIMARY KEY, json NOT NULL) WITHOUT ROWID;"_f(name));
864 Exec (
"create table if not exists \"{}\" (id INTEGER PRIMARY KEY, json NOT NULL);"_f(name));
867 Memory::MakeSharedPtr<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
871 virtual void DropCollection (
const String& name)
override
873 WrapExecute_ ([&] () { Exec (
"drop table \"{}\";"_f(name)); }, name,
true);
877 scoped_lock declareContext{fMaybeLock_};
878 Require (GetCollections ().Contains (name));
880 Memory::MakeSharedPtr<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
884 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
885 return Database::Document::SQLite::Transaction{conn};
887 virtual void Exec (
const String& sql)
override
889 scoped_lock declareContext{fMaybeLock_};
892 int e = ::sqlite3_exec (fDB_, sql.
AsUTF8<
string> ().c_str (),
nullptr,
nullptr,
nullptr);
893 if (e != SQLITE_OK) [[unlikely]] {
894 ThrowSQLiteErrorIfNotOK_ (e, fDB_);
899 virtual Duration GetBusyTimeout ()
const override
901 scoped_lock declareContext{fMaybeLock_};
903 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
905 Assert (::strcmp (azColName[0],
"timeout") == 0);
906 int val = ::atoi (argv[0]);
911 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma busy_timeout;", callback.GetStaticFunction (), callback.GetData (),
nullptr), fDB_);
913 return Duration{double (*d) / 1000.0};
915 virtual void SetBusyTimeout (
const Duration& timeout)
override
917 scoped_lock declareContext{fMaybeLock_};
918 ThrowSQLiteErrorIfNotOK_ (::sqlite3_busy_timeout (fDB_, (
int)(timeout.
As<
float> () * 1000)), fDB_);
920 virtual JournalModeType GetJournalMode ()
const override
923 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
925 Assert (::strcmp (azColName[0],
"journal_mode") == 0);
929 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode;", callback.GetStaticFunction (), callback.GetData (),
nullptr), fDB_);
931 if (d ==
"delete"sv) {
932 return JournalModeType::eDelete;
934 if (d ==
"truncate"sv) {
935 return JournalModeType::eTruncate;
937 if (d ==
"persist"sv) {
938 return JournalModeType::ePersist;
940 if (d ==
"memory"sv) {
941 return JournalModeType::eMemory;
944 return JournalModeType::eWAL;
947 return JournalModeType::eWAL2;
950 return JournalModeType::eOff;
953 return JournalModeType::eDelete;
955 virtual void SetJournalMode (JournalModeType journalMode)
override
957 scoped_lock declareContext{fMaybeLock_};
958 switch (journalMode) {
959 case JournalModeType::eDelete:
960 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'delete';",
nullptr, 0,
nullptr), fDB_);
962 case JournalModeType::eTruncate:
963 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'truncate';",
nullptr, 0,
nullptr), fDB_);
965 case JournalModeType::ePersist:
966 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'persist';",
nullptr, 0,
nullptr), fDB_);
968 case JournalModeType::eMemory:
969 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'memory';",
nullptr, 0,
nullptr), fDB_);
971 case JournalModeType::eWAL:
972 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'wal';",
nullptr, 0,
nullptr), fDB_);
974 case JournalModeType::eWAL2:
975 if (GetJournalMode () == JournalModeType::eWAL) {
976 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'delete';",
nullptr, 0,
nullptr), fDB_);
978 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'wal2';",
nullptr, 0,
nullptr), fDB_);
980 case JournalModeType::eOff:
981 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'off';",
nullptr, 0,
nullptr), fDB_);
986 template <
typename FUN>
987 inline auto WrapExecute_ (FUN&& f,
const optional<String>& collectionName,
bool write) -> invoke_result_t<FUN>
989 return Document::Connection::Private_::WrapLoggingExecuteHelper_ (forward<FUN> (f),
this, fOptions_, collectionName, write);
1001Document::SQLite::Connection::Ptr::Ptr (
const shared_ptr<IRep>& src)
1003 , busyTimeout{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
1004 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
1006 return thisObj->operator->()->GetBusyTimeout ();
1008 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
auto* property,
auto timeout) {
1009 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
1011 thisObj->operator->()->SetBusyTimeout (timeout);
1013 , journalMode{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
const auto* property) {
1014 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
1016 return thisObj->operator->()->GetJournalMode ();
1018 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
auto* property,
auto journalMode) {
1019 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
1021 thisObj->operator->()->SetJournalMode (journalMode);
1024#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
1025 if (src !=
nullptr) {
1026 _fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (src->fAssertExternallySynchronizedMutex.GetSharedContext ());
1036auto Document::SQLite::Connection::New (
const Options& options) -> Ptr
1038 switch (options.fInternallySynchronizedLetter) {
1039 case Execution::eInternallySynchronized:
1040 return Ptr{Memory::MakeSharedPtr<ConnectionRep_<Execution::eInternallySynchronized>> (options)};
1041 case Execution::eNotKnownInternallySynchronized:
1042 return Ptr{Memory::MakeSharedPtr<ConnectionRep_<Execution::eNotKnownInternallySynchronized>> (options)};
1055 MyRep_ (
const Connection::Ptr& db, Flag f)
1056 : fConnectionPtr_{db}
1059 case Flag::eDeferred:
1060 db->Exec (
"BEGIN DEFERRED TRANSACTION;"sv);
1062 case Flag::eExclusive:
1063 db->Exec (
"BEGIN EXCLUSIVE TRANSACTION;"sv);
1065 case Flag::eImmediate:
1066 db->Exec (
"BEGIN IMMEDIATE TRANSACTION;"sv);
1072 virtual void Commit ()
override
1074 Require (not fCompleted_);
1076 fConnectionPtr_->Exec (
"COMMIT TRANSACTION;"sv);
1080 Require (not fCompleted_);
1082 fConnectionPtr_->Exec (
"ROLLBACK TRANSACTION;"sv);
1087 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
1089 Connection::Ptr fConnectionPtr_;
1090 bool fCompleted_{
false};
1092Transaction::Transaction (
const Connection::Ptr& db, Flag f)
1093 : 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 qStroika_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
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
Duration is a chrono::duration<double> (=.
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement, Execution::SequencePolicy seq=Execution::SequencePolicy::eDEFAULT) const
Run the argument function (or lambda) on each element of the container.
nonvirtual bool Contains(ArgByValueType< T > element, EQUALS_COMPARER &&equalsComparer=EQUALS_COMPARER{}) const
nonvirtual size_t size() const
Returns the number of items contained.
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)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >