Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
Document/SQLite.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <system_error>
7
14
15#include "SQLite.h"
16
17using namespace Stroika::Foundation;
18
19using namespace Characters;
20using namespace Containers;
21using namespace Debug;
22using namespace DataExchange;
23using namespace Database;
24using namespace Database::Document::SQLite;
25using namespace Execution;
26using namespace Time;
27
32
33// Comment this in to turn on aggressive noisy DbgTrace in this module
34//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
35
37
38#if qStroika_HasComponent_sqlite
39namespace {
40 struct ModuleShutdown_ {
41 ~ModuleShutdown_ ()
42 {
43 Verify (::sqlite3_shutdown () == SQLITE_OK); // mostly pointless but avoids memory leak complaints
44 }
45 } sModuleShutdown_;
46 [[noreturn]] void ThrowSQLiteError_ (int errCode, sqlite3* sqliteConnection)
47 {
48 Require (errCode != SQLITE_OK);
49 optional<String> errMsgDetails;
50 if (sqliteConnection != nullptr) {
51 errMsgDetails = String::FromUTF8 (::sqlite3_errmsg (sqliteConnection));
52 }
53 switch (errCode) {
54 case SQLITE_BUSY: {
55 DbgTrace ("SQLITE_BUSY"_f); // The database file is locked
56 Throw (system_error{make_error_code (errc::device_or_resource_busy)});
57 } break;
58 case SQLITE_LOCKED: {
59 DbgTrace ("SQLITE_LOCKED"_f); // A table in the database is locked
60 Throw (system_error{make_error_code (errc::device_or_resource_busy)});
61 } break;
62 case SQLITE_CONSTRAINT: {
63 if (errMsgDetails) {
64 Throw (Exception{"SQLITE_CONSTRAINT: {}"_f(errMsgDetails)});
65 }
66 else {
67 static const auto kEx_ = Exception{"SQLITE_CONSTRAINT"sv};
68 Throw (kEx_);
69 }
70 } break;
71 case SQLITE_TOOBIG: {
72 static const auto kEx_ = Exception{"SQLITE_TOOBIG"sv};
73 Throw (kEx_);
74 } break;
75 case SQLITE_FULL: {
76 DbgTrace ("SQLITE_FULL"_f);
77 Throw (system_error{make_error_code (errc::no_space_on_device)});
78 } break;
79 case SQLITE_READONLY: {
80 static const auto kEx_ = Exception{"SQLITE_READONLY"sv};
81 Throw (kEx_);
82 } break;
83 case SQLITE_MISUSE: {
84 if (errMsgDetails) {
85 Throw (Exception{"SQLITE_MISUSE: {}"_f(errMsgDetails)});
86 }
87 else {
88 static const auto kEx_ = Exception{"SQLITE_MISUSE"sv};
89 Throw (kEx_);
90 }
91 } break;
92 case SQLITE_ERROR: {
93 if (errMsgDetails) {
94 Throw (Exception{"SQLITE_ERROR: {}"_f(errMsgDetails)});
95 }
96 else {
97 static const auto kEx_ = Exception{"SQLITE_ERROR"sv};
98 Throw (kEx_);
99 }
100 } break;
101 case SQLITE_NOMEM: {
102 DbgTrace ("SQLITE_NOMEM translated to bad_alloc"_f);
103 Throw (bad_alloc{});
104 } break;
105 }
106 if (errMsgDetails) {
107 Throw (Exception{"SQLite Error: {} (code {})"_f(errMsgDetails, errCode)});
108 }
109 else {
110 Throw (Exception{"SQLite Error: {}"_f(errCode)});
111 }
112 }
113 void ThrowSQLiteErrorIfNotOK_ (int errCode, sqlite3* sqliteConnection)
114 {
115 static_assert (SQLITE_OK == 0);
116 if (errCode != SQLITE_OK) [[unlikely]] {
117 ThrowSQLiteError_ (errCode, sqliteConnection);
118 }
119 }
120
121 /*
122 * Simple utility to be able to use lambdas with arbitrary captures more easily with sqlite c API
123 */
124 template <invocable<int, char**, char**> CB>
125 struct SQLiteCallback_ {
126 CB fCallback_;
127
128 using STATIC_FUNCTION_TYPE = int (*) (void*, int, char**, char**);
129
130 SQLiteCallback_ (CB&& cb)
131 : fCallback_{forward<CB> (cb)}
132 {
133 }
134 STATIC_FUNCTION_TYPE GetStaticFunction ()
135 {
136 return STATICFUNC_;
137 }
138 void* GetData ()
139 {
140 return this;
141 }
142
143 private:
144 static int STATICFUNC_ (void* SQLiteCallbackData, int argc, char** argv, char** azColName)
145 {
146 SQLiteCallback_* sqc = reinterpret_cast<SQLiteCallback_*> (SQLiteCallbackData);
147 return sqc->fCallback_ (argc, argv, azColName);
148 }
149 };
150
151 sqlite3_stmt* mkPreparedStatement_ (sqlite3* db, const String& statement)
152 {
153 RequireNotNull (db);
154 const char* pzTail = nullptr;
155 sqlite3_stmt* result{nullptr};
156 string utfStatement = statement.AsUTF8<string> (); // subtle - need explicit named temporary (in debug builds) so we can check assertion after - which points inside utfStatement
157 ThrowSQLiteErrorIfNotOK_ (::sqlite3_prepare_v2 (db, utfStatement.c_str (), -1, &result, &pzTail), db);
158 Assert (pzTail != nullptr);
159 Require (*pzTail == '\0'); // else argument string had cruft at the end or was a compound statement, not allowed by sqlite and this api/mechanism
160 EnsureNotNull (result);
161 return result;
162 }
163}
164
165/*
166 ********************************************************************************
167 *************************** SQLite::CompiledOptions ****************************
168 ********************************************************************************
169 */
170namespace {
171 struct VerifyFlags_ {
172 VerifyFlags_ ()
173 {
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"));
178#else
179 Assert (!::sqlite3_compileoption_used ("OMIT_JSON1"));
180#endif
181 }
182 } sVerifyFlags_;
183}
184
185namespace {
186 using Connection::Options;
187 struct ConnectionRep_ final : Database::Document::SQLite::Connection::IRep {
188
189 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex_;
190
192 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex_; // since shares unsyncrhonized connection, share its context
193 shared_ptr<ConnectionRep_> fConnectionRep_; // save to bump reference count
194 String fTableName_;
195
196 ::sqlite3_stmt* fAddStatement_{nullptr};
197 ::sqlite3_stmt* fGetOneStatement_{nullptr};
198
199 CollectionRep_ (const shared_ptr<ConnectionRep_>& connectionRep, const String& collectionName)
200 : fConnectionRep_{connectionRep}
201 , fTableName_{collectionName}
202 {
203#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
204 fAssertExternallySynchronizedMutex_.SetAssertExternallySynchronizedMutexContext (
205 connectionRep->fAssertExternallySynchronizedMutex_.GetSharedContext ());
206#endif
207 }
208 virtual ~CollectionRep_ ()
209 {
210 if (fAddStatement_ != nullptr) {
211 (void)::sqlite3_finalize (fAddStatement_);
212 }
213 if (fGetOneStatement_ != nullptr) {
214 (void)::sqlite3_finalize (fGetOneStatement_);
215 }
216 }
217 virtual IDType Add (const Document::Document& v) override
218 {
219#if USE_NOISY_TRACE_IN_THIS_MODULE_
220 TraceContextBumper ctx{"SQLite::CollectionRep_::Add()"};
221#endif
222 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
223 /**
224 * UNCLEAR if this way of capturing row_id is threadsafe or not.
225 * MAYBE OK if not using 'fullmutex' mode on database connection? @todo FIGURE OUT!!!!
226 *
227 * @todo: SIMONE suggests using GUID, and pre-computing the ID, and using that.
228 * COULD just precomute the id (easier if sqlite had sequence type) - or do two inserts - lots of tricky ways.
229 * none that efficient and clean and simple. I guess this is clean and simple and efficient, just probably a race
230 */
231 if (fAddStatement_ == nullptr) [[unlikely]] {
232 fAddStatement_ = mkPreparedStatement_ (fConnectionRep_->fDB_, "insert into {} (json) values(?);"_f(fTableName_));
233 }
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_);
241 }
242 return "{}"_f(sqlite3_last_insert_rowid (fConnectionRep_->fDB_));
243 }
244 virtual optional<Document::Document> GetOne (const IDType& id, const optional<Projection>& projection) override
245 {
246#if USE_NOISY_TRACE_IN_THIS_MODULE_
247 TraceContextBumper ctx{"SQLite::CollectionRep_::GetOne()"};
248#endif
249 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
250
251 // @todo figure out how to use json apis to support projection more efficiently
252 if (fGetOneStatement_ == nullptr) [[unlikely]] {
253 fGetOneStatement_ = mkPreparedStatement_ (fConnectionRep_->fDB_, "select json from {} where id=?;"_f(fTableName_));
254 }
255
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_);
268 }
269 if (rc != SQLITE_DONE) [[unlikely]] {
270 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
271 }
272
273 if (result) {
274 auto dr = *result;
275 dr.Add (Document::kID, id);
276 if (projection) {
277 dr = projection->Apply (dr);
278 }
279 result = dr;
280 }
281 return result;
282 }
283 virtual Sequence<Document::Document> GetAll (const optional<Filter>& filter, const optional<Projection>& projection) override
284 {
285#if USE_NOISY_TRACE_IN_THIS_MODULE_
286 TraceContextBumper ctx{"SQLite::CollectionRep_::GetAll()"};
287#endif
288 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
289 /* auto [mongoFilter, myFilter] = Partition_ (filter);
290 auto [mongoProjection, myProjection] = Partition_ (projection);*/
292
293 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
294 Assert (argc == 2);
295 VariantValue vv = Variant::JSON::Reader{}.Read (String{argv[1]});
297 vDoc.Add (Document::kID, String::FromUTF8 (argv[0]));
298 if (filter) {
299 // super sloppy slow inefficient impl!!!
300 if (not filter->Matches (vDoc)) {
301 return SQLITE_OK; // tell sqlite got it, but we drop it on the floor anyhow
302 }
303 }
304 if (projection) {
305 vDoc = projection->Apply (vDoc);
306 }
307 result.Append (vDoc);
308 return SQLITE_OK;
309 }};
310 // @todo PREPARED STATEMENT!
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_);
314 return result;
315 }
316 virtual void Update (const IDType& id, const Document::Document& newV, const optional<Set<String>>& onlyTheseFields) override
317 {
318#if USE_NOISY_TRACE_IN_THIS_MODULE_
319 TraceContextBumper ctx{"SQLite::CollectionRep_::Update()"};
320#endif
321 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
322 Document::Document uploadDoc = newV;
323 if (onlyTheseFields) {
324 uploadDoc.RetainAll (*onlyTheseFields);
325 }
326 // POOR IMPLEMENTATION - should use sql update - but tricky for this case, so KISS, and get functionally working so
327 // I can integrate this code in regtests
328 Document::Document d2Update = onlyTheseFields ? Memory::ValueOfOrThrow (this->GetOne (id, nullopt)) : uploadDoc;
329 // any fields listed in onlyTheseFields, but not present in newV need to be removed
330 if (onlyTheseFields) {
331 d2Update.AddAll (uploadDoc);
332 Set<String> removeMe = *onlyTheseFields - newV.Keys ();
333 d2Update.RemoveAll (removeMe);
334 }
335
336 // @todo PREPARED STATEMENT!
337 String r = Variant::JSON::Writer{}.WriteAsString (VariantValue{d2Update});
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_);
342 }
343 virtual void Remove (const IDType& id) override
344 {
345#if USE_NOISY_TRACE_IN_THIS_MODULE_
346 TraceContextBumper ctx{"SQLite::CollectionRep_::Remove()"};
347#endif
348 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
349 // @todo PREPARED STATEMENT!
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_);
354 }
355 };
356
357 ConnectionRep_ (const Options& options)
358 {
359 TraceContextBumper ctx{"SQLite::Connection::ConnectionRep_::ConnectionRep_"};
360
361 int flags = 0;
362 // https://www.sqlite.org/threadsafe.html explains the thread-safety stuff. Not sure I have it right, but hopefully --LGP 2023-09-13
363 switch (options.fThreadingMode.value_or (Options::kDefault_ThreadingMode)) {
364 case Options::ThreadingMode::eSingleThread:
365 break;
366 case Options::ThreadingMode::eMultiThread:
367 Require (CompiledOptions::kThe.THREADSAFE);
368 Require (::sqlite3_threadsafe ());
369 flags |= SQLITE_OPEN_NOMUTEX;
370 break;
371 case Options::ThreadingMode::eSerialized:
372 Require (CompiledOptions::kThe.THREADSAFE);
373 Require (::sqlite3_threadsafe ());
374 flags |= SQLITE_OPEN_FULLMUTEX;
375 break;
376 }
377
378 if (options.fImmutable) {
379 // NYI cuz requires uri syntax
381 Require (options.fReadOnly);
382 }
383 flags |= options.fReadOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
384
385 string uriArg;
387 [[maybe_unused]] int n{};
388 if (options.fDBPath) {
389 ++n;
390 }
391 if (options.fTemporaryDB) {
392 ++n;
393 }
394 if (options.fInMemoryDB) {
395 ++n;
396 }
397 Require (n == 1); // exactly one of fDBPath, fTemporaryDB, fInMemoryDB must be provided
398 }
399 if (options.fDBPath) {
400 uriArg = options.fDBPath->generic_string ();
401 if (uriArg[0] == ':') {
402 uriArg = "./" + uriArg; // sqlite docs warn to do this, to avoid issues with :memory or other extensions
403 }
404 }
405 if (options.fTemporaryDB) {
406 uriArg = string{};
407 // According to https://sqlite.org/inmemorydb.html, temporary DBs appear to require empty name
408 // @todo MAYBE fix to find a way to do named temporary DB? - or adjust API so no string name provided.
409 Require (not options.fTemporaryDB->empty ());
410 }
411 if (options.fInMemoryDB) {
412 // Not super clear why SQLITE_OPEN_URI needed, but the example in docs uses URI, and tracing through the sqlite open code
413 // it appears to require a URI format, but not really documented as near as I can tell...--LGP 2025-03-31
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 (); // often empty string
420 if (uriArg.empty ()) {
421 uriArg = ":memory";
422 }
423 else {
424 u8string safeCharURI = IO::Network::UniformResourceIdentification::PCTEncode (u8string{uriArg.begin (), uriArg.end ()}, {});
425 uriArg = "file:" + string{safeCharURI.begin (), safeCharURI.end ()} + "?mode=memory&cache=shared";
426 }
427 // For now, it appears we ALWAYS create memory DBS when opening (so cannot find a way to open shared) - so always set created flag
428 }
429
430 int e;
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);
435 fDB_ = nullptr;
436 }
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) {
439 ; // if?
440 }
441 }
442 }
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);
447 }
448 });
449 ThrowSQLiteError_ (e, fDB_);
450 }
451 if (options.fBusyTimeout) {
452 SetBusyTimeout (*options.fBusyTimeout);
453 }
454 if (options.fJournalMode) {
455 SetJournalMode (*options.fJournalMode);
456 }
457
458 // Exec ("SELECT load_extension ('/path/to/json1/extension')");
459
460 EnsureNotNull (fDB_);
461 }
462 ~ConnectionRep_ ()
463 {
464 AssertNotNull (fDB_);
465 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
466 }
467 virtual shared_ptr<const EngineProperties> GetEngineProperties () const override
468 {
469 struct MyEngineProperties_ final : EngineProperties {
470 virtual String GetEngineName () const override
471 {
472 return "SQLite"sv;
473 }
474 };
475 static const shared_ptr<const EngineProperties> kProps_ = make_shared<const MyEngineProperties_> ();
476 return kProps_;
477 }
478 virtual Set<String> GetCollections () override
479 {
480 // treat named all tables as collections (maybe just count those with two columns id/json?).
481 Set<String> results;
482 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
483 Assert (argc == 1);
484 results.Add (String::FromUTF8 (argv[0]));
485 return SQLITE_OK;
486 }};
487 //ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, ".tables", callback, &results, nullptr)); not sure why this doesn't work
488 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "SELECT name FROM sqlite_master WHERE type='table';",
489 callback.GetStaticFunction (), callback.GetData (), nullptr),
490 fDB_);
491 return results;
492 }
493 virtual void CreateCollection (const String& name) override
494 {
495 Exec ("create table if not exists {} (id INTEGER PRIMARY KEY, json NOT NULL);"_f(name));
496 }
497 virtual void DropCollection (const String& name) override
498 {
499 Exec ("drop table {};"_f(name));
500 }
501 virtual Document::Collection::Ptr GetCollection (const String& name) override
502 {
503 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
505 make_shared<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
506 }
507 virtual Document::Transaction mkTransaction () override
508 {
509 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
510 return Database::Document::SQLite::Transaction{conn};
511 }
512 virtual void Exec (const String& sql) override
513 {
514 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
515 int e = ::sqlite3_exec (fDB_, sql.AsUTF8<string> ().c_str (), nullptr, nullptr, nullptr);
516 if (e != SQLITE_OK) [[unlikely]] {
517 ThrowSQLiteErrorIfNotOK_ (e, fDB_);
518 }
519 }
520 virtual ::sqlite3* Peek () override
521 {
522 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_}; // not super helpful, but could catch errors - reason not very helpful is we lose lock long before we stop using ptr
523 return fDB_;
524 }
525 virtual Duration GetBusyTimeout () const override
526 {
527 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{fAssertExternallySynchronizedMutex_};
528 optional<int> d;
529 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
530 Assert (argc == 1);
531 Assert (::strcmp (azColName[0], "timeout") == 0);
532 int val = ::atoi (argv[0]);
533 Assert (val >= 0);
534 d = val;
535 return SQLITE_OK;
536 }};
537 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma busy_timeout;", callback.GetStaticFunction (), callback.GetData (), nullptr), fDB_);
538 Assert (d);
539 return Duration{double (*d) / 1000.0};
540 }
541 virtual void SetBusyTimeout (const Duration& timeout) override
542 {
543 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
544 ThrowSQLiteErrorIfNotOK_ (::sqlite3_busy_timeout (fDB_, (int)(timeout.As<float> () * 1000)), fDB_);
545 }
546 virtual JournalModeType GetJournalMode () const override
547 {
548 optional<string> d;
549 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
550 Assert (argc == 1);
551 Assert (::strcmp (azColName[0], "journal_mode") == 0);
552 d = argv[0];
553 return SQLITE_OK;
554 }};
555 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode;", callback.GetStaticFunction (), callback.GetData (), nullptr), fDB_);
556 Assert (d);
557 if (d == "delete"sv) {
558 return JournalModeType::eDelete;
559 }
560 if (d == "truncate"sv) {
561 return JournalModeType::eTruncate;
562 }
563 if (d == "persist"sv) {
564 return JournalModeType::ePersist;
565 }
566 if (d == "memory"sv) {
567 return JournalModeType::eMemory;
568 }
569 if (d == "wal"sv) {
570 return JournalModeType::eWAL;
571 }
572 if (d == "wal2"sv) {
573 return JournalModeType::eWAL2;
574 }
575 if (d == "off"sv) {
576 return JournalModeType::eOff;
577 }
579 return JournalModeType::eDelete;
580 }
581 virtual void SetJournalMode (JournalModeType journalMode) override
582 {
583 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
584 switch (journalMode) {
585 case JournalModeType::eDelete:
586 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'delete';", nullptr, 0, nullptr), fDB_);
587 break;
588 case JournalModeType::eTruncate:
589 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'truncate';", nullptr, 0, nullptr), fDB_);
590 break;
591 case JournalModeType::ePersist:
592 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'persist';", nullptr, 0, nullptr), fDB_);
593 break;
594 case JournalModeType::eMemory:
595 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'memory';", nullptr, 0, nullptr), fDB_);
596 break;
597 case JournalModeType::eWAL:
598 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'wal';", nullptr, 0, nullptr), fDB_);
599 break;
600 case JournalModeType::eWAL2:
601 if (GetJournalMode () == JournalModeType::eWAL) {
602 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'delete';", nullptr, 0, nullptr), fDB_);
603 }
604 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'wal2';", nullptr, 0, nullptr), fDB_);
605 break;
606 case JournalModeType::eOff:
607 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'off';", nullptr, 0, nullptr), fDB_);
608 break;
609 }
610 }
611
612 ::sqlite3* fDB_{};
613 };
614}
615
616/*
617 ********************************************************************************
618 *********************** SQL::SQLite::Connection::Ptr ***************************
619 ********************************************************************************
620 */
621Document::SQLite::Connection::Ptr::Ptr (const shared_ptr<IRep>& src)
622 : inherited{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);
625 RequireNotNull (thisObj->operator->());
626 return thisObj->operator->()->GetBusyTimeout ();
627 },
628 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, auto timeout) {
629 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
630 RequireNotNull (thisObj->operator->());
631 thisObj->operator->()->SetBusyTimeout (timeout);
632 }}
633 , journalMode{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
634 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
635 RequireNotNull (thisObj->operator->());
636 return thisObj->operator->()->GetJournalMode ();
637 },
638 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, auto journalMode) {
639 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
640 RequireNotNull (thisObj->operator->());
641 thisObj->operator->()->SetJournalMode (journalMode);
642 }}
643{
644#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
645 if (src != nullptr) {
646 // _fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (src->_fAssertExternallySynchronizedMutex.GetSharedContext ());
647 }
648#endif
649}
650
651/*
652 ********************************************************************************
653 ************************** SQL::SQLite::Connection *****************************
654 ********************************************************************************
655 */
656auto Document::SQLite::Connection::New (const Options& options) -> Ptr
657{
658 return Ptr{make_shared<ConnectionRep_> (options)};
659}
660
661/*
662 ********************************************************************************
663 ******************************* SQLite::Transaction ****************************
664 ********************************************************************************
665 */
666struct Transaction::MyRep_ : public Database::Document::Transaction::IRep {
667 MyRep_ (const Connection::Ptr& db, Flag f)
668 : fConnectionPtr_{db}
669 {
670 switch (f) {
671 case Flag::eDeferred:
672 db->Exec ("BEGIN DEFERRED TRANSACTION;"sv);
673 break;
674 case Flag::eExclusive:
675 db->Exec ("BEGIN EXCLUSIVE TRANSACTION;"sv);
676 break;
677 case Flag::eImmediate:
678 db->Exec ("BEGIN IMMEDIATE TRANSACTION;"sv);
679 break;
680 default:
682 }
683 }
684 virtual void Commit () override
685 {
686 Require (not fCompleted_);
687 fCompleted_ = true;
688 fConnectionPtr_->Exec ("COMMIT TRANSACTION;"sv);
689 }
690 virtual void Rollback () override
691 {
692 Require (not fCompleted_);
693 fCompleted_ = true;
694 fConnectionPtr_->Exec ("ROLLBACK TRANSACTION;"sv);
695 }
696 virtual Disposition GetDisposition () const override
697 {
698 // @todo record more info so we can report finer grained status ; try/catch in rollback/commit and dbgtraces
699 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
700 }
701 Connection::Ptr fConnectionPtr_;
702 bool fCompleted_{false};
703};
704Transaction::Transaction (const Connection::Ptr& db, Flag f)
705 : inherited{make_unique<MyRep_> (db, f)}
706{
707}
708#endif
#define AssertNotNull(p)
Definition Assertions.h:333
#define EnsureNotNull(p)
Definition Assertions.h:340
#define RequireNotReached()
Definition Assertions.h:385
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:48
#define WeakAssertNotImplemented()
Definition Assertions.h:483
#define RequireNotNull(p)
Definition Assertions.h:347
#define AssertNotReached()
Definition Assertions.h:355
#define Verify(c)
Definition Assertions.h:419
#define DbgTrace
Definition Trace.h:309
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:188
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...
Definition Mapping.inl:240
nonvirtual Iterable< key_type > Keys() const
Definition Mapping.inl:111
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)
Definition Sequence.inl:330
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
Definition Set.inl:138
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
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.
Definition Projection.h:32
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> (=.
Definition Duration.h:96
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...
Definition Throw.inl:43
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31