Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Document/SQLite.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <system_error>
7
10#include "Stroika/Foundation/Common/GUID.h"
14#include "Stroika/Foundation/Execution/Exceptions.h"
17
18#include "SQLite.h"
19
20using namespace Stroika::Foundation;
21
22using namespace Characters;
23using namespace Containers;
24using namespace Common;
25using namespace Debug;
26using namespace DataExchange;
27using namespace Database;
28using namespace Database::Document::SQLite;
29using namespace Execution;
30using namespace Time;
31
36
37using Database::Document::kID;
38using Database::Document::kOnlyIDs;
39
40// Comment this in to turn on aggressive noisy DbgTrace in this module
41// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
42
44
45#if qStroika_HasComponent_sqlite
46namespace {
47 struct ModuleShutdown_ {
48 ~ModuleShutdown_ ()
49 {
50 Verify (::sqlite3_shutdown () == SQLITE_OK); // mostly pointless but avoids memory leak complaints
51 }
52 } sModuleShutdown_;
53 [[noreturn]] void ThrowSQLiteError_ (int errCode, sqlite3* sqliteConnection)
54 {
55 Require (errCode != SQLITE_OK);
56 optional<String> errMsgDetails;
57 if (sqliteConnection != nullptr) {
58 errMsgDetails = String::FromUTF8 (::sqlite3_errmsg (sqliteConnection));
59 }
60 switch (errCode) {
61 case SQLITE_BUSY: {
62 DbgTrace ("SQLITE_BUSY"_f); // The database file is locked
63 ThrowError (errc::device_or_resource_busy);
64 } break;
65 case SQLITE_LOCKED: {
66 DbgTrace ("SQLITE_LOCKED"_f); // A table in the database is locked
67 ThrowError (errc::device_or_resource_busy);
68 } break;
69 case SQLITE_CONSTRAINT: {
70 if (errMsgDetails) {
71 Throw (Exception{"SQLITE_CONSTRAINT: {}"_f(errMsgDetails)});
72 }
73 else {
74 static const auto kEx_ = Exception{"SQLITE_CONSTRAINT"sv};
75 Throw (kEx_);
76 }
77 } break;
78 case SQLITE_TOOBIG: {
79 static const auto kEx_ = Exception{"SQLITE_TOOBIG"sv};
80 Throw (kEx_);
81 } break;
82 case SQLITE_FULL: {
83 DbgTrace ("SQLITE_FULL"_f);
84 ThrowError (errc::no_space_on_device);
85 } break;
86 case SQLITE_READONLY: {
87 static const auto kEx_ = Exception{"SQLITE_READONLY"sv};
88 Throw (kEx_);
89 } break;
90 case SQLITE_MISUSE: {
91 if (errMsgDetails) {
92 Throw (Exception{"SQLITE_MISUSE: {}"_f(errMsgDetails)});
93 }
94 else {
95 static const auto kEx_ = Exception{"SQLITE_MISUSE"sv};
96 Throw (kEx_);
97 }
98 } break;
99 case SQLITE_ERROR: {
100 if (errMsgDetails) {
101 Throw (Exception{"SQLITE_ERROR: {}"_f(errMsgDetails)});
102 }
103 else {
104 static const auto kEx_ = Exception{"SQLITE_ERROR"sv};
105 Throw (kEx_);
106 }
107 } break;
108 case SQLITE_NOMEM: {
109 DbgTrace ("SQLITE_NOMEM translated to bad_alloc"_f);
110 Throw (bad_alloc{});
111 } break;
112 }
113 if (errMsgDetails) {
114 Throw (Exception{"SQLite Error: {} (code {})"_f(errMsgDetails, errCode)});
115 }
116 else {
117 Throw (Exception{"SQLite Error: {}"_f(errCode)});
118 }
119 }
120 void ThrowSQLiteErrorIfNotOK_ (int errCode, sqlite3* sqliteConnection)
121 {
122 static_assert (SQLITE_OK == 0);
123 if (errCode != SQLITE_OK) [[unlikely]] {
124 ThrowSQLiteError_ (errCode, sqliteConnection);
125 }
126 }
127
128 /*
129 * Simple utility to be able to use lambdas with arbitrary captures more easily with SQLite c API
130 */
131 template <invocable<int, char**, char**> CB>
132 struct SQLiteCallback_ {
133 CB fCallback_;
134
135 using STATIC_FUNCTION_TYPE = int (*) (void*, int, char**, char**);
136
137 SQLiteCallback_ (CB&& cb)
138 : fCallback_{forward<CB> (cb)}
139 {
140 }
141 STATIC_FUNCTION_TYPE GetStaticFunction ()
142 {
143 return STATICFUNC_;
144 }
145 void* GetData ()
146 {
147 return this;
148 }
149 /**
150 * Rethrow whatever the callback threw, once sqlite3's frames are gone - call this after sqlite3_exec, before
151 * making anything of the code it returned.
152 */
153 void ThrowIfCapturedException ()
154 {
155 if (fCapturedException_ != nullptr) [[unlikely]] {
156 rethrow_exception (fCapturedException_);
157 }
158 }
159
160 private:
161 // the callback's exception, as thrown - so rethrown whole: a throw of *pointer-to-base would slice it
162 exception_ptr fCapturedException_;
163
164 /*
165 * The callback can throw - it is handed raw bytes from the database to make Strings of - and the exception must
166 * not escape into sqlite3, whose frames lie between here and sqlite3_exec. sqlite3 is C, so its functions are
167 * nounwind, and with -flto the unwind tables for them go away: the unwinder then finds no handler at all and
168 * terminates. So catch it here, return non-zero (which is how sqlite3_exec is told to stop), and rethrow from
169 * ThrowIfCapturedException.
170 */
171 static int STATICFUNC_ (void* SQLiteCallbackData, int argc, char** argv, char** azColName) noexcept
172 {
173 SQLiteCallback_* sqc = reinterpret_cast<SQLiteCallback_*> (SQLiteCallbackData);
174 if (sqc->fCapturedException_ == nullptr) [[likely]] {
175 try {
176 return sqc->fCallback_ (argc, argv, azColName);
177 }
178 catch (...) {
179 sqc->fCapturedException_ = current_exception ();
180 }
181 }
182 return 1; // anything non-zero aborts the sqlite3_exec
183 }
184 };
185
186 // not reference counted; caller responsibility to destroy before db connection
187 struct MyPreparedStatement_ {
188 MyPreparedStatement_ () = default;
189 MyPreparedStatement_ (::sqlite3* db, const String& statement)
190 {
191 RequireNotNull (db);
192#if USE_NOISY_TRACE_IN_THIS_MODULE_
193 TraceContextBumper ctx{"MyPreparedStatement_", "sql={}"_f, statement};
194#endif
195 const char* pzTail = nullptr;
196 string utfStatement = statement.AsUTF8<string> (); // subtle - need explicit named temporary (in debug builds) so we can check assertion after - which points inside utfStatement
197 ThrowSQLiteErrorIfNotOK_ (::sqlite3_prepare_v2 (db, utfStatement.c_str (), -1, &fObj_, &pzTail), db);
198 Assert (pzTail != nullptr);
199 Require (*pzTail == '\0'); // else argument string had cruft at the end or was a compound statement, not allowed by SQLite and this api/mechanism
200 EnsureNotNull (fObj_);
201 }
202 MyPreparedStatement_ (const MyPreparedStatement_&) = delete;
203 MyPreparedStatement_ (MyPreparedStatement_&&) noexcept = default;
204 ~MyPreparedStatement_ ()
205 {
206 if (fObj_ != nullptr) {
207 (void)::sqlite3_finalize (fObj_); // ignore result - errors indicate error on last evaluation of prepared statement, not on deletion of it
208 }
209 }
210 MyPreparedStatement_& operator= (const MyPreparedStatement_&) = delete;
211 MyPreparedStatement_& operator= (MyPreparedStatement_&& rhs) noexcept
212 {
213 fObj_ = rhs.fObj_;
214 rhs.fObj_ = nullptr;
215 return *this;
216 };
217 operator ::sqlite3_stmt* () const
218 {
219 return fObj_;
220 }
221
222 private:
223 ::sqlite3_stmt* fObj_{nullptr};
224 };
225 static_assert (movable<MyPreparedStatement_>);
226 static_assert (not copyable<MyPreparedStatement_>);
227
228 String ExtractColumnText_ (::sqlite3_stmt* statement, unsigned int col)
229 {
230 RequireNotNull (statement);
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)) {
235 case SQLITE_NULL:
236 // DbgTrace ("extractcoltext col={} ct={}, returning null string for NULL result"_f, col, ::sqlite3_column_type (statement, col));
237 return String{};
238 default:
239 Throw (Execution::Exception<runtime_error>{"Expected text column but got column type {}"_f(colType)});
240 }
241 }
242 // DbgTrace ("extractcolt ext col={} ct={}, returning '{}"_f, col, ::sqlite3_column_type (statement, col), String::FromUTF8 (t));
243 return String::FromUTF8 (t);
244 }
245}
246
247/*
248 ********************************************************************************
249 *************************** SQLite::CompiledOptions ****************************
250 ********************************************************************************
251 */
252namespace {
253 struct VerifyFlags_ {
254 VerifyFlags_ ()
255 {
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"));
260#else
261 Assert (!::sqlite3_compileoption_used ("OMIT_JSON1"));
262#endif
263 }
264 } sVerifyFlags_;
265}
266
267namespace {
268 /**
269 * Break the given Stroika filter into parts that can be remoted to SQLite db, and parts that must be handled locally
270 */
271 tuple<optional<String>, optional<Filter>> Partition_ (const optional<Filter>& filter)
272 {
273 if (filter) {
274 /*
275 * For now just look for FIELD EQUALS VALUE expressions in the top level conjunction. These can be done
276 * server or client side transparently, and moving them server side is more efficient.
277 *
278 * Much more could be done, but this is a good cost/benefit start.
279 */
281 StringBuilder whereClause;
282 for (Document::FilterElements::Operation op : filter->GetConjunctionOperations ()) {
283 bool transferred = false;
284 if (const Document::FilterElements::Equals* eqOp = get_if<Document::FilterElements::Equals> (&op)) {
285 if (const Document::FilterElements::Value* rhsValue = get_if<Document::FilterElements::Value> (&eqOp->fRHS)) {
286 if (not whereClause.empty ()) {
287 whereClause << " AND "sv;
288 }
289 // move to server side
290 if (eqOp->fLHS == Database::Document::kID) {
291 whereClause << "{} = '{}'"_f(Database::Document::kID, rhsValue->As<String> ()); // not sure this is right way to compare?
292 }
293 else {
294 // others compared in json part
295 String vSQL = 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;
299 }
300 whereClause << "json_extract(json, '$.{}') = {}"_f(String{eqOp->fLHS}, vSQL); // not sure this is right way to compare?
301 }
302 transferred = true;
303 }
304 }
305 if (not transferred) {
306 clientSideOps += op; // keep for client side
307 }
308 }
309 if (not whereClause.empty ()) {
310 // if we moved any to server side, then return the filterDoc and the client side ops
311 return make_tuple (whereClause, clientSideOps.empty () ? optional<Filter>{} : make_optional (Filter{clientSideOps}));
312 }
313 // else no change
314 return make_tuple (nullopt, filter);
315 }
316 return make_tuple (nullopt, nullopt);
317 }
318}
319
320namespace {
321 /**
322 * Break the given Stroika filter into parts that can be handled in SQLite, and parts that must be handled locally
323 * Also return array showing names cuz SQLite returns array (for > 1) and these are the names in this order of the fields/objects:
324 */
325 tuple<optional<tuple<String, Sequence<String>>>, optional<Projection>> Partition_ (const optional<Projection>& p)
326 {
327 if (p) {
328 /*
329 * json_extract appears to only support 'include' and not 'omit' operations
330 */
331 tuple<Document::Projection::Flag, Set<String>> fields = p->GetFields ();
332 // Require (get<1> (fields).size () >= 1); // cannot (usefully) project to null-space (can cuz id handled elsewhere now)
333 if (get<Document::Projection::Flag> (fields) == Document::Projection::Flag::eInclude) {
334 StringBuilder projectionQuery;
335 Sequence<String> fieldNames;
336 for (String f : get<1> (fields)) {
337 String mongoFieldName = f;
338 Require (f != Document::kID); // prevented before calling here - handled in caller
339 if (fieldNames.empty ()) {
340 projectionQuery << "json_extract(json,'"sv;
341 }
342 else {
343 projectionQuery << ","sv;
344 }
345 projectionQuery << "$."sv << mongoFieldName;
346 fieldNames.push_back (mongoFieldName);
347 }
348 if (not fieldNames.empty ()) {
349 projectionQuery << "')"sv;
350 }
351 return make_tuple (make_tuple (projectionQuery, fieldNames), nullopt);
352 }
353 else {
354 return make_tuple (nullopt, p); // cannot optimize exclude (yet)
355 }
356 }
357 return make_tuple (nullopt, nullopt);
358 }
359 // called with the result of a statement after a 'step' operation that produced a ROW.
360 // And assumes the row contains data from the Partition_ algorithm above
361 Document::Document ExtractRowValueAfterStep_ (::sqlite3_stmt* statement, unsigned int dataCol, const IDType& id,
362 const optional<tuple<String, Sequence<String>>>& sqliteProjection,
363 const optional<Projection>& remainingProjection)
364 {
365 /*
366 * https://www.sqlite.org/json1.html
367 * "There is a subtle incompatibility between the json_extract() function in SQLite and the json_extract() function in MySQL. The MySQL version of json_extract() always returns JSON. The SQLite version of json_extract() only returns JSON if there are two or more PATH arguments"
368 */
369 static const auto kJSONReader_ = Variant::JSON::Reader{};
370 String colText = ExtractColumnText_ (statement, dataCol);
371 VariantValue valueReadBackFromDB = colText.empty () ? VariantValue{} : kJSONReader_.Read (colText); // treat NULL or "" as empty document, not syntax error
373 if (sqliteProjection == nullopt) {
374 dr = valueReadBackFromDB.As<Mapping<String, VariantValue>> ();
375 }
376 else {
377 auto arrayOfFieldNames = get<Sequence<String>> (*sqliteProjection);
378 if (arrayOfFieldNames.size () == 1) {
379 dr = Mapping<String, VariantValue>{{arrayOfFieldNames[0], valueReadBackFromDB}};
380 }
381 else {
382 Assert (valueReadBackFromDB.GetType () == VariantValue::eArray);
383 Assert (valueReadBackFromDB.As<Sequence<VariantValue>> ().size () == arrayOfFieldNames.size ());
384 Iterator<String> nameI = arrayOfFieldNames.begin ();
385 dr = valueReadBackFromDB.As<Sequence<VariantValue>> ().Map<Document::Document> (
386 [&nameI] (const VariantValue& vv) mutable { return KeyValuePair{*nameI++, vv}; });
387 Assert (nameI == arrayOfFieldNames.end ());
388 }
389 }
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);
393 }
394 if (remainingProjection) {
395 dr = remainingProjection->Apply (dr);
396 }
397 return dr;
398 };
399}
400
401namespace {
402 using Connection::Options;
403
404 template <InternallySynchronized SYNC_STYLE>
405 using MyMaybeLock_ =
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>>);
409
410 template <Execution::InternallySynchronized SYNC_STYLE>
411 struct ConnectionRep_ final : Database::Document::SQLite::Connection::IRep {
412
413 const Options fOptions_;
414 const bool fAllowUserDefinedRowID_{false};
415 Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE mutable MyMaybeLock_<SYNC_STYLE> fMaybeLock_; // mutable cuz this is what we lock to assure internal sync for const/non-const methods
416
418 shared_ptr<ConnectionRep_> fConnectionRep_; // save to bump reference count
419 const String fTableName_;
420
421 MyPreparedStatement_ fAddStatement_{};
422 MyPreparedStatement_ fGetOneStatement_{};
423 MyPreparedStatement_ fRemoveStatement_{};
424 MyPreparedStatement_ fUpdateStatement_{};
425
426 CollectionRep_ (const shared_ptr<ConnectionRep_>& connectionRep, const String& collectionName)
427 : fConnectionRep_{connectionRep}
428 , fTableName_{collectionName}
429 {
430 }
431 virtual ~CollectionRep_ () = default;
432 virtual String GetName () const override
433 {
434 return fTableName_;
435 }
436 virtual IDType Add (const Document::Document& v) override
437 {
438#if USE_NOISY_TRACE_IN_THIS_MODULE_
439 TraceContextBumper ctx{"SQLite::CollectionRep_::Add"};
440#endif
441 Require (not v.ContainsKey (Database::Document::kID) or fConnectionRep_->fOptions_.fAddAllowsExternallySpecifiedIDs);
442 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
443
444 return fConnectionRep_->WrapExecute_ (
445 [&] () {
446 Document::Document jsonValue2Write = v;
447
448 // Extract (or produce new) ID in fAllowUserDefinedRowID_ mode to use as top level ID
449 // but on NO case allow for ID in JSON
450 optional<IDType> id;
451 if (fConnectionRep_->fAllowUserDefinedRowID_) {
452 if (auto o = v.Lookup (Database::Document::kID)) {
453 id = o->As<String> ();
454 jsonValue2Write.RemoveIf (Database::Document::kID); // don't write ID to JSON
455 }
456 else {
457 id = Common::GUID::GenerateNew ().As<String> ();
458 }
459 }
460 Assert (not jsonValue2Write.ContainsKey (Database::Document::kID));
461
462 /**
463 * UNCLEAR if this way of capturing row_id is threadsafe or not.
464 * MAYBE OK if not using 'full mutex' mode on database connection? @todo FIGURE OUT!!!!
465 *
466 * @todo: SIMONE suggests using GUID, and pre-computing the ID, and using that.
467 * COULD just precompute the id (easier if SQLite had sequence type) - or do two inserts - lots of tricky ways.
468 * none that efficient and clean and simple. I guess this is clean and simple and efficient, just probably a race
469 */
470 if (fAddStatement_ == nullptr) [[unlikely]] {
471 if (fConnectionRep_->fAllowUserDefinedRowID_) {
472 fAddStatement_ =
473 MyPreparedStatement_{fConnectionRep_->fDB_, "insert into \"{}\" (id, json) values(?,?);"_f(fTableName_)};
474 }
475 else {
476 fAddStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_, "insert into \"{}\" (json) values(?);"_f(fTableName_)};
477 }
478 }
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_);
486 }
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_);
493 }
494 return fConnectionRep_->fAllowUserDefinedRowID_ ? *id : "{}"_f(::sqlite3_last_insert_rowid (fConnectionRep_->fDB_));
495 },
496 fTableName_, true);
497 }
498 virtual optional<Document::Document> Get (const IDType& id, const optional<Projection>& projection) override
499 {
500#if USE_NOISY_TRACE_IN_THIS_MODULE_
501 TraceContextBumper ctx{"SQLite::CollectionRep_::Get", "id={}, projection={}"_f, id, projection};
502#endif
503 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
504 return fConnectionRep_->WrapExecute_ (
505 [&] () {
506 // locally construct MyPreparedStatement_ for case with projection, and/or cache statement for grabbing whole thing
507 optional<Projection> projectionWithoutID = projection;
508 bool includeIDInProjection = true;
509 if (projectionWithoutID) {
510 auto [flags, fieldNames] = projectionWithoutID->GetFields ();
511 switch (flags) {
512 case Document::Projection::Flag::eInclude: {
513 if (fieldNames.Contains (Document::kID)) {
514 fieldNames.Remove (Document::kID);
515 projectionWithoutID = Projection{flags, fieldNames};
516 }
517 else {
518 includeIDInProjection = false;
519 }
520 } break;
521 case Document::Projection::Flag::eOmit: {
522 if (fieldNames.Contains (Document::kID)) {
523 includeIDInProjection = false;
524 projectionWithoutID = Projection{flags, fieldNames};
525 }
526 } break;
527 }
528 }
529 auto [sqliteProjection, remainingAfterProjection] = Partition_ (projectionWithoutID);
530 optional<MyPreparedStatement_> sqliteProjectionStatement;
531 bool ignoreResult = false;
532 if (sqliteProjection) {
533 // DbgTrace ("sqliteProjectionStatement:= select {} from {} where id=?;"_f, get<String> (*sqliteProjection), fTableName_);
534 if (get<String> (*sqliteProjection).empty ()) {
535 sqliteProjectionStatement.emplace (fConnectionRep_->fDB_, "select NULL from \"{}\" where id=?;"_f(fTableName_));
536 ignoreResult = true;
537 }
538 else {
539 sqliteProjectionStatement.emplace (
540 fConnectionRep_->fDB_, "select {} from \"{}\" where id=?;"_f(get<String> (*sqliteProjection), fTableName_));
541 }
542 }
543 else if (fGetOneStatement_ == nullptr) [[unlikely]] {
544 fGetOneStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_, "select json from \"{}\" where id=?;"_f(fTableName_)};
545 }
546 ::sqlite3_stmt* useStatment = sqliteProjectionStatement.has_value () ? *sqliteProjectionStatement : fGetOneStatement_;
547 AssertNotNull (useStatment);
548
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_);
554
555 int rc = ::sqlite3_step (useStatment);
556 optional<Document::Document> result;
557 if (rc == SQLITE_ROW) [[likely]] {
558 if (ignoreResult) {
559 result = Document::Document{};
560 }
561 else {
562 result = ExtractRowValueAfterStep_ (useStatment, 0u, id, sqliteProjection, remainingAfterProjection);
563 }
564 rc = ::sqlite3_step (useStatment);
565 }
566 if (rc != SQLITE_DONE) [[unlikely]] {
567 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
568 }
569 if (includeIDInProjection and result.has_value ()) {
570 result->Add (Document::kID, id);
571 }
572 return result;
573 },
574 fTableName_, false);
575 }
576 virtual Sequence<Document::Document> GetAll (const optional<Filter>& filter, const optional<Projection>& projection) override
577 {
578#if USE_NOISY_TRACE_IN_THIS_MODULE_
579 TraceContextBumper ctx{"SQLite::CollectionRep_::GetAll", "filter={}, projection={}"_f, filter, projection};
580#endif
581 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
582 return fConnectionRep_->WrapExecute_ (
583 [&] () {
584 // Optimize some important special cases
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_);
589 int rc;
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));
594 Document::Document vDoc = valueReadBackFromDB.As<Mapping<String, VariantValue>> ();
595 vDoc.Add (Document::kID, id);
596 result += vDoc;
597 }
598 if (rc != SQLITE_DONE) [[unlikely]] {
599 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
600 }
601 }
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_);
605 int rc;
606 while ((rc = ::sqlite3_step (statement)) == SQLITE_ROW) {
607 String id = ExtractColumnText_ (statement, 0u);
609 vDoc.Add (Document::kID, id);
610 result += vDoc;
611 }
612 if (rc != SQLITE_DONE) [[unlikely]] {
613 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
614 }
615 }
616 else {
617 // general case
618 auto [sqliteWhereClause, remainingFilter] = Partition_ (filter);
619
620 //DbgTrace ("sqliteWhereClause={}"_f, sqliteWhereClause);
621 //DbgTrace ("remainingFilter={}"_f, remainingFilter);
622 // if there is a remainingFilter (performed after SQLite) - we need to form full objects and then apply the filter at the end
623 // so the filter can access those fields. REALLY - we could do a little better than this in general, but this is a good first attempt
624 auto [sqliteProjection, remainingAfterProjection] =
625 remainingFilter ? make_tuple (nullopt, projection) : Partition_ (projection);
626
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))};
631
632 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (statement), fConnectionRep_->fDB_);
633 int rc;
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); // some attributes need to be projected after filter cuz maybe used in filtering
640 }
641 result += vDoc;
642 }
643 }
644 if (rc != SQLITE_DONE) [[unlikely]] {
645 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
646 }
647 }
648 return result;
649 },
650 fTableName_, false);
651 }
652 virtual void Update (const IDType& id, const Document::Document& newV, const optional<Set<String>>& onlyTheseFields) override
653 {
654#if USE_NOISY_TRACE_IN_THIS_MODULE_
655 TraceContextBumper ctx{"SQLite::CollectionRep_::Update"};
656#endif
657 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
658 fConnectionRep_->WrapExecute_ (
659 [&] () {
660 Document::Document uploadDoc = newV;
661 if (onlyTheseFields) {
662 uploadDoc.RetainAll (*onlyTheseFields);
663 }
664 // POOR IMPLEMENTATION - should use sql update - but tricky for this case, so KISS, and get functionally working so
665 // I can integrate this code in regtests
666 Document::Document d2Update = onlyTheseFields ? Memory::ValueOfOrThrow (Get (id, nullopt)) : uploadDoc;
667 // any fields listed in onlyTheseFields, but not present in newV need to be removed
668 if (onlyTheseFields) {
669 d2Update.AddAll (uploadDoc);
670 Set<String> removeMe = *onlyTheseFields - newV.Keys ();
671 d2Update.RemoveAll (removeMe);
672 }
673 d2Update.RemoveIf (Document::kID); // never write this to the JSON
674
675 if (fUpdateStatement_ == nullptr) [[unlikely]] {
676 fUpdateStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_, "update \"{}\" SET json=? where id=?;"_f(fTableName_)};
677 }
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_);
690 }
691 },
692 fTableName_, true);
693 }
694 virtual void Remove (const IDType& id) override
695 {
696#if USE_NOISY_TRACE_IN_THIS_MODULE_
697 TraceContextBumper ctx{"SQLite::CollectionRep_::Remove"};
698#endif
699 scoped_lock declareContext{fConnectionRep_->fMaybeLock_};
700 if (fRemoveStatement_ == nullptr) [[unlikely]] {
701 fRemoveStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_, "delete from \"{}\" where id=?;"_f(fTableName_)};
702 }
703 fConnectionRep_->WrapExecute_ (
704 [&] () {
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_);
713 }
714 },
715 fTableName_, true);
716 }
717 };
718
719 ConnectionRep_ (const Options& options)
720 : fOptions_{options}
721 , fAllowUserDefinedRowID_{options.fAddAllowsExternallySpecifiedIDs}
722 {
723 TraceContextBumper ctx{"SQLite::Connection::ConnectionRep_::ConnectionRep_"};
724
725 int flags = 0;
726 // https://www.sqlite.org/threadsafe.html explains the thread-safety stuff. Not sure I have it right, but hopefully --LGP 2023-09-13
727 switch (options.fThreadingMode.value_or (Options::kDefault_ThreadingMode)) {
728 case Options::ThreadingMode::eSingleThread:
729 break;
730 case Options::ThreadingMode::eMultiThread:
731 Require (CompiledOptions::kThe.THREADSAFE);
732 Require (::sqlite3_threadsafe ());
733 flags |= SQLITE_OPEN_NOMUTEX;
734 break;
735 case Options::ThreadingMode::eSerialized:
736 Require (CompiledOptions::kThe.THREADSAFE);
737 Require (::sqlite3_threadsafe ());
738 flags |= SQLITE_OPEN_FULLMUTEX;
739 break;
740 }
741
742 if (options.fImmutable) {
743 // NYI cuz requires uri syntax
745 Require (options.fReadOnly);
746 }
747 flags |= options.fReadOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
748
749 string uriArg;
751 [[maybe_unused]] int n{};
752 if (options.fDBPath) {
753 ++n;
754 }
755 if (options.fTemporaryDB) {
756 ++n;
757 }
758 if (options.fInMemoryDB) {
759 ++n;
760 }
761 Require (n == 1); // exactly one of fDBPath, fTemporaryDB, fInMemoryDB must be provided
762 }
763 if (options.fDBPath) {
764 uriArg = options.fDBPath->generic_string ();
765 if (uriArg[0] == ':') {
766 uriArg = "./" + uriArg; // SQLite docs warn to do this, to avoid issues with :memory or other extensions
767 }
768 }
769 if (options.fTemporaryDB) {
770 uriArg = string{};
771 // According to https://sqlite.org/inmemorydb.html, temporary DBs appear to require empty name
772 // @todo MAYBE fix to find a way to do named temporary DB? - or adjust API so no string name provided.
773 Require (not options.fTemporaryDB->empty ());
774 }
775 if (options.fInMemoryDB) {
776 // Not super clear why SQLITE_OPEN_URI needed, but the example in docs uses URI, and tracing through the SQLite open code
777 // it appears to require a URI format, but not really documented as near as I can tell...--LGP 2025-03-31
778 //
779 // NOTE -https://www.sqlite.org/sharedcache.html#dontuse says DONT USE SHAREDCACHE but not sure how todo shared memory DB
780 // without it? And it DOES tend to produce a lot of spurious SQLITE_BUSY errors - not sure what todo --LGP 2025-05-06
781 //
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 (); // often empty string
788 if (uriArg.empty ()) {
789 uriArg = ":memory";
790 }
791 else {
792 u8string safeCharURI = IO::Network::UniformResourceIdentification::PCTEncode (u8string{uriArg.begin (), uriArg.end ()}, {});
793 uriArg = "file:" + string{safeCharURI.begin (), safeCharURI.end ()} + "?mode=memory&cache=shared";
794 }
795 // For now, it appears we ALWAYS create memory DBS when opening (so cannot find a way to open shared) - so always set created flag
796 }
797
798 int e;
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);
803 fDB_ = nullptr;
804 }
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) {
807 ; // if?
808 }
809 }
810 }
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);
815 }
816 });
817 ThrowSQLiteError_ (e, fDB_);
818 }
819 SetBusyTimeout (options.fBusyTimeout.value_or (Options::kBusyTimeout_Default));
820 if (options.fJournalMode) {
821 SetJournalMode (*options.fJournalMode);
822 }
823 EnsureNotNull (fDB_);
824 }
825 ~ConnectionRep_ ()
826 {
827 AssertNotNull (fDB_);
828 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
829 }
830 virtual shared_ptr<const EngineProperties> GetEngineProperties () const override
831 {
832 struct MyEngineProperties_ final : EngineProperties {
833 virtual String GetEngineName () const override
834 {
835 return "SQLite"sv;
836 }
837 };
838 static const shared_ptr<const EngineProperties> kProps_ = Memory::MakeSharedPtr<const MyEngineProperties_> ();
839 return kProps_;
840 }
841 virtual Database::Document::Connection::Options GetOptions () const override
842 {
843 return fOptions_;
844 }
845 virtual uintmax_t GetSpaceConsumed () const override
846 {
847 uintmax_t szTotal{};
848 auto incSize = [&] (const filesystem::path& p) {
849 error_code ec{};
850 uintmax_t sz = filesystem::file_size (p, ec);
851 if (!ec) {
852 szTotal += sz;
853 }
854 };
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"));
860 }
861 else {
863 }
864 return szTotal;
865 }
866 virtual Set<String> GetCollections () override
867 {
868 // treat named all tables as collections (maybe just count those with two columns id/json?).
869 return WrapExecute_ (
870 [&] () {
871 Set<String> results;
872 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
873 Assert (argc == 1);
874 results.Add (String::FromUTF8 (argv[0]));
875 return SQLITE_OK;
876 }};
877 // a callback that threw stopped the exec itself, so its exception is the error - not whatever sqlite3 made of being stopped
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_);
882 return results;
883 },
884 nullopt, false);
885 }
886 virtual Document::Collection::Ptr CreateCollection (const String& name) override
887 {
888 return WrapExecute_ (
889 [&] () {
890 // NOTE - the ID is stored OUTSIDE of the json, and never INSIDE the json object
891 if (fAllowUserDefinedRowID_) {
892 Exec ("create table if not exists \"{}\" (id TEXT PRIMARY KEY, json NOT NULL) WITHOUT ROWID;"_f(name));
893 }
894 else {
895 Exec ("create table if not exists \"{}\" (id INTEGER PRIMARY KEY, json NOT NULL);"_f(name));
896 }
898 Memory::MakeSharedPtr<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
899 },
900 name, true);
901 }
902 virtual void DropCollection (const String& name) override
903 {
904 WrapExecute_ ([&] () { Exec ("drop table \"{}\";"_f(name)); }, name, true);
905 }
906 virtual Document::Collection::Ptr GetCollection (const String& name) override
907 {
908 scoped_lock declareContext{fMaybeLock_};
909 Require (GetCollections ().Contains (name));
911 Memory::MakeSharedPtr<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
912 }
913 virtual Document::Transaction mkTransaction () override
914 {
915 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
916 return Database::Document::SQLite::Transaction{conn};
917 }
918 virtual void Exec (const String& sql) override
919 {
920 scoped_lock declareContext{fMaybeLock_};
921 WrapExecute_ (
922 [&] () {
923 int e = ::sqlite3_exec (fDB_, sql.AsUTF8<string> ().c_str (), nullptr, nullptr, nullptr);
924 if (e != SQLITE_OK) [[unlikely]] {
925 ThrowSQLiteErrorIfNotOK_ (e, fDB_);
926 }
927 },
928 nullopt, true);
929 }
930 virtual Duration GetBusyTimeout () const override
931 {
932 scoped_lock declareContext{fMaybeLock_};
933 optional<int> d;
934 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
935 Assert (argc == 1);
936 Assert (::strcmp (azColName[0], "timeout") == 0);
937 int val = ::atoi (argv[0]);
938 Assert (val >= 0);
939 d = val;
940 return SQLITE_OK;
941 }};
942 int execResult = ::sqlite3_exec (fDB_, "pragma busy_timeout;", callback.GetStaticFunction (), callback.GetData (), nullptr);
943 callback.ThrowIfCapturedException ();
944 ThrowSQLiteErrorIfNotOK_ (execResult, fDB_);
945 Assert (d);
946 return Duration{double (*d) / 1000.0};
947 }
948 virtual void SetBusyTimeout (const Duration& timeout) override
949 {
950 scoped_lock declareContext{fMaybeLock_};
951 ThrowSQLiteErrorIfNotOK_ (::sqlite3_busy_timeout (fDB_, (int)(timeout.As<float> () * 1000)), fDB_);
952 }
953 virtual JournalModeType GetJournalMode () const override
954 {
955 optional<string> d;
956 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
957 Assert (argc == 1);
958 Assert (::strcmp (azColName[0], "journal_mode") == 0);
959 d = argv[0];
960 return SQLITE_OK;
961 }};
962 int execResult = ::sqlite3_exec (fDB_, "pragma journal_mode;", callback.GetStaticFunction (), callback.GetData (), nullptr);
963 callback.ThrowIfCapturedException ();
964 ThrowSQLiteErrorIfNotOK_ (execResult, fDB_);
965 Assert (d);
966 if (d == "delete"sv) {
967 return JournalModeType::eDelete;
968 }
969 if (d == "truncate"sv) {
970 return JournalModeType::eTruncate;
971 }
972 if (d == "persist"sv) {
973 return JournalModeType::ePersist;
974 }
975 if (d == "memory"sv) {
976 return JournalModeType::eMemory;
977 }
978 if (d == "wal"sv) {
979 return JournalModeType::eWAL;
980 }
981 if (d == "wal2"sv) {
982 return JournalModeType::eWAL2;
983 }
984 if (d == "off"sv) {
985 return JournalModeType::eOff;
986 }
988 return JournalModeType::eDelete;
989 }
990 virtual void SetJournalMode (JournalModeType journalMode) override
991 {
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_);
996 break;
997 case JournalModeType::eTruncate:
998 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'truncate';", nullptr, 0, nullptr), fDB_);
999 break;
1000 case JournalModeType::ePersist:
1001 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'persist';", nullptr, 0, nullptr), fDB_);
1002 break;
1003 case JournalModeType::eMemory:
1004 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'memory';", nullptr, 0, nullptr), fDB_);
1005 break;
1006 case JournalModeType::eWAL:
1007 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'wal';", nullptr, 0, nullptr), fDB_);
1008 break;
1009 case JournalModeType::eWAL2:
1010 if (GetJournalMode () == JournalModeType::eWAL) {
1011 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'delete';", nullptr, 0, nullptr), fDB_);
1012 }
1013 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'wal2';", nullptr, 0, nullptr), fDB_);
1014 break;
1015 case JournalModeType::eOff:
1016 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'off';", nullptr, 0, nullptr), fDB_);
1017 break;
1018 }
1019 }
1020
1021 template <typename FUN>
1022 inline auto WrapExecute_ (FUN&& f, const optional<String>& collectionName, bool write) -> invoke_result_t<FUN>
1023 {
1024 return Document::Connection::Private_::WrapLoggingExecuteHelper_ (forward<FUN> (f), this, fOptions_, collectionName, write);
1025 }
1026
1027 ::sqlite3* fDB_{};
1028 };
1029}
1030
1031/*
1032 ********************************************************************************
1033 *********************** SQL::SQLite::Connection::Ptr ***************************
1034 ********************************************************************************
1035 */
1036Document::SQLite::Connection::Ptr::Ptr (const shared_ptr<IRep>& src)
1037 : inherited{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);
1040 RequireNotNull (thisObj->operator->());
1041 return thisObj->operator->()->GetBusyTimeout ();
1042 },
1043 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, auto timeout) {
1044 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
1045 RequireNotNull (thisObj->operator->());
1046 thisObj->operator->()->SetBusyTimeout (timeout);
1047 }}
1048 , journalMode{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
1049 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
1050 RequireNotNull (thisObj->operator->());
1051 return thisObj->operator->()->GetJournalMode ();
1052 },
1053 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, auto journalMode) {
1054 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
1055 RequireNotNull (thisObj->operator->());
1056 thisObj->operator->()->SetJournalMode (journalMode);
1057 }}
1058{
1059#if qStroika_Foundation_Debug_AssertExternallySynchronizedChecker_Enabled
1060 if (src != nullptr) {
1061 _fAssertExternallySynchronizedChecker.SetAssertExternallySynchronizedCheckerContext (src->fAssertExternallySynchronizedChecker.GetSharedContext ());
1062 }
1063#endif
1064}
1065
1066/*
1067 ********************************************************************************
1068 ************************** SQL::SQLite::Connection *****************************
1069 ********************************************************************************
1070 */
1071auto Document::SQLite::Connection::New (const Options& options) -> Ptr
1072{
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)};
1078 default:
1080 return nullptr;
1081 }
1082}
1083
1084/*
1085 ********************************************************************************
1086 ******************************* SQLite::Transaction ****************************
1087 ********************************************************************************
1088 */
1089struct Transaction::MyRep_ : public Database::Document::Transaction::IRep {
1090 MyRep_ (const Connection::Ptr& db, Flag f)
1091 : fConnectionPtr_{db}
1092 {
1093 switch (f) {
1094 case Flag::eDeferred:
1095 db->Exec ("BEGIN DEFERRED TRANSACTION;"sv);
1096 break;
1097 case Flag::eExclusive:
1098 db->Exec ("BEGIN EXCLUSIVE TRANSACTION;"sv);
1099 break;
1100 case Flag::eImmediate:
1101 db->Exec ("BEGIN IMMEDIATE TRANSACTION;"sv);
1102 break;
1103 default:
1105 }
1106 }
1107 virtual void Commit () override
1108 {
1109 Require (not fCompleted_);
1110 fCompleted_ = true;
1111 fConnectionPtr_->Exec ("COMMIT TRANSACTION;"sv);
1112 }
1113 virtual void Rollback () override
1114 {
1115 Require (not fCompleted_);
1116 fCompleted_ = true;
1117 fConnectionPtr_->Exec ("ROLLBACK TRANSACTION;"sv);
1118 }
1119 virtual Disposition GetDisposition () const override
1120 {
1121 // @todo record more info so we can report finer grained status ; try/catch in rollback/commit and dbgtraces
1122 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
1123 }
1124 Connection::Ptr fConnectionPtr_;
1125 bool fCompleted_{false};
1126};
1127Transaction::Transaction (const Connection::Ptr& db, Flag f)
1128 : inherited{make_unique<MyRep_> (db, f)}
1129{
1130}
1131#endif
#define AssertNotNull(p)
Definition Assertions.h:336
#define EnsureNotNull(p)
Definition Assertions.h:343
#define RequireNotReached()
Definition Assertions.h:388
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:49
#define WeakAssertNotImplemented()
Definition Assertions.h:486
#define RequireNotNull(p)
Definition Assertions.h:350
#define AssertNotReached()
Definition Assertions.h:358
#define Verify(c)
Definition Assertions.h:422
#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...
Definition StdCompat.h:481
#define DbgTrace
Definition Trace.h:327
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
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)
Definition Mapping.inl:186
nonvirtual bool ContainsKey(ArgByValueType< key_type > key) const
Definition Mapping.inl:175
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.
Definition Mapping.inl:233
nonvirtual void RemoveAll()
RemoveAll removes all, or all matching (predicate, iterator range, equals comparer or whatever) items...
Definition Mapping.inl:238
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 push_back(ArgByValueType< value_type > item)
Definition Sequence.inl:782
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
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:219
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
nonvirtual bool Contains(ArgByValueType< T > element, EQUALS_COMPARER &&equalsComparer=EQUALS_COMPARER{}) const
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:311
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.
Definition Iterable.inl:317
An Iterator<T> is a copyable object which allows traversing the contents of some container.
Definition Iterator.h:253
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
Definition Throw.inl:54
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
void ThrowError(error_code ec)
throw a SystemErrorException with the given error_code - or std::bad_alloc for errc::not_enough_memor...