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