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