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