4#include "Stroika/Foundation/StroikaPreComp.h"
19using namespace Characters;
20using namespace Containers;
22using namespace DataExchange;
23using namespace Database;
24using namespace Database::Document::SQLite;
25using namespace Execution;
38#if qStroika_HasComponent_sqlite
40 struct ModuleShutdown_ {
43 Verify (::sqlite3_shutdown () == SQLITE_OK);
46 [[noreturn]]
void ThrowSQLiteError_ (
int errCode, sqlite3* sqliteConnection)
48 Require (errCode != SQLITE_OK);
49 optional<String> errMsgDetails;
50 if (sqliteConnection !=
nullptr) {
51 errMsgDetails = String::FromUTF8 (::sqlite3_errmsg (sqliteConnection));
56 Throw (system_error{make_error_code (errc::device_or_resource_busy)});
60 Throw (system_error{make_error_code (errc::device_or_resource_busy)});
62 case SQLITE_CONSTRAINT: {
67 static const auto kEx_ =
Exception{
"SQLITE_CONSTRAINT"sv};
72 static const auto kEx_ =
Exception{
"SQLITE_TOOBIG"sv};
77 Throw (system_error{make_error_code (errc::no_space_on_device)});
79 case SQLITE_READONLY: {
80 static const auto kEx_ =
Exception{
"SQLITE_READONLY"sv};
88 static const auto kEx_ =
Exception{
"SQLITE_MISUSE"sv};
97 static const auto kEx_ =
Exception{
"SQLITE_ERROR"sv};
102 DbgTrace (
"SQLITE_NOMEM translated to bad_alloc"_f);
107 Throw (
Exception{
"SQLite Error: {} (code {})"_f(errMsgDetails, errCode)});
113 void ThrowSQLiteErrorIfNotOK_ (
int errCode, sqlite3* sqliteConnection)
115 static_assert (SQLITE_OK == 0);
116 if (errCode != SQLITE_OK) [[unlikely]] {
117 ThrowSQLiteError_ (errCode, sqliteConnection);
124 template <invocable<
int,
char**,
char**> CB>
125 struct SQLiteCallback_ {
128 using STATIC_FUNCTION_TYPE = int (*) (
void*, int,
char**,
char**);
130 SQLiteCallback_ (CB&& cb)
131 : fCallback_{forward<CB> (cb)}
134 STATIC_FUNCTION_TYPE GetStaticFunction ()
144 static int STATICFUNC_ (
void* SQLiteCallbackData,
int argc,
char** argv,
char** azColName)
146 SQLiteCallback_* sqc =
reinterpret_cast<SQLiteCallback_*
> (SQLiteCallbackData);
147 return sqc->fCallback_ (argc, argv, azColName);
151 sqlite3_stmt* mkPreparedStatement_ (sqlite3* db,
const String& statement)
154 const char* pzTail =
nullptr;
155 sqlite3_stmt* result{
nullptr};
156 string utfStatement = statement.
AsUTF8<
string> ();
157 ThrowSQLiteErrorIfNotOK_ (::sqlite3_prepare_v2 (db, utfStatement.c_str (), -1, &result, &pzTail), db);
158 Assert (pzTail !=
nullptr);
159 Require (*pzTail ==
'\0');
171 struct VerifyFlags_ {
174 Assert (CompiledOptions::kThe.ENABLE_NORMALIZE == !!::sqlite3_compileoption_used (
"ENABLE_NORMALIZE"));
175 Assert (CompiledOptions::kThe.THREADSAFE == !!::sqlite3_compileoption_used (
"THREADSAFE"));
176#if SQLITE_VERSION_NUMBER < 3038000
177 Assert (::sqlite3_compileoption_used (
"ENABLE_JSON1"));
179 Assert (!::sqlite3_compileoption_used (
"OMIT_JSON1"));
186 using Connection::Options;
187 struct ConnectionRep_ final : Database::Document::SQLite::Connection::IRep {
193 shared_ptr<ConnectionRep_> fConnectionRep_;
196 ::sqlite3_stmt* fAddStatement_{
nullptr};
197 ::sqlite3_stmt* fGetOneStatement_{
nullptr};
199 CollectionRep_ (
const shared_ptr<ConnectionRep_>& connectionRep,
const String& collectionName)
200 : fConnectionRep_{connectionRep}
201 , fTableName_{collectionName}
203#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
204 fAssertExternallySynchronizedMutex_.SetAssertExternallySynchronizedMutexContext (
205 connectionRep->fAssertExternallySynchronizedMutex_.GetSharedContext ());
208 virtual ~CollectionRep_ ()
210 if (fAddStatement_ !=
nullptr) {
211 (void)::sqlite3_finalize (fAddStatement_);
213 if (fGetOneStatement_ !=
nullptr) {
214 (void)::sqlite3_finalize (fGetOneStatement_);
219#if USE_NOISY_TRACE_IN_THIS_MODULE_
231 if (fAddStatement_ ==
nullptr) [[unlikely]] {
232 fAddStatement_ = mkPreparedStatement_ (fConnectionRep_->fDB_,
"insert into {} (json) values(?);"_f(fTableName_));
234 string jsonText = Variant::JSON::Writer{}.WriteAsString (
VariantValue{v}).AsUTF8<string> ();
235 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fAddStatement_), fConnectionRep_->fDB_);
236 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fAddStatement_, 1, jsonText.c_str (),
static_cast<int> (jsonText.length ()), SQLITE_TRANSIENT),
237 fConnectionRep_->fDB_);
238 int rc = ::sqlite3_step (fAddStatement_);
239 if (rc != SQLITE_DONE) {
240 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
242 return "{}"_f(sqlite3_last_insert_rowid (fConnectionRep_->fDB_));
244 virtual optional<Document::Document> GetOne (
const IDType&
id,
const optional<Projection>& projection)
override
246#if USE_NOISY_TRACE_IN_THIS_MODULE_
252 if (fGetOneStatement_ ==
nullptr) [[unlikely]] {
253 fGetOneStatement_ = mkPreparedStatement_ (fConnectionRep_->fDB_,
"select json from {} where id=?;"_f(fTableName_));
256 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fGetOneStatement_), fConnectionRep_->fDB_);
257 string idAsUTFSTR =
id.AsUTF8<
string> ();
258 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fGetOneStatement_, 1, idAsUTFSTR.c_str (),
259 static_cast<int> (idAsUTFSTR.length ()), SQLITE_TRANSIENT),
260 fConnectionRep_->fDB_);
261 int rc = ::sqlite3_step (fGetOneStatement_);
262 optional<Document::Document> result;
263 if (rc == SQLITE_ROW) [[likely]] {
264 result = Variant::JSON::Reader{}
265 .Read (String::FromUTF8 (
reinterpret_cast<const char*
> (::sqlite3_column_text (fGetOneStatement_, 0))))
267 rc = ::sqlite3_step (fGetOneStatement_);
269 if (rc != SQLITE_DONE) [[unlikely]] {
270 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
275 dr.Add (Document::kID,
id);
277 dr = projection->Apply (dr);
285#if USE_NOISY_TRACE_IN_THIS_MODULE_
293 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
297 vDoc.
Add (Document::kID, String::FromUTF8 (argv[0]));
300 if (not filter->Matches (vDoc)) {
305 vDoc = projection->
Apply (vDoc);
311 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fConnectionRep_->fDB_,
"SELECT id,json FROM {};"_f(fTableName_).AsUTF8<string> ().c_str (),
312 callback.GetStaticFunction (), callback.GetData (),
nullptr),
313 fConnectionRep_->fDB_);
318#if USE_NOISY_TRACE_IN_THIS_MODULE_
323 if (onlyTheseFields) {
328 Document::Document d2Update = onlyTheseFields ? Memory::ValueOfOrThrow (this->GetOne (
id, nullopt)) : uploadDoc;
330 if (onlyTheseFields) {
331 d2Update.
AddAll (uploadDoc);
338 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fConnectionRep_->fDB_,
339 "update {} SET json='{}' where id='{}';"_f(fTableName_, r,
id).AsUTF8<string> ().c_str (),
340 nullptr,
nullptr,
nullptr),
341 fConnectionRep_->fDB_);
343 virtual void Remove (
const IDType&
id)
override
345#if USE_NOISY_TRACE_IN_THIS_MODULE_
350 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fConnectionRep_->fDB_,
351 "delete from {} where id='{}';"_f(fTableName_,
id).AsUTF8<string> ().c_str (),
352 nullptr,
nullptr,
nullptr),
353 fConnectionRep_->fDB_);
357 ConnectionRep_ (
const Options& options)
363 switch (options.fThreadingMode.value_or (Options::kDefault_ThreadingMode)) {
364 case Options::ThreadingMode::eSingleThread:
366 case Options::ThreadingMode::eMultiThread:
367 Require (CompiledOptions::kThe.THREADSAFE);
368 Require (::sqlite3_threadsafe ());
369 flags |= SQLITE_OPEN_NOMUTEX;
371 case Options::ThreadingMode::eSerialized:
372 Require (CompiledOptions::kThe.THREADSAFE);
373 Require (::sqlite3_threadsafe ());
374 flags |= SQLITE_OPEN_FULLMUTEX;
378 if (options.fImmutable) {
381 Require (options.fReadOnly);
383 flags |= options.fReadOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
387 [[maybe_unused]]
int n{};
388 if (options.fDBPath) {
391 if (options.fTemporaryDB) {
394 if (options.fInMemoryDB) {
399 if (options.fDBPath) {
400 uriArg = options.fDBPath->generic_string ();
401 if (uriArg[0] ==
':') {
402 uriArg =
"./" + uriArg;
405 if (options.fTemporaryDB) {
409 Require (not options.fTemporaryDB->empty ());
411 if (options.fInMemoryDB) {
414 flags |= SQLITE_OPEN_MEMORY;
415 flags |= SQLITE_OPEN_URI;
416 flags |= SQLITE_OPEN_SHAREDCACHE;
417 Require (not options.fReadOnly);
418 Require (options.fCreateDBPathIfDoesNotExist);
419 uriArg = options.fInMemoryDB->AsNarrowSDKString ();
420 if (uriArg.empty ()) {
424 u8string safeCharURI = IO::Network::UniformResourceIdentification::PCTEncode (u8string{uriArg.begin (), uriArg.end ()}, {});
425 uriArg =
"file:" +
string{safeCharURI.begin (), safeCharURI.end ()} +
"?mode=memory&cache=shared";
431 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, flags, options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_CANTOPEN) {
432 if (options.fCreateDBPathIfDoesNotExist) {
433 if (fDB_ !=
nullptr) {
434 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
437 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, SQLITE_OPEN_CREATE | flags,
438 options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_OK) {
443 if (e != SQLITE_OK) [[unlikely]] {
444 [[maybe_unused]]
auto&& cleanup =
Finally ([
this] ()
noexcept {
445 if (fDB_ !=
nullptr) {
446 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
449 ThrowSQLiteError_ (e, fDB_);
451 if (options.fBusyTimeout) {
452 SetBusyTimeout (*options.fBusyTimeout);
454 if (options.fJournalMode) {
455 SetJournalMode (*options.fJournalMode);
465 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
467 virtual shared_ptr<const EngineProperties> GetEngineProperties ()
const override
470 virtual String GetEngineName ()
const override
475 static const shared_ptr<const EngineProperties> kProps_ = make_shared<const MyEngineProperties_> ();
482 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
484 results.
Add (String::FromUTF8 (argv[0]));
488 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"SELECT name FROM sqlite_master WHERE type='table';",
489 callback.GetStaticFunction (), callback.GetData (),
nullptr),
493 virtual void CreateCollection (
const String& name)
override
495 Exec (
"create table if not exists {} (id INTEGER PRIMARY KEY, json NOT NULL);"_f(name));
497 virtual void DropCollection (
const String& name)
override
499 Exec (
"drop table {};"_f(name));
505 make_shared<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
509 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
510 return Database::Document::SQLite::Transaction{conn};
512 virtual void Exec (
const String& sql)
override
515 int e = ::sqlite3_exec (fDB_, sql.
AsUTF8<
string> ().c_str (),
nullptr,
nullptr,
nullptr);
516 if (e != SQLITE_OK) [[unlikely]] {
517 ThrowSQLiteErrorIfNotOK_ (e, fDB_);
520 virtual ::sqlite3* Peek ()
override
525 virtual Duration GetBusyTimeout ()
const override
529 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
531 Assert (::strcmp (azColName[0],
"timeout") == 0);
532 int val = ::atoi (argv[0]);
537 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma busy_timeout;", callback.GetStaticFunction (), callback.GetData (),
nullptr), fDB_);
539 return Duration{double (*d) / 1000.0};
541 virtual void SetBusyTimeout (
const Duration& timeout)
override
544 ThrowSQLiteErrorIfNotOK_ (::sqlite3_busy_timeout (fDB_, (
int)(timeout.
As<
float> () * 1000)), fDB_);
546 virtual JournalModeType GetJournalMode ()
const override
549 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
551 Assert (::strcmp (azColName[0],
"journal_mode") == 0);
555 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode;", callback.GetStaticFunction (), callback.GetData (),
nullptr), fDB_);
557 if (d ==
"delete"sv) {
558 return JournalModeType::eDelete;
560 if (d ==
"truncate"sv) {
561 return JournalModeType::eTruncate;
563 if (d ==
"persist"sv) {
564 return JournalModeType::ePersist;
566 if (d ==
"memory"sv) {
567 return JournalModeType::eMemory;
570 return JournalModeType::eWAL;
573 return JournalModeType::eWAL2;
576 return JournalModeType::eOff;
579 return JournalModeType::eDelete;
581 virtual void SetJournalMode (JournalModeType journalMode)
override
584 switch (journalMode) {
585 case JournalModeType::eDelete:
586 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'delete';",
nullptr, 0,
nullptr), fDB_);
588 case JournalModeType::eTruncate:
589 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'truncate';",
nullptr, 0,
nullptr), fDB_);
591 case JournalModeType::ePersist:
592 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'persist';",
nullptr, 0,
nullptr), fDB_);
594 case JournalModeType::eMemory:
595 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'memory';",
nullptr, 0,
nullptr), fDB_);
597 case JournalModeType::eWAL:
598 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'wal';",
nullptr, 0,
nullptr), fDB_);
600 case JournalModeType::eWAL2:
601 if (GetJournalMode () == JournalModeType::eWAL) {
602 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'delete';",
nullptr, 0,
nullptr), fDB_);
604 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'wal2';",
nullptr, 0,
nullptr), fDB_);
606 case JournalModeType::eOff:
607 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'off';",
nullptr, 0,
nullptr), fDB_);
621Document::SQLite::Connection::Ptr::Ptr (
const shared_ptr<IRep>& src)
623 , busyTimeout{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
624 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
626 return thisObj->operator->()->GetBusyTimeout ();
628 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
auto* property,
auto timeout) {
629 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
631 thisObj->operator->()->SetBusyTimeout (timeout);
633 , journalMode{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
const auto* property) {
634 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
636 return thisObj->operator->()->GetJournalMode ();
638 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
auto* property,
auto journalMode) {
639 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
641 thisObj->operator->()->SetJournalMode (journalMode);
644#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
645 if (src !=
nullptr) {
656auto Document::SQLite::Connection::New (
const Options& options) -> Ptr
658 return Ptr{make_shared<ConnectionRep_> (options)};
667 MyRep_ (
const Connection::Ptr& db, Flag f)
668 : fConnectionPtr_{db}
671 case Flag::eDeferred:
672 db->Exec (
"BEGIN DEFERRED TRANSACTION;"sv);
674 case Flag::eExclusive:
675 db->Exec (
"BEGIN EXCLUSIVE TRANSACTION;"sv);
677 case Flag::eImmediate:
678 db->Exec (
"BEGIN IMMEDIATE TRANSACTION;"sv);
684 virtual void Commit ()
override
686 Require (not fCompleted_);
688 fConnectionPtr_->Exec (
"COMMIT TRANSACTION;"sv);
692 Require (not fCompleted_);
694 fConnectionPtr_->Exec (
"ROLLBACK TRANSACTION;"sv);
699 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
701 Connection::Ptr fConnectionPtr_;
702 bool fCompleted_{
false};
704Transaction::Transaction (
const Connection::Ptr& db, Flag f)
705 : 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()
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 unsigned int AddAll(ITERABLE_OF_ADDABLE &&items, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
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 Append(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
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...
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.
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 >