Stroika Library 3.0d18
 
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 flags |= SQLITE_OPEN_MEMORY;
229 flags |= SQLITE_OPEN_URI;
230 flags |= SQLITE_OPEN_SHAREDCACHE;
231 Require (not options.fReadOnly);
232 Require (options.fCreateDBPathIfDoesNotExist);
233 uriArg = options.fInMemoryDB->AsNarrowSDKString (); // often empty string
234 if (uriArg.empty ()) {
235 uriArg = ":memory";
236 }
237 else {
238 u8string safeCharURI = IO::Network::UniformResourceIdentification::PCTEncode (u8string{uriArg.begin (), uriArg.end ()}, {});
239 uriArg = "file:" + string{safeCharURI.begin (), safeCharURI.end ()} + "?mode=memory&cache=shared";
240 }
241 // For now, it appears we ALWAYS create memory DBS when opening (so cannot find a way to open shared) - so always set created flag
242 }
243
244 int e;
245 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, flags, options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_CANTOPEN) {
246 if (options.fCreateDBPathIfDoesNotExist) {
247 if (fDB_ != nullptr) {
248 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
249 fDB_ = nullptr;
250 }
251 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, SQLITE_OPEN_CREATE | flags,
252 options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_OK) {
253 ; // if???
254 }
255 }
256 }
257 if (e != SQLITE_OK) [[unlikely]] {
258 [[maybe_unused]] auto&& cleanup = Finally ([this] () noexcept {
259 if (fDB_ != nullptr) {
260 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
261 }
262 });
263 ThrowSQLiteError_ (e, fDB_);
264 }
265 if (options.fBusyTimeout) {
266 SetBusyTimeout (*options.fBusyTimeout);
267 }
268 if (options.fJournalMode) {
269 SetJournalMode (*options.fJournalMode);
270 }
271 EnsureNotNull (fDB_);
272 }
273 ~Rep_ ()
274 {
275 AssertNotNull (fDB_);
276 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
277 }
278 virtual shared_ptr<const EngineProperties> GetEngineProperties () const override
279 {
280 struct MyEngineProperties_ final : EngineProperties {
281 virtual String GetEngineName () const override
282 {
283 return "SQLite"sv;
284 }
285 virtual String GetSQL (NonStandardSQL n) const override
286 {
287 switch (n) {
288 case NonStandardSQL::eDoesTableExist:
289 return "SELECT name FROM sqlite_master WHERE type='table' AND name="_k + SQL::EngineProperties::kDoesTableExistParameterName;
290 }
292 return String{};
293 }
294 virtual bool RequireStatementResetAfterModifyingStatmentToCompleteTransaction () const override
295 {
296 return true;
297 }
298 virtual bool SupportsNestedTransactions () const override
299 {
300 return false;
301 }
302 };
303 static const shared_ptr<const EngineProperties> kProps_ = make_shared<const MyEngineProperties_> ();
304 return kProps_;
305 }
306 virtual SQL::Statement mkStatement (const String& sql) override
307 {
308 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
309 return Database::SQL::SQLite::Statement{conn, sql};
310 }
311 virtual SQL::Transaction mkTransaction () override
312 {
313 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
314 return Database::SQL::SQLite::Transaction{conn};
315 }
316 virtual void Exec (const String& sql) override
317 {
319 int e = ::sqlite3_exec (fDB_, sql.AsUTF8<string> ().c_str (), nullptr, nullptr, nullptr);
320 if (e != SQLITE_OK) [[unlikely]] {
321 ThrowSQLiteErrorIfNotOK_ (e, fDB_);
322 }
323 }
324 virtual ::sqlite3* Peek () override
325 {
326 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
327 return fDB_;
328 }
329 virtual Duration GetBusyTimeout () const override
330 {
332 optional<int> d;
333 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
334 Assert (argc == 1);
335 Assert (::strcmp (azColName[0], "timeout") == 0);
336 int val = ::atoi (argv[0]);
337 Assert (val >= 0);
338 d = val;
339 return SQLITE_OK;
340 }};
341 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma busy_timeout;", callback.GetStaticFunction (), callback.GetData (), nullptr), fDB_);
342 Assert (d);
343 return Duration{double (*d) / 1000.0};
344 }
345 virtual void SetBusyTimeout (const Duration& timeout) override
346 {
348 ThrowSQLiteErrorIfNotOK_ (::sqlite3_busy_timeout (fDB_, (int)(timeout.As<float> () * 1000)), fDB_);
349 }
350 virtual JournalModeType GetJournalMode () const override
351 {
352 optional<string> d;
353 auto callback = SQLiteCallback_{[&] ([[maybe_unused]] int argc, char** argv, [[maybe_unused]] char** azColName) {
354 Assert (argc == 1);
355 Assert (::strcmp (azColName[0], "journal_mode") == 0);
356 d = argv[0];
357 return SQLITE_OK;
358 }};
359 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode;", callback.GetStaticFunction (), callback.GetData (), nullptr), fDB_);
360 Assert (d);
361 if (d == "delete"sv) {
362 return JournalModeType::eDelete;
363 }
364 if (d == "truncate"sv) {
365 return JournalModeType::eTruncate;
366 }
367 if (d == "persist"sv) {
368 return JournalModeType::ePersist;
369 }
370 if (d == "memory"sv) {
371 return JournalModeType::eMemory;
372 }
373 if (d == "wal"sv) {
374 return JournalModeType::eWAL;
375 }
376 if (d == "wal2"sv) {
377 return JournalModeType::eWAL2;
378 }
379 if (d == "off"sv) {
380 return JournalModeType::eOff;
381 }
383 return JournalModeType::eDelete;
384 }
385 virtual void SetJournalMode (JournalModeType journalMode) override
386 {
388 switch (journalMode) {
389 case JournalModeType::eDelete:
390 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'delete';", nullptr, 0, nullptr), fDB_);
391 break;
392 case JournalModeType::eTruncate:
393 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'truncate';", nullptr, 0, nullptr), fDB_);
394 break;
395 case JournalModeType::ePersist:
396 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'persist';", nullptr, 0, nullptr), fDB_);
397 break;
398 case JournalModeType::eMemory:
399 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'memory';", nullptr, 0, nullptr), fDB_);
400 break;
401 case JournalModeType::eWAL:
402 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'wal';", nullptr, 0, nullptr), fDB_);
403 break;
404 case JournalModeType::eWAL2:
405 if (GetJournalMode () == JournalModeType::eWAL) {
406 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'delete';", nullptr, 0, nullptr), fDB_);
407 }
408 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'wal2';", nullptr, 0, nullptr), fDB_);
409 break;
410 case JournalModeType::eOff:
411 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_, "pragma journal_mode = 'off';", nullptr, 0, nullptr), fDB_);
412 break;
413 }
414 }
415
416 ::sqlite3* fDB_{};
417 };
418}
419
420/*
421 ********************************************************************************
422 *********************** SQL::SQLite::Connection::Ptr ***************************
423 ********************************************************************************
424 */
425SQL::SQLite::Connection::Ptr::Ptr (const shared_ptr<IRep>& src)
426 : inherited{src}
427 , busyTimeout{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
428 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
429 RequireNotNull (thisObj->operator->());
430 return thisObj->operator->()->GetBusyTimeout ();
431 },
432 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, auto timeout) {
433 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
434 RequireNotNull (thisObj->operator->());
435 thisObj->operator->()->SetBusyTimeout (timeout);
436 }}
437 , journalMode{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) {
438 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
439 RequireNotNull (thisObj->operator->());
440 return thisObj->operator->()->GetJournalMode ();
441 },
442 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, auto journalMode) {
443 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
444 RequireNotNull (thisObj->operator->());
445 thisObj->operator->()->SetJournalMode (journalMode);
446 }}
447{
448#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
449 if (src != nullptr) {
450 fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (src->fAssertExternallySynchronizedMutex.GetSharedContext ());
451 }
452#endif
453}
454
455/*
456 ********************************************************************************
457 ************************** SQL::SQLite::Connection *****************************
458 ********************************************************************************
459 */
460auto SQL::SQLite::Connection::New (const Options& options) -> Ptr
461{
462 return Ptr{make_shared<Rep_> (options)};
463}
464
465/*
466 ********************************************************************************
467 ******************************* SQLite::Statement ******************************
468 ********************************************************************************
469 */
470struct Statement::MyRep_ : IRep {
471 MyRep_ (const Connection::Ptr& db, const String& query)
472 : fConnectionPtr_{db}
473 {
474#if USE_NOISY_TRACE_IN_THIS_MODULE_
475 TraceContextBumper ctx{L"SQLite::Statement::MyRep_::CTOR",
476 Stroika_Foundation_Debug_OptionalizeTraceArgs (L"db=%p, query='%s'", db.Peek (), query.As<wstring> ().c_str ())};
477#endif
478 RequireNotNull (db);
479 RequireNotNull (db->Peek ());
480#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
481 _fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (
482 fConnectionPtr_.fAssertExternallySynchronizedMutex.GetSharedContext ());
483#endif
484 string queryUTF8 = query.AsUTF8<string> ();
485 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
486 const char* pzTail = nullptr;
487 ThrowSQLiteErrorIfNotOK_ (::sqlite3_prepare_v2 (db->Peek (), queryUTF8.c_str (), -1, &fStatementObj_, &pzTail), db->Peek ());
488 Assert (pzTail != nullptr);
489 if (*pzTail != '\0') {
490 // @todo possibly should allow 0 or string of whitespace and ignore that too? -- LGP 2021-04-29
491 Throw (Exception{"Unexpected text after query"sv});
492 }
493 AssertNotNull (fStatementObj_);
494 unsigned int colCount = static_cast<unsigned int> (::sqlite3_column_count (fStatementObj_));
495 for (unsigned int i = 0; i < colCount; ++i) {
496 const char* colTypeUTF8 = ::sqlite3_column_decltype (fStatementObj_, i);
497 fColumns_.push_back (ColumnDescription{String::FromUTF8 (::sqlite3_column_name (fStatementObj_, i)),
498 (colTypeUTF8 == nullptr) ? optional<String>{} : String::FromUTF8 (colTypeUTF8)});
499#if USE_NOISY_TRACE_IN_THIS_MODULE_
500 DbgTrace (L"sqlite3_column_decltype(i) = %s", ::sqlite3_column_decltype (fStatementObj_, i) == nullptr
501 ? L"{nullptr}"
502 : String::FromUTF8 (::sqlite3_column_decltype (fStatementObj_, i)).c_str ());
503#endif
504 }
505
506 // Default setting (not documented, but I assume) is null
507 unsigned int paramCount = static_cast<unsigned int> (::sqlite3_bind_parameter_count (fStatementObj_));
508 for (unsigned int i = 1; i <= paramCount; ++i) {
509 const char* tmp = ::sqlite3_bind_parameter_name (fStatementObj_, i); // can be null
510 fParameters_ += ParameterDescription{tmp == nullptr ? optional<String>{} : String::FromUTF8 (tmp), nullptr};
511 }
512 }
513 ~MyRep_ ()
514 {
515 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
516 AssertNotNull (fStatementObj_);
517 (void)::sqlite3_finalize (fStatementObj_);
518 }
519 virtual String GetSQL (WhichSQLFlag whichSQL) const override
520 {
521 AssertExternallySynchronizedMutex::ReadContext declareContext{_fAssertExternallySynchronizedMutex};
522 switch (whichSQL) {
523 case WhichSQLFlag::eOriginal:
524 return String::FromUTF8 (::sqlite3_sql (fStatementObj_));
525 case WhichSQLFlag::eExpanded: {
526 auto tmp = ::sqlite3_expanded_sql (fStatementObj_);
527 if (tmp != nullptr) {
528 String r = String::FromUTF8 (tmp);
529 ::sqlite3_free (tmp);
530 return r;
531 }
532 throw bad_alloc{};
533 }
534 case WhichSQLFlag::eNormalized:
535 if constexpr (CompiledOptions::kThe.ENABLE_NORMALIZE) {
536// This ifdef should NOT be needed (because if constexpr should prevent it from being evaluated), but
537// that doesn't appear to work on MSVC 2k19 - and for now - more of a PITA than its worth for bug define
538#ifdef SQLITE_ENABLE_NORMALIZE
539 return String::FromUTF8 (::sqlite3_normalized_sql (fStatementObj_));
540#endif
541 }
543 return String{};
544 default:
546 return String{};
547 }
548 }
549 virtual Sequence<ColumnDescription> GetColumns () const override
550 {
551 AssertExternallySynchronizedMutex::ReadContext declareContext{_fAssertExternallySynchronizedMutex};
552 return Sequence<ColumnDescription>{fColumns_};
553 };
554 virtual Sequence<ParameterDescription> GetParameters () const override
555 {
556 AssertExternallySynchronizedMutex::ReadContext declareContext{_fAssertExternallySynchronizedMutex};
557 return fParameters_;
558 };
559 virtual void Bind () override
560 {
561 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
562 ThrowSQLiteErrorIfNotOK_ (::sqlite3_clear_bindings (fStatementObj_), fConnectionPtr_->Peek ());
563 }
564 virtual void Bind (unsigned int parameterIndex, const VariantValue& v) override
565 {
566 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
567 fParameters_ (parameterIndex).fValue = v;
568 switch (v.GetType ()) {
569 case VariantValue::eDate:
570 case VariantValue::eDateTime:
571 case VariantValue::eString: {
572 string u = v.As<String> ().AsUTF8<string> ();
573 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_text (fStatementObj_, parameterIndex + 1, u.c_str (), static_cast<int> (u.length ()), SQLITE_TRANSIENT),
574 fConnectionPtr_->Peek ());
575 } break;
576 case VariantValue::eBoolean:
577 case VariantValue::eInteger:
578 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_int64 (fStatementObj_, parameterIndex + 1, v.As<sqlite3_int64> ()), fConnectionPtr_->Peek ());
579 break;
580 case VariantValue::eFloat:
581 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_double (fStatementObj_, parameterIndex + 1, v.As<double> ()), fConnectionPtr_->Peek ());
582 break;
583 case VariantValue::eBLOB: {
584 Memory::BLOB b = v.As<Memory::BLOB> ();
585 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_blob64 (fStatementObj_, parameterIndex + 1, b.begin (), b.size (), SQLITE_TRANSIENT),
586 fConnectionPtr_->Peek ());
587 } break;
588 case VariantValue::eNull:
589 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_null (fStatementObj_, parameterIndex + 1), fConnectionPtr_->Peek ());
590 break;
591 default:
592 AssertNotImplemented (); // add more types
593 break;
594 }
595 }
596 virtual void Bind (const String& parameterName, const VariantValue& v) override
597 {
598 Require (not parameterName.empty ());
599 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
600 String pn = parameterName;
601 if (pn[0] != ':') {
602 pn = ":"_k + pn;
603 }
604 for (unsigned int i = 0; i < fParameters_.length (); ++i) {
605 if (fParameters_[i].fName == pn) {
606 Bind (i, v);
607 return;
608 }
609 }
610 DbgTrace ("Statement::Bind: Parameter '{}' not found in list {}"_f, parameterName,
611 fParameters_.Map<Traversal::Iterable<String>> ([] (const auto& i) { return i.fName; }));
612 RequireNotReached (); // invalid paramter name provided
613 }
614 virtual void Reset () override
615 {
616#if USE_NOISY_TRACE_IN_THIS_MODULE_
617 TraceContextBumper ctx{"SQLite::Statement::MyRep_::Statement::Reset"};
618#endif
619 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
620 AssertNotNull (fStatementObj_);
621 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fStatementObj_), fConnectionPtr_->Peek ());
622 }
623 virtual optional<Row> GetNextRow () override
624 {
625#if USE_NOISY_TRACE_IN_THIS_MODULE_
626 TraceContextBumper ctx{"SQLite::Statement::MyRep_::Statement::GetNextRow"};
627#endif
628 AssertExternallySynchronizedMutex::WriteContext declareContext{_fAssertExternallySynchronizedMutex};
629 // @todo MAYBE redo with https://www.sqlite.org/c3ref/value.html
630 AssertNotNull (fStatementObj_);
631 int rc = ::sqlite3_step (fStatementObj_);
632 switch (rc) {
633 case SQLITE_OK: {
634 AssertNotReached (); // I think this should never happen with this API
635 } break;
636 case SQLITE_ROW: {
637 Row row;
638 for (unsigned int i = 0; i < fColumns_.size (); ++i) {
639 VariantValue v;
640 // The actual returned type may not be the same as the DECLARED type (for example if the column is declared variant)
641 switch (::sqlite3_column_type (fStatementObj_, i)) {
642 case SQLITE_INTEGER: {
643 v = VariantValue{::sqlite3_column_int (fStatementObj_, i)};
644 } break;
645 case SQLITE_FLOAT: {
646 v = VariantValue{::sqlite3_column_double (fStatementObj_, i)};
647 } break;
648 case SQLITE_BLOB: {
649 const byte* data = reinterpret_cast<const byte*> (::sqlite3_column_blob (fStatementObj_, i));
650 size_t byteCount = static_cast<size_t> (::sqlite3_column_bytes (fStatementObj_, i));
651 v = VariantValue{Memory::BLOB{data, data + byteCount}};
652 } break;
653 case SQLITE_NULL: {
654 // default to null value
655 } break;
656 case SQLITE_TEXT: {
657 // @todo redo as sqlite3_column_text16, but doesn't help unix case? Maybe just iff sizeof(wchart_t)==2?
658 AssertNotNull (::sqlite3_column_text (fStatementObj_, i));
659 v = VariantValue{String::FromUTF8 (reinterpret_cast<const char*> (::sqlite3_column_text (fStatementObj_, i)))};
660 } break;
661 default: {
663 } break;
664 }
665 row.Add (fColumns_[i].fName, v);
666 }
667 return row;
668 } break;
669 case SQLITE_DONE: {
670 return nullopt;
671 } break;
672 }
673 ThrowSQLiteError_ (rc, fConnectionPtr_->Peek ());
674 }
675
676 Connection::Ptr fConnectionPtr_;
677 ::sqlite3_stmt* fStatementObj_;
678 vector<ColumnDescription> fColumns_;
680};
681
682Statement::Statement (const Connection::Ptr& db, const String& query)
683 : inherited{make_unique<MyRep_> (db, query)}
684{
685}
686
687/*
688 ********************************************************************************
689 ******************************* SQLite::Transaction ****************************
690 ********************************************************************************
691 */
692struct Transaction::MyRep_ : public SQL::Transaction::IRep {
693 MyRep_ (const Connection::Ptr& db, Flag f)
694 : fConnectionPtr_{db}
695 {
696 switch (f) {
697 case Flag::eDeferred:
698 db->Exec ("BEGIN DEFERRED TRANSACTION;"sv);
699 break;
700 case Flag::eExclusive:
701 db->Exec ("BEGIN EXCLUSIVE TRANSACTION;"sv);
702 break;
703 case Flag::eImmediate:
704 db->Exec ("BEGIN IMMEDIATE TRANSACTION;"sv);
705 break;
706 default:
708 }
709 }
710 virtual void Commit () override
711 {
712 Require (not fCompleted_);
713 fCompleted_ = true;
714 fConnectionPtr_->Exec ("COMMIT TRANSACTION;"sv);
715 }
716 virtual void Rollback () override
717 {
718 Require (not fCompleted_);
719 fCompleted_ = true;
720 fConnectionPtr_->Exec ("ROLLBACK TRANSACTION;"sv);
721 }
722 virtual Disposition GetDisposition () const override
723 {
724 // @todo record more info so we can report finer grained status ; try/catch in rollback/commit and dbgtraces
725 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
726 }
727 Connection::Ptr fConnectionPtr_;
728 bool fCompleted_{false};
729};
730Transaction::Transaction (const Connection::Ptr& db, Flag f)
731 : inherited{make_unique<MyRep_> (db, f)}
732{
733}
734#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:253
nonvirtual size_t size() const
Definition BLOB.inl:281
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