Stroika Library 3.0d23x
 
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
200/*
201 ********************************************************************************
202 *************************** SQLite::CompiledOptions ****************************
203 ********************************************************************************
204 */
205namespace {
206 struct VerifyFlags_ {
207 VerifyFlags_ ()
208 {
209 Assert (CompiledOptions::kThe.ENABLE_NORMALIZE == !!::sqlite3_compileoption_used ("ENABLE_NORMALIZE"));
210 Assert (CompiledOptions::kThe.THREADSAFE == !!::sqlite3_compileoption_used ("THREADSAFE"));
211#if SQLITE_VERSION_NUMBER < 3038000
212 Assert (::sqlite3_compileoption_used ("ENABLE_JSON1"));
213#else
214 Assert (!::sqlite3_compileoption_used ("OMIT_JSON1"));
215#endif
216 }
217 } sVerifyFlags_;
218}
219
220namespace {
221 /**
222 * Break the given Stroika filter into parts that can be remoted to SQLite db, and parts that must be handled locally
223 */
224 tuple<optional<String>, optional<Filter>> Partition_ (const optional<Filter>& filter)
225 {
226 if (filter) {
227 /*
228 * For now just look for FIELD EQUALS VALUE expressions in the top level conjunction. These can be done
229 * server or client side transparently, and moving them server side is more efficient.
230 *
231 * Much more could be done, but this is a good cost/benefit start.
232 */
234 StringBuilder whereClause;
235 for (Document::FilterElements::Operation op : filter->GetConjunctionOperations ()) {
236 bool transferred = false;
237 if (const Document::FilterElements::Equals* eqOp = get_if<Document::FilterElements::Equals> (&op)) {
238 if (const Document::FilterElements::Value* rhsValue = get_if<Document::FilterElements::Value> (&eqOp->fRHS)) {
239 if (not whereClause.empty ()) {
240 whereClause << " AND "sv;
241 }
242 // move to server side
243 if (eqOp->fLHS == Database::Document::kID) {
244 whereClause << "{} = '{}'"_f(Database::Document::kID, rhsValue->As<String> ()); // not sure this is right way to compare?
245 }
246 else {
247 // others compared in json part
248 String vSQL = rhsValue->As<String> ();
249 if (rhsValue->GetType () == VariantValue::eString or rhsValue->GetType () == VariantValue::eDate or
250 rhsValue->GetType () == VariantValue::eDateTime) {
251 vSQL = "'"sv + vSQL + "'"sv;
252 }
253 whereClause << "json_extract(json, '$.{}') = {}"_f(String{eqOp->fLHS}, vSQL); // not sure this is right way to compare?
254 }
255 transferred = true;
256 }
257 }
258 if (not transferred) {
259 clientSideOps += op; // keep for client side
260 }
261 }
262 if (not whereClause.empty ()) {
263 // if we moved any to server side, then return the filterDoc and the client side ops
264 return make_tuple (whereClause, clientSideOps.empty () ? optional<Filter>{} : make_optional (Filter{clientSideOps}));
265 }
266 // else no change
267 return make_tuple (nullopt, filter);
268 }
269 return make_tuple (nullopt, nullopt);
270 }
271}
272
273namespace {
274 /**
275 * Break the given Stroika filter into parts that can be handled in SQLite, and parts that must be handled locally
276 * Also return array showing names cuz SQLite returns array (for > 1) and these are the names in this order of the fields/objects:
277 */
278 tuple<optional<tuple<String, Sequence<String>>>, optional<Projection>> Partition_ (const optional<Projection>& p)
279 {
280 if (p) {
281 /*
282 * json_extract appears to only support 'include' and not 'omit' operations
283 */
284 tuple<Document::Projection::Flag, Set<String>> fields = p->GetFields ();
285 // Require (get<1> (fields).size () >= 1); // cannot (usefully) project to null-space (can cuz id handled elsewhere now)
286 if (get<Document::Projection::Flag> (fields) == Document::Projection::Flag::eInclude) {
287 StringBuilder projectionQuery;
288 Sequence<String> fieldNames;
289 for (String f : get<1> (fields)) {
290 String mongoFieldName = f;
291 Require (f != Document::kID); // prevented before calling here - handled in caller
292 if (fieldNames.empty ()) {
293 projectionQuery << "json_extract(json,'"sv;
294 }
295 else {
296 projectionQuery << ","sv;
297 }
298 projectionQuery << "$."sv << mongoFieldName;
299 fieldNames.push_back (mongoFieldName);
300 }
301 if (not fieldNames.empty ()) {
302 projectionQuery << "')"sv;
303 }
304 return make_tuple (make_tuple (projectionQuery, fieldNames), nullopt);
305 }
306 else {
307 return make_tuple (nullopt, p); // cannot optimize exclude (yet)
308 }
309 }
310 return make_tuple (nullopt, nullopt);
311 }
312 // called with the result of a statement after a 'step' operation that produced a ROW.
313 // And assumes the row contains data from the Partition_ algorithm above
314 Document::Document ExtractRowValueAfterStep_ (::sqlite3_stmt* statement, int dataCol, const IDType& id,
315 const optional<tuple<String, Sequence<String>>>& sqliteProjection,
316 const optional<Projection>& remainingProjection)
317 {
318 /*
319 * https://www.sqlite.org/json1.html
320 * "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"
321 */
322 static const auto kJSONReader_ = Variant::JSON::Reader{};
323 VariantValue valueReadBackFromDB =
324 kJSONReader_.Read (String::FromUTF8 (reinterpret_cast<const char*> (::sqlite3_column_text (statement, dataCol))));
326 if (sqliteProjection == nullopt) {
327 dr = valueReadBackFromDB.As<Mapping<String, VariantValue>> ();
328 }
329 else {
330 auto arrayOfFieldNames = get<Sequence<String>> (*sqliteProjection);
331 if (arrayOfFieldNames.size () == 1) {
332 dr = Mapping<String, VariantValue>{{arrayOfFieldNames[0], valueReadBackFromDB}};
333 }
334 else {
335 Assert (valueReadBackFromDB.GetType () == VariantValue::eArray);
336 Assert (valueReadBackFromDB.As<Sequence<VariantValue>> ().size () == arrayOfFieldNames.size ());
337 Iterator<String> nameI = arrayOfFieldNames.begin ();
338 dr = valueReadBackFromDB.As<Sequence<VariantValue>> ().Map<Document::Document> (
339 [&nameI] (const VariantValue& vv) mutable { return KeyValuePair{*nameI++, vv}; });
340 Assert (nameI == arrayOfFieldNames.end ());
341 }
342 }
343 if ((sqliteProjection and get<Sequence<String>> (*sqliteProjection).Contains (Document::kID)) or remainingProjection == nullopt or
344 remainingProjection->Includes (Document::kID)) {
345 dr.Add (Document::kID, id);
346 }
347 if (remainingProjection) {
348 dr = remainingProjection->Apply (dr);
349 }
350 return dr;
351 };
352}
353
354namespace {
355 using Connection::Options;
356 struct ConnectionRep_ final : Database::Document::SQLite::Connection::IRep {
357
358 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex_;
359 const Options fOptions_;
360 const bool fAllowUserDefinedRowID_{false};
361
363 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex_; // since shares unsynchronized connection, share its context
364 shared_ptr<ConnectionRep_> fConnectionRep_; // save to bump reference count
365 String fTableName_;
366
367 MyPreparedStatement_ fAddStatement_{};
368 MyPreparedStatement_ fGetOneStatement_{};
369 MyPreparedStatement_ fRemoveStatement_{};
370 MyPreparedStatement_ fUpdateStatement_{};
371
372 CollectionRep_ (const shared_ptr<ConnectionRep_>& connectionRep, const String& collectionName)
373 : fConnectionRep_{connectionRep}
374 , fTableName_{collectionName}
375 {
376#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
377 fAssertExternallySynchronizedMutex_.SetAssertExternallySynchronizedMutexContext (
378 connectionRep->fAssertExternallySynchronizedMutex_.GetSharedContext ());
379#endif
380 }
381 virtual ~CollectionRep_ () = default;
382 virtual IDType Add (const Document::Document& v) override
383 {
384#if USE_NOISY_TRACE_IN_THIS_MODULE_
385 TraceContextBumper ctx{"SQLite::CollectionRep_::Add"};
386#endif
387 Require (not v.ContainsKey (Database::Document::kID) or fConnectionRep_->fOptions_.fAddAllowsExternallySpecifiedIDs);
388 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
389
390 Document::Document jsonValue2Write = v;
391
392 // Extract (or produce new) ID in fAllowUserDefinedRowID_ mode to use as top level ID
393 // but on NO case allow for ID in JSON
394 optional<IDType> id;
395 if (fConnectionRep_->fAllowUserDefinedRowID_) {
396 if (auto o = v.Lookup (Database::Document::kID)) {
397 id = o->As<String> ();
398 jsonValue2Write.RemoveIf (Database::Document::kID); // don't write ID to JSON
399 }
400 else {
402 }
403 }
404 Assert (not jsonValue2Write.ContainsKey (Database::Document::kID));
405
406 /**
407 * UNCLEAR if this way of capturing row_id is threadsafe or not.
408 * MAYBE OK if not using 'full mutex' mode on database connection? @todo FIGURE OUT!!!!
409 *
410 * @todo: SIMONE suggests using GUID, and pre-computing the ID, and using that.
411 * COULD just precompute the id (easier if SQLite had sequence type) - or do two inserts - lots of tricky ways.
412 * none that efficient and clean and simple. I guess this is clean and simple and efficient, just probably a race
413 */
414 if (fAddStatement_ == nullptr) [[unlikely]] {
415 if (fConnectionRep_->fAllowUserDefinedRowID_) {
416 fAddStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_, "insert into {} (id, json) values(?,?);"_f(fTableName_)};
417 }
418 else {
419 fAddStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_, "insert into {} (json) values(?);"_f(fTableName_)};
420 }
421 }
422 static const auto kJSONWriter_ = Variant::JSON::Writer{};
423 string jsonText = kJSONWriter_.WriteAsString (VariantValue{jsonValue2Write}).AsUTF8<string> ();
424 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fAddStatement_), fConnectionRep_->fDB_);
425 if (fConnectionRep_->fAllowUserDefinedRowID_) {
426 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fAddStatement_, 1, id->AsUTF8<string> ().c_str (),
427 static_cast<int> (id->AsUTF8 ().length ()), SQLITE_TRANSIENT),
428 fConnectionRep_->fDB_);
429 }
430 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fAddStatement_, fConnectionRep_->fAllowUserDefinedRowID_ ? 2 : 1,
431 jsonText.c_str (), static_cast<int> (jsonText.length ()), SQLITE_TRANSIENT),
432 fConnectionRep_->fDB_);
433 int rc = ::sqlite3_step (fAddStatement_);
434 if (rc != SQLITE_DONE) {
435 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
436 }
437 return fConnectionRep_->fAllowUserDefinedRowID_ ? *id : "{}"_f(::sqlite3_last_insert_rowid (fConnectionRep_->fDB_));
438 }
439 virtual optional<Document::Document> Get (const IDType& id, const optional<Projection>& projection) override
440 {
441#if USE_NOISY_TRACE_IN_THIS_MODULE_
442 TraceContextBumper ctx{"SQLite::CollectionRep_::Get", "id={}, projection={}"_f, id, projection};
443#endif
444 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
445
446 // locally construct MyPreparedStatement_ for case with projection, and/or cache statement for grabbing whole thing
447 optional<Projection> projectionWithoutID = projection;
448 bool includeIDInProjection = true;
449 if (projectionWithoutID) {
450 auto [flags, fieldNames] = projectionWithoutID->GetFields ();
451 switch (flags) {
452 case Document::Projection::Flag::eInclude: {
453 if (fieldNames.Contains (Document::kID)) {
454 fieldNames.Remove (Document::kID);
455 projectionWithoutID = Projection{flags, fieldNames};
456 }
457 else {
458 includeIDInProjection = false;
459 }
460 } break;
461 case Document::Projection::Flag::eOmit: {
462 if (fieldNames.Contains (Document::kID)) {
463 includeIDInProjection = false;
464 projectionWithoutID = Projection{flags, fieldNames};
465 }
466 } break;
467 }
468 }
469 auto [sqliteProjection, remainingAfterProjection] = Partition_ (projectionWithoutID);
470 optional<MyPreparedStatement_> sqliteProjectionStatement;
471 bool ignoreResult = false;
472 if (sqliteProjection) {
473 // DbgTrace ("sqliteProjectionStatement:= select {} from {} where id=?;"_f, get<String> (*sqliteProjection), fTableName_);
474 if (get<String> (*sqliteProjection).empty ()) {
475 sqliteProjectionStatement.emplace (fConnectionRep_->fDB_, "select NULL from {} where id=?;"_f(fTableName_));
476 ignoreResult = true;
477 }
478 else {
479 sqliteProjectionStatement.emplace (fConnectionRep_->fDB_,
480 "select {} from {} where id=?;"_f(get<String> (*sqliteProjection), fTableName_));
481 }
482 }
483 else if (fGetOneStatement_ == nullptr) [[unlikely]] {
484 fGetOneStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_, "select json from {} where id=?;"_f(fTableName_)};
485 }
486 ::sqlite3_stmt* useStatment = sqliteProjectionStatement.has_value () ? *sqliteProjectionStatement : fGetOneStatement_;
487 AssertNotNull (useStatment);
488
489 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (useStatment), fConnectionRep_->fDB_);
490 string idAsUTFSTR = id.AsUTF8<string> ();
491 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (useStatment, 1, idAsUTFSTR.c_str (), static_cast<int> (idAsUTFSTR.length ()), SQLITE_TRANSIENT),
492 fConnectionRep_->fDB_);
493
494 int rc = ::sqlite3_step (useStatment);
495 optional<Document::Document> result;
496 if (rc == SQLITE_ROW) [[likely]] {
497 if (ignoreResult) {
498 result = Document::Document{};
499 }
500 else {
501 result = ExtractRowValueAfterStep_ (useStatment, 0, id, sqliteProjection, remainingAfterProjection);
502 }
503 rc = ::sqlite3_step (useStatment);
504 }
505 if (rc != SQLITE_DONE) [[unlikely]] {
506 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
507 }
508 if (includeIDInProjection) {
509 result->Add (Document::kID, id);
510 }
511 return result;
512 }
513 virtual Sequence<Document::Document> GetAll (const optional<Filter>& filter, const optional<Projection>& projection) override
514 {
515#if USE_NOISY_TRACE_IN_THIS_MODULE_
516 TraceContextBumper ctx{"SQLite::CollectionRep_::GetAll", "filter={}, projection={}"_f, filter, projection};
517#endif
518 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
519
520 // Optimize some important special cases
522 if (filter == nullopt and projection == nullopt) {
523 MyPreparedStatement_ statement{fConnectionRep_->fDB_, "select id,json from {};"_f(fTableName_)};
524 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (statement), fConnectionRep_->fDB_);
525 int rc;
526 while ((rc = ::sqlite3_step (statement)) == SQLITE_ROW) {
527 static const auto kJSONReader_ = Variant::JSON::Reader{};
528 String id = String::FromUTF8 (reinterpret_cast<const char*> (::sqlite3_column_text (statement, 0)));
529 VariantValue valueReadBackFromDB =
530 kJSONReader_.Read (String::FromUTF8 (reinterpret_cast<const char*> (::sqlite3_column_text (statement, 1))));
531 Document::Document vDoc = valueReadBackFromDB.As<Mapping<String, VariantValue>> ();
532 vDoc.Add (Document::kID, id);
533 result += vDoc;
534 }
535 if (rc != SQLITE_DONE) [[unlikely]] {
536 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
537 }
538 }
539 else if (filter == nullopt and projection == kOnlyIDs) {
540 MyPreparedStatement_ statement{fConnectionRep_->fDB_, "select id from {};"_f(fTableName_)};
541 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (statement), fConnectionRep_->fDB_);
542 int rc;
543 while ((rc = ::sqlite3_step (statement)) == SQLITE_ROW) {
544 String id = String::FromUTF8 (reinterpret_cast<const char*> (::sqlite3_column_text (statement, 0)));
546 vDoc.Add (Document::kID, id);
547 result += vDoc;
548 }
549 if (rc != SQLITE_DONE) [[unlikely]] {
550 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
551 }
552 }
553 else {
554 // general case
555 auto [sqliteWhereClause, remainingFilter] = Partition_ (filter);
556
557 //DbgTrace ("sqliteWhereClause={}"_f, sqliteWhereClause);
558 //DbgTrace ("remainingFilter={}"_f, remainingFilter);
559 // if there is a remainingFilter (performed after SQLite) - we need to form full objects and then apply the filter at the end
560 // 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
561 auto [sqliteProjection, remainingAfterProjection] = remainingFilter ? make_tuple (nullopt, projection) : Partition_ (projection);
562
563 MyPreparedStatement_ statement{
564 fConnectionRep_->fDB_,
565 "select id,{} from {} {};"_f(sqliteProjection == nullopt ? "json"_k : get<String> (*sqliteProjection), fTableName_,
566 sqliteWhereClause == nullopt ? "" : ("where "_k + *sqliteWhereClause))};
567
568 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (statement), fConnectionRep_->fDB_);
569 int rc;
570 while ((rc = ::sqlite3_step (statement)) == SQLITE_ROW) {
571 String id = String::FromUTF8 (reinterpret_cast<const char*> (::sqlite3_column_text (statement, 0)));
572 Document::Document vDoc = ExtractRowValueAfterStep_ (statement, 1, id, sqliteProjection, remainingAfterProjection);
573 if (remainingFilter == nullopt or remainingFilter->Matches (vDoc)) {
574 if (remainingAfterProjection) {
575 vDoc = remainingAfterProjection->Apply (vDoc); // some attributes need to be projected after filter cuz maybe used in filtering
576 }
577 result += vDoc;
578 }
579 }
580 if (rc != SQLITE_DONE) [[unlikely]] {
581 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
582 }
583 }
584 return result;
585 }
586 virtual void Update (const IDType& id, const Document::Document& newV, const optional<Set<String>>& onlyTheseFields) override
587 {
588#if USE_NOISY_TRACE_IN_THIS_MODULE_
589 TraceContextBumper ctx{"SQLite::CollectionRep_::Update"};
590#endif
591 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
592 Document::Document uploadDoc = newV;
593 if (onlyTheseFields) {
594 uploadDoc.RetainAll (*onlyTheseFields);
595 }
596 // POOR IMPLEMENTATION - should use sql update - but tricky for this case, so KISS, and get functionally working so
597 // I can integrate this code in regtests
598 Document::Document d2Update = onlyTheseFields ? Memory::ValueOfOrThrow (Get (id, nullopt)) : uploadDoc;
599 // any fields listed in onlyTheseFields, but not present in newV need to be removed
600 if (onlyTheseFields) {
601 d2Update.AddAll (uploadDoc);
602 Set<String> removeMe = *onlyTheseFields - newV.Keys ();
603 d2Update.RemoveAll (removeMe);
604 }
605 d2Update.RemoveIf (Document::kID); // never write this to the JSON
606
607 if (fUpdateStatement_ == nullptr) [[unlikely]] {
608 fUpdateStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_, "update {} SET json=? where id=?;"_f(fTableName_)};
609 }
610 static const auto kJSONWriter_ = Variant::JSON::Writer{};
611 string r = kJSONWriter_.WriteAsString (VariantValue{d2Update}).AsUTF8<string> ();
612 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fUpdateStatement_), fConnectionRep_->fDB_);
613 string idText = id.AsUTF8<string> ();
614 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fUpdateStatement_, 1, r.c_str (), static_cast<int> (r.length ()), SQLITE_TRANSIENT),
615 fConnectionRep_->fDB_);
616 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fUpdateStatement_, 2, idText.c_str (), static_cast<int> (idText.length ()), SQLITE_TRANSIENT),
617 fConnectionRep_->fDB_);
618 int rc = ::sqlite3_step (fUpdateStatement_);
619 if (rc != SQLITE_DONE) {
620 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
621 }
622 }
623 virtual void Remove (const IDType& id) override
624 {
625#if USE_NOISY_TRACE_IN_THIS_MODULE_
626 TraceContextBumper ctx{"SQLite::CollectionRep_::Remove"};
627#endif
628 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
629 if (fRemoveStatement_ == nullptr) [[unlikely]] {
630 fRemoveStatement_ = MyPreparedStatement_{fConnectionRep_->fDB_, "delete from {} where id=?;"_f(fTableName_)};
631 }
632 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fRemoveStatement_), fConnectionRep_->fDB_);
633 string idText = id.AsUTF8<string> ();
634 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fRemoveStatement_, 1, idText.c_str (), static_cast<int> (idText.length ()), SQLITE_TRANSIENT),
635 fConnectionRep_->fDB_);
636 int rc = ::sqlite3_step (fRemoveStatement_);
637 if (rc != SQLITE_DONE) {
638 ThrowSQLiteErrorIfNotOK_ (rc, fConnectionRep_->fDB_);
639 }
640 }
641 };
642
643 ConnectionRep_ (const Options& options)
644 : fOptions_{options}
645 , fAllowUserDefinedRowID_{options.fAddAllowsExternallySpecifiedIDs}
646 {
647 TraceContextBumper ctx{"SQLite::Connection::ConnectionRep_::ConnectionRep_"};
648
649 int flags = 0;
650 // https://www.sqlite.org/threadsafe.html explains the thread-safety stuff. Not sure I have it right, but hopefully --LGP 2023-09-13
651 switch (options.fThreadingMode.value_or (Options::kDefault_ThreadingMode)) {
652 case Options::ThreadingMode::eSingleThread:
653 break;
654 case Options::ThreadingMode::eMultiThread:
655 Require (CompiledOptions::kThe.THREADSAFE);
656 Require (::sqlite3_threadsafe ());
657 flags |= SQLITE_OPEN_NOMUTEX;
658 break;
659 case Options::ThreadingMode::eSerialized:
660 Require (CompiledOptions::kThe.THREADSAFE);
661 Require (::sqlite3_threadsafe ());
662 flags |= SQLITE_OPEN_FULLMUTEX;
663 break;
664 }
665
666 if (options.fImmutable) {
667 // NYI cuz requires uri syntax
669 Require (options.fReadOnly);
670 }
671 flags |= options.fReadOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
672
673 string uriArg;
675 [[maybe_unused]] int n{};
676 if (options.fDBPath) {
677 ++n;
678 }
679 if (options.fTemporaryDB) {
680 ++n;
681 }
682 if (options.fInMemoryDB) {
683 ++n;
684 }
685 Require (n == 1); // exactly one of fDBPath, fTemporaryDB, fInMemoryDB must be provided
686 }
687 if (options.fDBPath) {
688 uriArg = options.fDBPath->generic_string ();
689 if (uriArg[0] == ':') {
690 uriArg = "./" + uriArg; // SQLite docs warn to do this, to avoid issues with :memory or other extensions
691 }
692 }
693 if (options.fTemporaryDB) {
694 uriArg = string{};
695 // According to https://sqlite.org/inmemorydb.html, temporary DBs appear to require empty name
696 // @todo MAYBE fix to find a way to do named temporary DB? - or adjust API so no string name provided.
697 Require (not options.fTemporaryDB->empty ());
698 }
699 if (options.fInMemoryDB) {
700 // Not super clear why SQLITE_OPEN_URI needed, but the example in docs uses URI, and tracing through the SQLite open code
701 // it appears to require a URI format, but not really documented as near as I can tell...--LGP 2025-03-31
702 //
703 // NOTE -https://www.sqlite.org/sharedcache.html#dontuse says DONT USE SHAREDCACHE but not sure how todo shared memory DB
704 // without it? And it DOES tend to produce a lot of spurious SQLITE_BUSY errors - not sure what todo --LGP 2025-05-06
705 //
706 flags |= SQLITE_OPEN_MEMORY;
707 flags |= SQLITE_OPEN_URI;
708 flags |= SQLITE_OPEN_SHAREDCACHE;
709 Require (not options.fReadOnly);
710 Require (options.fCreateDBPathIfDoesNotExist);
711 uriArg = options.fInMemoryDB->AsNarrowSDKString (); // often empty string
712 if (uriArg.empty ()) {
713 uriArg = ":memory";
714 }
715 else {
716 u8string safeCharURI = IO::Network::UniformResourceIdentification::PCTEncode (u8string{uriArg.begin (), uriArg.end ()}, {});
717 uriArg = "file:" + string{safeCharURI.begin (), safeCharURI.end ()} + "?mode=memory&cache=shared";
718 }
719 // For now, it appears we ALWAYS create memory DBS when opening (so cannot find a way to open shared) - so always set created flag
720 }
721
722 int e;
723 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, flags, options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_CANTOPEN) {
724 if (options.fCreateDBPathIfDoesNotExist) {
725 if (fDB_ != nullptr) {
726 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
727 fDB_ = nullptr;
728 }
729 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, SQLITE_OPEN_CREATE | flags,
730 options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_OK) {
731 ; // if?
732 }
733 }
734 }
735 if (e != SQLITE_OK) [[unlikely]] {
736 [[maybe_unused]] auto&& cleanup = Finally ([this] () noexcept {
737 if (fDB_ != nullptr) {
738 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
739 }
740 });
741 ThrowSQLiteError_ (e, fDB_);
742 }
743 SetBusyTimeout (options.fBusyTimeout.value_or (Options::kBusyTimeout_Default));
744 if (options.fJournalMode) {
745 SetJournalMode (*options.fJournalMode);
746 }
747 EnsureNotNull (fDB_);
748 }
749 ~ConnectionRep_ ()
750 {
751 AssertNotNull (fDB_);
752 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
753 }
754 virtual shared_ptr<const EngineProperties> GetEngineProperties () const override
755 {
756 struct MyEngineProperties_ final : EngineProperties {
757 virtual String GetEngineName () const override
758 {
759 return "SQLite"sv;
760 }
761 };
762 static const shared_ptr<const EngineProperties> kProps_ = Memory::MakeSharedPtr<const MyEngineProperties_> ();
763 return kProps_;
764 }
765 virtual Database::Document::Connection::Options GetOptions () const override
766 {
767 return fOptions_;
768 }
769 virtual Set<String> GetCollections () override
770 {
771 // treat named all tables as collections (maybe just count those with two columns id/json?).
772 Set<String> results;
773 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
774 Assert (argc == 1);
775 results.Add (String::FromUTF8 (argv[0]));
776 return SQLITE_OK;
777 }};
778 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "SELECT name FROM sqlite_master WHERE type='table';",
779 callback.GetStaticFunction (), callback.GetData (), nullptr),
780 fDB_);
781 return results;
782 }
783 virtual Document::Collection::Ptr CreateCollection (const String& name) override
784 {
785 // NOTE - the ID is stored OUTSIDE of the json, and never INSIDE the json object
786 if (fAllowUserDefinedRowID_) {
787 Exec ("create table if not exists {} (id TEXT PRIMARY KEY, json NOT NULL) WITHOUT ROWID;"_f(name));
788 }
789 else {
790 Exec ("create table if not exists {} (id INTEGER PRIMARY KEY, json NOT NULL);"_f(name));
791 }
793 Memory::MakeSharedPtr<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
794 }
795 virtual void DropCollection (const String& name) override
796 {
797 Exec ("drop table {};"_f(name));
798 }
799 virtual Document::Collection::Ptr GetCollection (const String& name) override
800 {
801 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
802 Require (GetCollections ().Contains (name));
804 Memory::MakeSharedPtr<CollectionRep_> (Debug::UncheckedDynamicPointerCast<ConnectionRep_> (shared_from_this ()), name)};
805 }
806 virtual Document::Transaction mkTransaction () override
807 {
808 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
809 return Database::Document::SQLite::Transaction{conn};
810 }
811 virtual void Exec (const String& sql) override
812 {
813 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
814 int e = ::sqlite3_exec (fDB_, sql.AsUTF8<string> ().c_str (), nullptr, nullptr, nullptr);
815 if (e != SQLITE_OK) [[unlikely]] {
816 ThrowSQLiteErrorIfNotOK_ (e, fDB_);
817 }
818 }
819 virtual ::sqlite3* Peek () override
820 {
821 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
822 return fDB_;
823 }
824 virtual Duration GetBusyTimeout () const override
825 {
826 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{fAssertExternallySynchronizedMutex_};
827 optional<int> d;
828 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
829 Assert (argc == 1);
830 Assert (::strcmp (azColName[0], "timeout") == 0);
831 int val = ::atoi (argv[0]);
832 Assert (val >= 0);
833 d = val;
834 return SQLITE_OK;
835 }};
836 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma busy_timeout;", callback.GetStaticFunction (), callback.GetData (), nullptr), fDB_);
837 Assert (d);
838 return Duration{double (*d) / 1000.0};
839 }
840 virtual void SetBusyTimeout (const Duration& timeout) override
841 {
842 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
843 ThrowSQLiteErrorIfNotOK_ (::sqlite3_busy_timeout (fDB_, (int)(timeout.As<float> () * 1000)), fDB_);
844 }
845 virtual JournalModeType GetJournalMode () const override
846 {
847 optional<string> d;
848 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
849 Assert (argc == 1);
850 Assert (::strcmp (azColName[0], "journal_mode") == 0);
851 d = argv[0];
852 return SQLITE_OK;
853 }};
854 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode;", callback.GetStaticFunction (), callback.GetData (), nullptr), fDB_);
855 Assert (d);
856 if (d == "delete"sv) {
857 return JournalModeType::eDelete;
858 }
859 if (d == "truncate"sv) {
860 return JournalModeType::eTruncate;
861 }
862 if (d == "persist"sv) {
863 return JournalModeType::ePersist;
864 }
865 if (d == "memory"sv) {
866 return JournalModeType::eMemory;
867 }
868 if (d == "wal"sv) {
869 return JournalModeType::eWAL;
870 }
871 if (d == "wal2"sv) {
872 return JournalModeType::eWAL2;
873 }
874 if (d == "off"sv) {
875 return JournalModeType::eOff;
876 }
878 return JournalModeType::eDelete;
879 }
880 virtual void SetJournalMode (JournalModeType journalMode) override
881 {
882 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySynchronizedMutex_};
883 switch (journalMode) {
884 case JournalModeType::eDelete:
885 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'delete';", nullptr, 0, nullptr), fDB_);
886 break;
887 case JournalModeType::eTruncate:
888 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'truncate';", nullptr, 0, nullptr), fDB_);
889 break;
890 case JournalModeType::ePersist:
891 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'persist';", nullptr, 0, nullptr), fDB_);
892 break;
893 case JournalModeType::eMemory:
894 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'memory';", nullptr, 0, nullptr), fDB_);
895 break;
896 case JournalModeType::eWAL:
897 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'wal';", nullptr, 0, nullptr), fDB_);
898 break;
899 case JournalModeType::eWAL2:
900 if (GetJournalMode () == JournalModeType::eWAL) {
901 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'delete';", nullptr, 0, nullptr), fDB_);
902 }
903 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'wal2';", nullptr, 0, nullptr), fDB_);
904 break;
905 case JournalModeType::eOff:
906 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'off';", nullptr, 0, nullptr), fDB_);
907 break;
908 }
909 }
910
911 ::sqlite3* fDB_{};
912 };
913}
914
915/*
916 ********************************************************************************
917 *********************** SQL::SQLite::Connection::Ptr ***************************
918 ********************************************************************************
919 */
920Document::SQLite::Connection::Ptr::Ptr (const shared_ptr<IRep>& src)
921 : inherited{src}
922 , busyTimeout{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
923 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
924 RequireNotNull (thisObj->operator->());
925 return thisObj->operator->()->GetBusyTimeout ();
926 },
927 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, auto timeout) {
928 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
929 RequireNotNull (thisObj->operator->());
930 thisObj->operator->()->SetBusyTimeout (timeout);
931 }}
932 , journalMode{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
933 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
934 RequireNotNull (thisObj->operator->());
935 return thisObj->operator->()->GetJournalMode ();
936 },
937 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, auto journalMode) {
938 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
939 RequireNotNull (thisObj->operator->());
940 thisObj->operator->()->SetJournalMode (journalMode);
941 }}
942{
943#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
944 if (src != nullptr) {
945 _fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (src->fAssertExternallySynchronizedMutex.GetSharedContext ());
946 }
947#endif
948}
949
950/*
951 ********************************************************************************
952 ************************** SQL::SQLite::Connection *****************************
953 ********************************************************************************
954 */
955auto Document::SQLite::Connection::New (const Options& options) -> Ptr
956{
957 return Ptr{Memory::MakeSharedPtr<ConnectionRep_> (options)};
958}
959
960/*
961 ********************************************************************************
962 ******************************* SQLite::Transaction ****************************
963 ********************************************************************************
964 */
965struct Transaction::MyRep_ : public Database::Document::Transaction::IRep {
966 MyRep_ (const Connection::Ptr& db, Flag f)
967 : fConnectionPtr_{db}
968 {
969 switch (f) {
970 case Flag::eDeferred:
971 db->Exec ("BEGIN DEFERRED TRANSACTION;"sv);
972 break;
973 case Flag::eExclusive:
974 db->Exec ("BEGIN EXCLUSIVE TRANSACTION;"sv);
975 break;
976 case Flag::eImmediate:
977 db->Exec ("BEGIN IMMEDIATE TRANSACTION;"sv);
978 break;
979 default:
981 }
982 }
983 virtual void Commit () override
984 {
985 Require (not fCompleted_);
986 fCompleted_ = true;
987 fConnectionPtr_->Exec ("COMMIT TRANSACTION;"sv);
988 }
989 virtual void Rollback () override
990 {
991 Require (not fCompleted_);
992 fCompleted_ = true;
993 fConnectionPtr_->Exec ("ROLLBACK TRANSACTION;"sv);
994 }
995 virtual Disposition GetDisposition () const override
996 {
997 // @todo record more info so we can report finer grained status ; try/catch in rollback/commit and dbgtraces
998 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
999 }
1000 Connection::Ptr fConnectionPtr_;
1001 bool fCompleted_{false};
1002};
1003Transaction::Transaction (const Connection::Ptr& db, Flag f)
1004 : inherited{make_unique<MyRep_> (db, f)}
1005{
1006}
1007#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 CONTAINER_OF_Key_T As() const
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 optional< mapped_type > Lookup(ArgByValueType< key_type > key) const
Definition Mapping.inl:142
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
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: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
static GUID GenerateNew() noexcept
Definition GUID.cpp:76