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