Stroika Library 3.0d20
 
Loading...
Searching...
No Matches
SQL/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
12
13#include "SQLite.h"
14
15using namespace Stroika::Foundation;
16
17using namespace Characters;
18using namespace Debug;
19using namespace Database;
20using namespace Database::SQL::SQLite;
21using namespace Execution;
22using namespace Time;
23
24// Comment this in to turn on aggressive noisy DbgTrace in this module
25//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
26
27//#if qStroika_HasComponent_sqlite && defined(_MSC_VER)
28//// Use #pragma comment lib instead of explicit entry in the lib entry of the project file
29//#pragma comment(lib, "sqlite.lib")
30//#endif
31
33
34#if qStroika_HasComponent_sqlite
35namespace {
36 struct ModuleShutdown_ {
37 ~ModuleShutdown_ ()
38 {
39 Verify (::sqlite3_shutdown () == SQLITE_OK); // mostly pointless but avoids memory leak complaints
40 }
41 } sModuleShutdown_;
42 [[noreturn]] void ThrowSQLiteError_ (int errCode, sqlite3* sqliteConnection)
43 {
44 Require (errCode != SQLITE_OK);
45 optional<String> errMsgDetails;
46 if (sqliteConnection != nullptr) {
47 errMsgDetails = String::FromUTF8 (::sqlite3_errmsg (sqliteConnection));
48 }
49 switch (errCode) {
50 case SQLITE_BUSY: {
51 DbgTrace ("SQLITE_BUSY"_f); // The database file is locked
52 Throw (system_error{make_error_code (errc::device_or_resource_busy)});
53 } break;
54 case SQLITE_LOCKED: {
55 DbgTrace ("SQLITE_LOCKED"_f); // A table in the database is locked
56 Throw (system_error{make_error_code (errc::device_or_resource_busy)});
57 } break;
58 case SQLITE_CONSTRAINT: {
59 if (errMsgDetails) {
60 Throw (Exception{"SQLITE_CONSTRAINT: {}"_f(errMsgDetails)});
61 }
62 else {
63 static const auto kEx_ = Exception{"SQLITE_CONSTRAINT"sv};
64 Throw (kEx_);
65 }
66 } break;
67 case SQLITE_TOOBIG: {
68 static const auto kEx_ = Exception{"SQLITE_TOOBIG"sv};
69 Throw (kEx_);
70 } break;
71 case SQLITE_FULL: {
72 DbgTrace ("SQLITE_FULL"_f);
73 Throw (system_error{make_error_code (errc::no_space_on_device)});
74 } break;
75 case SQLITE_READONLY: {
76 static const auto kEx_ = Exception{"SQLITE_READONLY"sv};
77 Throw (kEx_);
78 } break;
79 case SQLITE_MISUSE: {
80 if (errMsgDetails) {
81 Throw (Exception{"SQLITE_MISUSE: {}"_f(errMsgDetails)});
82 }
83 else {
84 static const auto kEx_ = Exception{"SQLITE_MISUSE"sv};
85 Throw (kEx_);
86 }
87 } break;
88 case SQLITE_ERROR: {
89 if (errMsgDetails) {
90 Throw (Exception{"SQLITE_ERROR: {}"_f(errMsgDetails)});
91 }
92 else {
93 static const auto kEx_ = Exception{"SQLITE_ERROR"sv};
94 Throw (kEx_);
95 }
96 } break;
97 case SQLITE_NOMEM: {
98 DbgTrace ("SQLITE_NOMEM translated to bad_alloc"_f);
99 Throw (bad_alloc{});
100 } break;
101 }
102 if (errMsgDetails) {
103 Throw (Exception{"SQLite Error: {} (code {})"_f(errMsgDetails, errCode)});
104 }
105 else {
106 Throw (Exception{"SQLite Error: {}"_f(errCode)});
107 }
108 }
109 void ThrowSQLiteErrorIfNotOK_ (int errCode, sqlite3* sqliteConnection)
110 {
111 static_assert (SQLITE_OK == 0);
112 if (errCode != SQLITE_OK) [[unlikely]] {
113 ThrowSQLiteError_ (errCode, sqliteConnection);
114 }
115 }
116
117 /*
118 * Simple utility to be able to use lambdas with arbitrary captures more easily with sqlite c API
119 */
120 template <invocable<int, char**, char**> CB>
121 struct SQLiteCallback_ {
122 CB fCallback_;
123
124 using STATIC_FUNCTION_TYPE = int (*) (void*, int, char**, char**);
125
126 SQLiteCallback_ (CB&& cb)
127 : fCallback_{forward<CB> (cb)}
128 {
129 }
130 STATIC_FUNCTION_TYPE GetStaticFunction ()
131 {
132 return STATICFUNC_;
133 }
134 void* GetData ()
135 {
136 return this;
137 }
138
139 private:
140 static int STATICFUNC_ (void* SQLiteCallbackData, int argc, char** argv, char** azColName)
141 {
142 SQLiteCallback_* sqc = reinterpret_cast<SQLiteCallback_*> (SQLiteCallbackData);
143 return sqc->fCallback_ (argc, argv, azColName);
144 }
145 };
146}
147
148/*
149 ********************************************************************************
150 *************************** SQLite::CompiledOptions ****************************
151 ********************************************************************************
152 */
153namespace {
154 struct VerifyFlags_ {
155 VerifyFlags_ ()
156 {
157 Assert (CompiledOptions::kThe.ENABLE_NORMALIZE == !!::sqlite3_compileoption_used ("ENABLE_NORMALIZE"));
158 Assert (CompiledOptions::kThe.THREADSAFE == !!::sqlite3_compileoption_used ("THREADSAFE"));
159#if SQLITE_VERSION_NUMBER < 3038000
160 Assert (CompiledOptions::kThe.ENABLE_JSON1 == !!::sqlite3_compileoption_used ("ENABLE_JSON1"));
161#else
162 Assert (CompiledOptions::kThe.ENABLE_JSON1 == !::sqlite3_compileoption_used ("OMIT_JSON1"));
163#endif
164 }
165 } sVerifyFlags_;
166}
167
168namespace {
169 using Connection::Options;
170 struct Rep_ final : Database::SQL::SQLite::Connection::IRep, private Debug::AssertExternallySynchronizedMutex {
171 Rep_ (const Options& options)
172 {
173 TraceContextBumper ctx{"SQLite::Connection::Rep_::Rep_"};
174
175 int flags = 0;
176 // https://www.sqlite.org/threadsafe.html explains the thread-safety stuff. Not sure I have it right, but hopefully --LGP 2023-09-13
177 switch (options.fThreadingMode.value_or (Options::kDefault_ThreadingMode)) {
178 case Options::ThreadingMode::eSingleThread:
179 break;
180 case Options::ThreadingMode::eMultiThread:
181 Require (CompiledOptions::kThe.THREADSAFE);
182 Require (::sqlite3_threadsafe ());
183 flags |= SQLITE_OPEN_NOMUTEX;
184 break;
185 case Options::ThreadingMode::eSerialized:
186 Require (CompiledOptions::kThe.THREADSAFE);
187 Require (::sqlite3_threadsafe ());
188 flags |= SQLITE_OPEN_FULLMUTEX;
189 break;
190 }
191
192 if (options.fImmutable) {
193 // NYI cuz requires uri syntax
195 Require (options.fReadOnly);
196 }
197 flags |= options.fReadOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
198
199 string uriArg;
201 [[maybe_unused]] int n{};
202 if (options.fDBPath) {
203 ++n;
204 }
205 if (options.fTemporaryDB) {
206 ++n;
207 }
208 if (options.fInMemoryDB) {
209 ++n;
210 }
211 Require (n == 1); // exactly one of fDBPath, fTemporaryDB, fInMemoryDB must be provided
212 }
213 if (options.fDBPath) {
214 uriArg = options.fDBPath->generic_string ();
215 if (uriArg[0] == ':') {
216 uriArg = "./" + uriArg; // sqlite docs warn to do this, to avoid issues with :memory or other extensions
217 }
218 }
219 if (options.fTemporaryDB) {
220 uriArg = string{};
221 // According to https://sqlite.org/inmemorydb.html, temporary DBs appear to require empty name
222 // @todo MAYBE fix to find a way to do named temporary DB? - or adjust API so no string name provided.
223 Require (not options.fTemporaryDB->empty ());
224 }
225 if (options.fInMemoryDB) {
226 // Not super clear why SQLITE_OPEN_URI needed, but the example in docs uses URI, and tracing through the sqlite open code
227 // it appears to require a URI format, but not really documented as near as I can tell...--LGP 2025-03-31
228 //
229 // NOTE -https://www.sqlite.org/sharedcache.html#dontuse says DONT USE SHAREDCACHE but not sure how todo shared memory DB
230 // without it? And it DOES tend to produce alot of spurrious SQLITE_BUSY errors - not sure what todo --LGP 2025-05-06
231 //
232 flags |= SQLITE_OPEN_MEMORY;
233 flags |= SQLITE_OPEN_URI;
234 flags |= SQLITE_OPEN_SHAREDCACHE;
235 Require (not options.fReadOnly);
236 Require (options.fCreateDBPathIfDoesNotExist);
237 uriArg = options.fInMemoryDB->AsNarrowSDKString (); // often empty string
238 if (uriArg.empty ()) {
239 uriArg = ":memory";
240 }
241 else {
242 u8string safeCharURI = IO::Network::UniformResourceIdentification::PCTEncode (u8string{uriArg.begin (), uriArg.end ()}, {});
243 uriArg = "file:" + string{safeCharURI.begin (), safeCharURI.end ()} + "?mode=memory&cache=shared";
244 }
245 // For now, it appears we ALWAYS create memory DBS when opening (so cannot find a way to open shared) - so always set created flag
246 }
247
248 int e;
249 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, flags, options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_CANTOPEN) {
250 if (options.fCreateDBPathIfDoesNotExist) {
251 if (fDB_ != nullptr) {
252 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
253 fDB_ = nullptr;
254 }
255 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, SQLITE_OPEN_CREATE | flags,
256 options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_OK) {
257 ; // if???
258 }
259 }
260 }
261 if (e != SQLITE_OK) [[unlikely]] {
262 [[maybe_unused]] auto&& cleanup = Finally ([this] () noexcept {
263 if (fDB_ != nullptr) {
264 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
265 }
266 });
267 ThrowSQLiteError_ (e, fDB_);
268 }
269 SetBusyTimeout (options.fBusyTimeout.value_or (Options::kBusyTimeout_Default));
270 if (options.fJournalMode) {
271 SetJournalMode (*options.fJournalMode);
272 }
273 EnsureNotNull (fDB_);
274 }
275 ~Rep_ ()
276 {
277 AssertNotNull (fDB_);
278 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
279 }
280 virtual shared_ptr<const EngineProperties> GetEngineProperties () const override
281 {
282 struct MyEngineProperties_ final : EngineProperties {
283 virtual String GetEngineName () const override
284 {
285 return "SQLite"sv;
286 }
287 virtual String GetSQL (NonStandardSQL n) const override
288 {
289 switch (n) {
290 case NonStandardSQL::eDoesTableExist:
291 return "SELECT name FROM sqlite_master WHERE type='table' AND name="_k + SQL::EngineProperties::kDoesTableExistParameterName;
292 }
294 return String{};
295 }
296 virtual bool RequireStatementResetAfterModifyingStatmentToCompleteTransaction () const override
297 {
298 return true;
299 }
300 virtual bool SupportsNestedTransactions () const override
301 {
302 return false;
303 }
304 };
305 static const shared_ptr<const EngineProperties> kProps_ = make_shared<const MyEngineProperties_> ();
306 return kProps_;
307 }
308 virtual SQL::Statement mkStatement (const String& sql) override
309 {
310 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
311 return Database::SQL::SQLite::Statement{conn, sql};
312 }
313 virtual SQL::Transaction mkTransaction () override
314 {
315 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
316 return Database::SQL::SQLite::Transaction{conn};
317 }
318 virtual void Exec (const String& sql) override
319 {
321 int e = ::sqlite3_exec (fDB_, sql.AsUTF8<string> ().c_str (), nullptr, nullptr, nullptr);
322 if (e != SQLITE_OK) [[unlikely]] {
323 ThrowSQLiteErrorIfNotOK_ (e, fDB_);
324 }
325 }
326 virtual ::sqlite3* Peek () override
327 {
328 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{*this}; // not super helpful, but could catch errors - reason not very helpful is we lose lock long before we stop using ptr
329 return fDB_;
330 }
331 virtual Duration GetBusyTimeout () const override
332 {
334 optional<int> d;
335 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
336 Assert (argc == 1);
337 Assert (::strcmp (azColName[0], "timeout") == 0);
338 int val = ::atoi (argv[0]);
339 Assert (val >= 0);
340 d = val;
341 return SQLITE_OK;
342 }};
343 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma busy_timeout;", callback.GetStaticFunction (), callback.GetData (), nullptr), fDB_);
344 Assert (d);
345 return Duration{double (*d) / 1000.0};
346 }
347 virtual void SetBusyTimeout (const Duration& timeout) override
348 {
350 ThrowSQLiteErrorIfNotOK_ (::sqlite3_busy_timeout (fDB_, (int)(timeout.As<float> () * 1000)), fDB_);
351 }
352 virtual JournalModeType GetJournalMode () const override
353 {
354 optional<string> d;
355 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
356 Assert (argc == 1);
357 Assert (::strcmp (azColName[0], "journal_mode") == 0);
358 d = argv[0];
359 return SQLITE_OK;
360 }};
361 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode;", callback.GetStaticFunction (), callback.GetData (), nullptr), fDB_);
362 Assert (d);
363 if (d == "delete"sv) {
364 return JournalModeType::eDelete;
365 }
366 if (d == "truncate"sv) {
367 return JournalModeType::eTruncate;
368 }
369 if (d == "persist"sv) {
370 return JournalModeType::ePersist;
371 }
372 if (d == "memory"sv) {
373 return JournalModeType::eMemory;
374 }
375 if (d == "wal"sv) {
376 return JournalModeType::eWAL;
377 }
378 if (d == "wal2"sv) {
379 return JournalModeType::eWAL2;
380 }
381 if (d == "off"sv) {
382 return JournalModeType::eOff;
383 }
385 return JournalModeType::eDelete;
386 }
387 virtual void SetJournalMode (JournalModeType journalMode) override
388 {
390 switch (journalMode) {
391 case JournalModeType::eDelete:
392 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'delete';", nullptr, 0, nullptr), fDB_);
393 break;
394 case JournalModeType::eTruncate:
395 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'truncate';", nullptr, 0, nullptr), fDB_);
396 break;
397 case JournalModeType::ePersist:
398 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'persist';", nullptr, 0, nullptr), fDB_);
399 break;
400 case JournalModeType::eMemory:
401 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'memory';", nullptr, 0, nullptr), fDB_);
402 break;
403 case JournalModeType::eWAL:
404 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'wal';", nullptr, 0, nullptr), fDB_);
405 break;
406 case JournalModeType::eWAL2:
407 if (GetJournalMode () == JournalModeType::eWAL) {
408 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'delete';", nullptr, 0, nullptr), fDB_);
409 }
410 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'wal2';", nullptr, 0, nullptr), fDB_);
411 break;
412 case JournalModeType::eOff:
413 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'off';", nullptr, 0, nullptr), fDB_);
414 break;
415 }
416 }
417
418 ::sqlite3* fDB_{};
419 };
420}
421
422/*
423 ********************************************************************************
424 *********************** SQL::SQLite::Connection::Ptr ***************************
425 ********************************************************************************
426 */
427SQL::SQLite::Connection::Ptr::Ptr (const shared_ptr<IRep>& src)
428 : inherited{src}
429 , busyTimeout{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
430 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
431 RequireNotNull (thisObj->operator->());
432 return thisObj->operator->()->GetBusyTimeout ();
433 },
434 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, auto timeout) {
435 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
436 RequireNotNull (thisObj->operator->());
437 thisObj->operator->()->SetBusyTimeout (timeout);
438 }}
439 , journalMode{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
440 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
441 RequireNotNull (thisObj->operator->());
442 return thisObj->operator->()->GetJournalMode ();
443 },
444 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, auto journalMode) {
445 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
446 RequireNotNull (thisObj->operator->());
447 thisObj->operator->()->SetJournalMode (journalMode);
448 }}
449{
450#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
451 if (src != nullptr) {
452 fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (src->fAssertExternallySynchronizedMutex.GetSharedContext ());
453 }
454#endif
455}
456
457/*
458 ********************************************************************************
459 ************************** SQL::SQLite::Connection *****************************
460 ********************************************************************************
461 */
462auto SQL::SQLite::Connection::New (const Options& options) -> Ptr
463{
464 return Ptr{make_shared<Rep_> (options)};
465}
466
467/*
468 ********************************************************************************
469 ******************************* SQLite::Statement ******************************
470 ********************************************************************************
471 */
472struct Statement::MyRep_ : IRep {
473 MyRep_ (const Connection::Ptr& db, const String& query)
474 : fConnectionPtr_{db}
475 {
476#if USE_NOISY_TRACE_IN_THIS_MODULE_
477 TraceContextBumper ctx{"SQLite::Statement::MyRep_::CTOR",
478 Stroika_Foundation_Debug_OptionalizeTraceArgs ("db={}, query='{}'"_f, db.Peek (), query)};
479#endif
480 RequireNotNull (db);
481 RequireNotNull (db->Peek ());
482#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
483 _fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (
484 fConnectionPtr_.fAssertExternallySynchronizedMutex.GetSharedContext ());
485#endif
486 string queryUTF8 = query.AsUTF8<string> ();
487 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
488 const char* pzTail = nullptr;
489 ThrowSQLiteErrorIfNotOK_ (::sqlite3_prepare_v2 (db->Peek (), queryUTF8.c_str (), -1, &fStatementObj_, &pzTail), db->Peek ());
490 Assert (pzTail != nullptr);
491 if (*pzTail != '\0') {
492 // @todo possibly should allow 0 or string of whitespace and ignore that too? -- LGP 2021-04-29
493 Throw (Exception{"Unexpected text after query"sv});
494 }
495 AssertNotNull (fStatementObj_);
496 unsigned int colCount = static_cast<unsigned int> (::sqlite3_column_count (fStatementObj_));
497 for (unsigned int i = 0; i < colCount; ++i) {
498 const char* colTypeUTF8 = ::sqlite3_column_decltype (fStatementObj_, i);
499 fColumns_.push_back (ColumnDescription{String::FromUTF8 (::sqlite3_column_name (fStatementObj_, i)),
500 (colTypeUTF8 == nullptr) ? optional<String>{} : String::FromUTF8 (colTypeUTF8)});
501#if USE_NOISY_TRACE_IN_THIS_MODULE_
502 DbgTrace (L"sqlite3_column_decltype(i) = %s", ::sqlite3_column_decltype (fStatementObj_, i) == nullptr
503 ? L"{nullptr}"
504 : String::FromUTF8 (::sqlite3_column_decltype (fStatementObj_, i)).c_str ());
505#endif
506 }
507
508 // Default setting (not documented, but I assume) is null
509 unsigned int paramCount = static_cast<unsigned int> (::sqlite3_bind_parameter_count (fStatementObj_));
510 for (unsigned int i = 1; i <= paramCount; ++i) {
511 const char* tmp = ::sqlite3_bind_parameter_name (fStatementObj_, i); // can be null
512 fParameters_ += ParameterDescription{tmp == nullptr ? optional<String>{} : String::FromUTF8 (tmp), nullptr};
513 }
514 }
515 ~MyRep_ ()
516 {
517 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
518 AssertNotNull (fStatementObj_);
519 (void)::sqlite3_finalize (fStatementObj_); // ignore result - errors indicate error on last evaluation of prepared statement, not on deletion of it
520 }
521 virtual String GetSQL (WhichSQLFlag whichSQL) const override
522 {
523 AssertExternallySynchronizedMutex::ReadContext declareContext{_fAssertExternallySynchronizedMutex};
524 switch (whichSQL) {
525 case WhichSQLFlag::eOriginal:
526 return String::FromUTF8 (::sqlite3_sql (fStatementObj_));
527 case WhichSQLFlag::eExpanded: {
528 auto tmp = ::sqlite3_expanded_sql (fStatementObj_);
529 if (tmp != nullptr) {
530 String r = String::FromUTF8 (tmp);
531 ::sqlite3_free (tmp);
532 return r;
533 }
534 throw bad_alloc{};
535 }
536 case WhichSQLFlag::eNormalized:
537 if constexpr (CompiledOptions::kThe.ENABLE_NORMALIZE) {
538// This ifdef should NOT be needed (because if constexpr should prevent it from being evaluated), but
539// that doesn't appear to work on MSVC 2k19 - and for now - more of a PITA than its worth for bug define
540#ifdef SQLITE_ENABLE_NORMALIZE
541 return String::FromUTF8 (::sqlite3_normalized_sql (fStatementObj_));
542#endif
543 }
545 return String{};
546 default:
548 return String{};
549 }
550 }
551 virtual Sequence<ColumnDescription> GetColumns () const override
552 {
553 AssertExternallySynchronizedMutex::ReadContext declareContext{_fAssertExternallySynchronizedMutex};
554 return Sequence<ColumnDescription>{fColumns_};
555 };
556 virtual Sequence<ParameterDescription> GetParameters () const override
557 {
558 AssertExternallySynchronizedMutex::ReadContext declareContext{_fAssertExternallySynchronizedMutex};
559 return fParameters_;
560 };
561 virtual void Bind () override
562 {
563 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
564 ThrowSQLiteErrorIfNotOK_ (::sqlite3_clear_bindings (fStatementObj_), fConnectionPtr_->Peek ());
565 }
566 virtual void Bind (unsigned int parameterIndex, const VariantValue& v) override
567 {
568 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
569 fParameters_ (parameterIndex).fValue = v;
570 switch (v.GetType ()) {
571 case VariantValue::eDate:
572 case VariantValue::eDateTime:
573 case VariantValue::eString: {
574 string u = v.As<String> ().AsUTF8<string> ();
575 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fStatementObj_, parameterIndex + 1, u.c_str (), static_cast<int> (u.length ()), SQLITE_TRANSIENT),
576 fConnectionPtr_->Peek ());
577 } break;
578 case VariantValue::eBoolean:
579 case VariantValue::eInteger:
580 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_int64 (fStatementObj_, parameterIndex + 1, v.As<sqlite3_int64> ()), fConnectionPtr_->Peek ());
581 break;
582 case VariantValue::eFloat:
583 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_double (fStatementObj_, parameterIndex + 1, v.As<double> ()), fConnectionPtr_->Peek ());
584 break;
585 case VariantValue::eBLOB: {
586 Memory::BLOB b = v.As<Memory::BLOB> ();
587 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_blob64 (fStatementObj_, parameterIndex + 1, b.begin (), b.size (), SQLITE_TRANSIENT),
588 fConnectionPtr_->Peek ());
589 } break;
590 case VariantValue::eNull:
591 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_null (fStatementObj_, parameterIndex + 1), fConnectionPtr_->Peek ());
592 break;
593 default:
594 AssertNotImplemented (); // add more types
595 break;
596 }
597 }
598 virtual void Bind (const String& parameterName, const VariantValue& v) override
599 {
600 Require (not parameterName.empty ());
601 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
602 String pn = parameterName;
603 if (pn[0] != ':') {
604 pn = ":"_k + pn;
605 }
606 for (unsigned int i = 0; i < fParameters_.length (); ++i) {
607 if (fParameters_[i].fName == pn) {
608 Bind (i, v);
609 return;
610 }
611 }
612 DbgTrace ("Statement::Bind: Parameter '{}' not found in list {}"_f, parameterName,
613 fParameters_.Map<Traversal::Iterable<String>> ([] (const auto& i) { return i.fName; }));
614 RequireNotReached (); // invalid parameter name provided
615 }
616 virtual void Reset () override
617 {
618#if USE_NOISY_TRACE_IN_THIS_MODULE_
619 TraceContextBumper ctx{"SQLite::Statement::MyRep_::Statement::Reset"};
620#endif
621 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
622 AssertNotNull (fStatementObj_);
623 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fStatementObj_), fConnectionPtr_->Peek ());
624 }
625 virtual optional<Row> GetNextRow () override
626 {
627#if USE_NOISY_TRACE_IN_THIS_MODULE_
628 TraceContextBumper ctx{"SQLite::Statement::MyRep_::Statement::GetNextRow"};
629#endif
630 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
631 // @todo MAYBE redo with https://www.sqlite.org/c3ref/value.html
632 AssertNotNull (fStatementObj_);
633 int rc = ::sqlite3_step (fStatementObj_);
634 switch (rc) {
635 case SQLITE_OK: {
636 AssertNotReached (); // I think this should never happen with this API
637 } break;
638 case SQLITE_ROW: {
639 Row row;
640 for (unsigned int i = 0; i < fColumns_.size (); ++i) {
641 VariantValue v;
642 // The actual returned type may not be the same as the DECLARED type (for example if the column is declared variant)
643 switch (::sqlite3_column_type (fStatementObj_, i)) {
644 case SQLITE_INTEGER: {
645 v = VariantValue{::sqlite3_column_int (fStatementObj_, i)};
646 } break;
647 case SQLITE_FLOAT: {
648 v = VariantValue{::sqlite3_column_double (fStatementObj_, i)};
649 } break;
650 case SQLITE_BLOB: {
651 const byte* data = reinterpret_cast<const byte*> (::sqlite3_column_blob (fStatementObj_, i));
652 size_t byteCount = static_cast<size_t> (::sqlite3_column_bytes (fStatementObj_, i));
653 v = VariantValue{Memory::BLOB{data, data + byteCount}};
654 } break;
655 case SQLITE_NULL: {
656 // default to null value
657 } break;
658 case SQLITE_TEXT: {
659 // @todo redo as sqlite3_column_text16, but doesn't help unix case? Maybe just iff sizeof(wchart_t)==2?
660 AssertNotNull (::sqlite3_column_text (fStatementObj_, i));
661 v = VariantValue{String::FromUTF8 (reinterpret_cast<const char*> (::sqlite3_column_text (fStatementObj_, i)))};
662 } break;
663 default: {
665 } break;
666 }
667 row.Add (fColumns_[i].fName, v);
668 }
669 return row;
670 } break;
671 case SQLITE_DONE: {
672 return nullopt;
673 } break;
674 }
675 ThrowSQLiteError_ (rc, fConnectionPtr_->Peek ());
676 }
677
678 Connection::Ptr fConnectionPtr_;
679 ::sqlite3_stmt* fStatementObj_;
680 vector<ColumnDescription> fColumns_;
682};
683
684Statement::Statement (const Connection::Ptr& db, const String& query)
685 : inherited{make_unique<MyRep_> (db, query)}
686{
687}
688
689/*
690 ********************************************************************************
691 ******************************* SQLite::Transaction ****************************
692 ********************************************************************************
693 */
694struct Transaction::MyRep_ : public SQL::Transaction::IRep {
695 MyRep_ (const Connection::Ptr& db, Flag f)
696 : fConnectionPtr_{db}
697 {
698 switch (f) {
699 case Flag::eDeferred:
700 db->Exec ("BEGIN DEFERRED TRANSACTION;"sv);
701 break;
702 case Flag::eExclusive:
703 db->Exec ("BEGIN EXCLUSIVE TRANSACTION;"sv);
704 break;
705 case Flag::eImmediate:
706 db->Exec ("BEGIN IMMEDIATE TRANSACTION;"sv);
707 break;
708 default:
710 }
711 }
712 virtual void Commit () override
713 {
714 Require (not fCompleted_);
715 fCompleted_ = true;
716 fConnectionPtr_->Exec ("COMMIT TRANSACTION;"sv);
717 }
718 virtual void Rollback () override
719 {
720 Require (not fCompleted_);
721 fCompleted_ = true;
722 fConnectionPtr_->Exec ("ROLLBACK TRANSACTION;"sv);
723 }
724 virtual Disposition GetDisposition () const override
725 {
726 // @todo record more info so we can report finer grained status ; try/catch in rollback/commit and dbgtraces
727 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
728 }
729 Connection::Ptr fConnectionPtr_;
730 bool fCompleted_{false};
731};
732Transaction::Transaction (const Connection::Ptr& db, Flag f)
733 : inherited{make_unique<MyRep_> (db, f)}
734{
735}
736#endif
#define AssertNotNull(p)
Definition Assertions.h:333
#define EnsureNotNull(p)
Definition Assertions.h:340
#define AssertNotImplemented()
Definition Assertions.h:401
#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
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A generalization of a vector: a container whose elements are keyed by the natural numbers.
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...
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...
nonvirtual const byte * begin() const
Definition BLOB.inl:258
nonvirtual size_t size() const
Definition BLOB.inl:286
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
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