Stroika Library 3.0d25
 
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
11#include "Stroika/Foundation/Execution/Exceptions.h"
14
15#include "SQLite.h"
16
17using namespace Stroika::Foundation;
18
19using namespace Characters;
20using namespace Debug;
21using namespace Database;
22using namespace Database::SQL::SQLite;
23using namespace Execution;
24using namespace Time;
25
26// Comment this in to turn on aggressive noisy DbgTrace in this module
27//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
28
30using Memory::MakeSharedPtr;
31
32#if qStroika_HasComponent_sqlite
33namespace {
34 struct ModuleShutdown_ {
35 ~ModuleShutdown_ ()
36 {
37 Verify (::sqlite3_shutdown () == SQLITE_OK); // mostly pointless but avoids memory leak complaints
38 }
39 } sModuleShutdown_;
40 [[noreturn]] void ThrowSQLiteError_ (int errCode, sqlite3* sqliteConnection)
41 {
42 Require (errCode != SQLITE_OK);
43 optional<String> errMsgDetails;
44 if (sqliteConnection != nullptr) {
45 errMsgDetails = String::FromUTF8 (::sqlite3_errmsg (sqliteConnection));
46 }
47 switch (errCode) {
48 case SQLITE_BUSY: {
49 DbgTrace ("SQLITE_BUSY"_f); // The database file is locked
50 ThrowError (errc::device_or_resource_busy);
51 } break;
52 case SQLITE_LOCKED: {
53 DbgTrace ("SQLITE_LOCKED"_f); // A table in the database is locked
54 ThrowError (errc::device_or_resource_busy);
55 } break;
56 case SQLITE_CONSTRAINT: {
57 if (errMsgDetails) {
58 Throw (Exception{"SQLITE_CONSTRAINT: {}"_f(errMsgDetails)});
59 }
60 else {
61 static const auto kEx_ = Exception{"SQLITE_CONSTRAINT"sv};
62 Throw (kEx_);
63 }
64 } break;
65 case SQLITE_TOOBIG: {
66 static const auto kEx_ = Exception{"SQLITE_TOOBIG"sv};
67 Throw (kEx_);
68 } break;
69 case SQLITE_FULL: {
70 DbgTrace ("SQLITE_FULL"_f);
71 ThrowError (errc::no_space_on_device);
72 } break;
73 case SQLITE_READONLY: {
74 static const auto kEx_ = Exception{"SQLITE_READONLY"sv};
75 Throw (kEx_);
76 } break;
77 case SQLITE_MISUSE: {
78 if (errMsgDetails) {
79 Throw (Exception{"SQLITE_MISUSE: {}"_f(errMsgDetails)});
80 }
81 else {
82 static const auto kEx_ = Exception{"SQLITE_MISUSE"sv};
83 Throw (kEx_);
84 }
85 } break;
86 case SQLITE_ERROR: {
87 if (errMsgDetails) {
88 Throw (Exception{"SQLITE_ERROR: {}"_f(errMsgDetails)});
89 }
90 else {
91 static const auto kEx_ = Exception{"SQLITE_ERROR"sv};
92 Throw (kEx_);
93 }
94 } break;
95 case SQLITE_NOMEM: {
96 DbgTrace ("SQLITE_NOMEM translated to bad_alloc"_f);
97 Throw (bad_alloc{});
98 } break;
99 }
100 if (errMsgDetails) {
101 Throw (Exception{"SQLite Error: {} (code {})"_f(errMsgDetails, errCode)});
102 }
103 else {
104 Throw (Exception{"SQLite Error: {}"_f(errCode)});
105 }
106 }
107 void ThrowSQLiteErrorIfNotOK_ (int errCode, sqlite3* sqliteConnection)
108 {
109 static_assert (SQLITE_OK == 0);
110 if (errCode != SQLITE_OK) [[unlikely]] {
111 ThrowSQLiteError_ (errCode, sqliteConnection);
112 }
113 }
114
115 /*
116 * Simple utility to be able to use lambdas with arbitrary captures more easily with sqlite c API
117 */
118 template <invocable<int, char**, char**> CB>
119 struct SQLiteCallback_ {
120 CB fCallback_;
121
122 using STATIC_FUNCTION_TYPE = int (*) (void*, int, char**, char**);
123
124 SQLiteCallback_ (CB&& cb)
125 : fCallback_{forward<CB> (cb)}
126 {
127 }
128 STATIC_FUNCTION_TYPE GetStaticFunction ()
129 {
130 return STATICFUNC_;
131 }
132 void* GetData ()
133 {
134 return this;
135 }
136
137 private:
138 static int STATICFUNC_ (void* SQLiteCallbackData, int argc, char** argv, char** azColName)
139 {
140 SQLiteCallback_* sqc = reinterpret_cast<SQLiteCallback_*> (SQLiteCallbackData);
141 return sqc->fCallback_ (argc, argv, azColName);
142 }
143 };
144}
145
146/*
147 ********************************************************************************
148 *************************** SQLite::CompiledOptions ****************************
149 ********************************************************************************
150 */
151namespace {
152 struct VerifyFlags_ {
153 VerifyFlags_ ()
154 {
155 Assert (CompiledOptions::kThe.ENABLE_NORMALIZE == !!::sqlite3_compileoption_used ("ENABLE_NORMALIZE"));
156 Assert (CompiledOptions::kThe.THREADSAFE == !!::sqlite3_compileoption_used ("THREADSAFE"));
157#if SQLITE_VERSION_NUMBER < 3038000
158 Assert (CompiledOptions::kThe.ENABLE_JSON1 == !!::sqlite3_compileoption_used ("ENABLE_JSON1"));
159#else
160 Assert (CompiledOptions::kThe.ENABLE_JSON1 == !::sqlite3_compileoption_used ("OMIT_JSON1"));
161#endif
162 }
163 } sVerifyFlags_;
164}
165
166namespace {
167 using Connection::Options;
168 struct Rep_ final : Database::SQL::SQLite::Connection::IRep, private Debug::AssertExternallySynchronizedChecker {
169 Rep_ (const Options& options)
170 {
171 TraceContextBumper ctx{"SQLite::Connection::Rep_::Rep_"};
172
173 int flags = 0;
174 // https://www.sqlite.org/threadsafe.html explains the thread-safety stuff. Not sure I have it right, but hopefully --LGP 2023-09-13
175 switch (options.fThreadingMode.value_or (Options::kDefault_ThreadingMode)) {
176 case Options::ThreadingMode::eSingleThread:
177 break;
178 case Options::ThreadingMode::eMultiThread:
179 Require (CompiledOptions::kThe.THREADSAFE);
180 Require (::sqlite3_threadsafe ());
181 flags |= SQLITE_OPEN_NOMUTEX;
182 break;
183 case Options::ThreadingMode::eSerialized:
184 Require (CompiledOptions::kThe.THREADSAFE);
185 Require (::sqlite3_threadsafe ());
186 flags |= SQLITE_OPEN_FULLMUTEX;
187 break;
188 }
189
190 if (options.fImmutable) {
191 // NYI cuz requires uri syntax
193 Require (options.fReadOnly);
194 }
195 flags |= options.fReadOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
196
197 string uriArg;
199 [[maybe_unused]] int n{};
200 if (options.fDBPath) {
201 ++n;
202 }
203 if (options.fTemporaryDB) {
204 ++n;
205 }
206 if (options.fInMemoryDB) {
207 ++n;
208 }
209 Require (n == 1); // exactly one of fDBPath, fTemporaryDB, fInMemoryDB must be provided
210 }
211 if (options.fDBPath) {
212 uriArg = options.fDBPath->generic_string ();
213 if (uriArg[0] == ':') {
214 uriArg = "./" + uriArg; // sqlite docs warn to do this, to avoid issues with :memory or other extensions
215 }
216 }
217 if (options.fTemporaryDB) {
218 uriArg = string{};
219 // According to https://sqlite.org/inmemorydb.html, temporary DBs appear to require empty name
220 // @todo MAYBE fix to find a way to do named temporary DB? - or adjust API so no string name provided.
221 Require (not options.fTemporaryDB->empty ());
222 }
223 if (options.fInMemoryDB) {
224 // Not super clear why SQLITE_OPEN_URI needed, but the example in docs uses URI, and tracing through the sqlite open code
225 // it appears to require a URI format, but not really documented as near as I can tell...--LGP 2025-03-31
226 //
227 // NOTE -https://www.sqlite.org/sharedcache.html#dontuse says DONT USE SHAREDCACHE but not sure how todo shared memory DB
228 // without it? And it DOES tend to produce alot of spurrious SQLITE_BUSY errors - not sure what todo --LGP 2025-05-06
229 //
230 flags |= SQLITE_OPEN_MEMORY;
231 flags |= SQLITE_OPEN_URI;
232 flags |= SQLITE_OPEN_SHAREDCACHE;
233 Require (not options.fReadOnly);
234 Require (options.fCreateDBPathIfDoesNotExist);
235 uriArg = options.fInMemoryDB->AsNarrowSDKString (); // often empty string
236 if (uriArg.empty ()) {
237 uriArg = ":memory";
238 }
239 else {
240 u8string safeCharURI = IO::Network::UniformResourceIdentification::PCTEncode (u8string{uriArg.begin (), uriArg.end ()}, {});
241 uriArg = "file:" + string{safeCharURI.begin (), safeCharURI.end ()} + "?mode=memory&cache=shared";
242 }
243 // For now, it appears we ALWAYS create memory DBS when opening (so cannot find a way to open shared) - so always set created flag
244 }
245
246 int e;
247 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, flags, options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_CANTOPEN) {
248 if (options.fCreateDBPathIfDoesNotExist) {
249 if (fDB_ != nullptr) {
250 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
251 fDB_ = nullptr;
252 }
253 if ((e = ::sqlite3_open_v2 (uriArg.c_str (), &fDB_, SQLITE_OPEN_CREATE | flags,
254 options.fVFS ? options.fVFS->AsNarrowSDKString ().c_str () : nullptr)) == SQLITE_OK) {
255 ; // if???
256 }
257 }
258 }
259 if (e != SQLITE_OK) [[unlikely]] {
260 [[maybe_unused]] auto&& cleanup = Finally ([this] () noexcept {
261 if (fDB_ != nullptr) {
262 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
263 }
264 });
265 ThrowSQLiteError_ (e, fDB_);
266 }
267 SetBusyTimeout (options.fBusyTimeout.value_or (Options::kBusyTimeout_Default));
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_ = MakeSharedPtr<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::AssertExternallySynchronizedChecker::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_AssertExternallySynchronizedChecker_Enabled
449 if (src != nullptr) {
450 fAssertExternallySynchronizedChecker.SetAssertExternallySynchronizedCheckerContext (src->fAssertExternallySynchronizedChecker.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{MakeSharedPtr<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{"SQLite::Statement::MyRep_::CTOR",
476 Stroika_Foundation_Debug_OptionalizeTraceArgs ("db={}, query='{}'"_f, db.Peek (), query)};
477#endif
478 RequireNotNull (db);
479 RequireNotNull (db->Peek ());
480#if qStroika_Foundation_Debug_AssertExternallySynchronizedChecker_Enabled
481 _fAssertExternallySynchronizedChecker.SetAssertExternallySynchronizedCheckerContext (
482 fConnectionPtr_.fAssertExternallySynchronizedChecker.GetSharedContext ());
483#endif
484 string queryUTF8 = query.AsUTF8<string> ();
485 AssertExternallySynchronizedChecker::WriteContext declareContext{_fAssertExternallySynchronizedChecker};
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 AssertExternallySynchronizedChecker::WriteContext declareContext{_fAssertExternallySynchronizedChecker};
516 AssertNotNull (fStatementObj_);
517 (void)::sqlite3_finalize (fStatementObj_); // ignore result - errors indicate error on last evaluation of prepared statement, not on deletion of it
518 }
519 virtual String GetSQL (WhichSQLFlag whichSQL) const override
520 {
521 AssertExternallySynchronizedChecker::ReadContext declareContext{_fAssertExternallySynchronizedChecker};
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 AssertExternallySynchronizedChecker::ReadContext declareContext{_fAssertExternallySynchronizedChecker};
552 return Sequence<ColumnDescription>{fColumns_};
553 };
554 virtual Sequence<ParameterDescription> GetParameters () const override
555 {
556 AssertExternallySynchronizedChecker::ReadContext declareContext{_fAssertExternallySynchronizedChecker};
557 return fParameters_;
558 };
559 virtual void Bind () override
560 {
561 AssertExternallySynchronizedChecker::WriteContext declareContext{_fAssertExternallySynchronizedChecker};
562 ThrowSQLiteErrorIfNotOK_ (::sqlite3_clear_bindings (fStatementObj_), fConnectionPtr_->Peek ());
563 }
564 virtual void Bind (unsigned int parameterIndex, const VariantValue& v) override
565 {
566 AssertExternallySynchronizedChecker::WriteContext declareContext{_fAssertExternallySynchronizedChecker};
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 AssertExternallySynchronizedChecker::WriteContext declareContext{_fAssertExternallySynchronizedChecker};
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 parameter 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 AssertExternallySynchronizedChecker::WriteContext declareContext{_fAssertExternallySynchronizedChecker};
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 AssertExternallySynchronizedChecker::WriteContext declareContext{_fAssertExternallySynchronizedChecker};
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:336
#define EnsureNotNull(p)
Definition Assertions.h:343
#define AssertNotImplemented()
Definition Assertions.h:404
#define RequireNotReached()
Definition Assertions.h:388
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:49
#define WeakAssertNotImplemented()
Definition Assertions.h:486
#define RequireNotNull(p)
Definition Assertions.h:350
#define AssertNotReached()
Definition Assertions.h:358
#define Verify(c)
Definition Assertions.h:422
#define DbgTrace
Definition Trace.h:327
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:288
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...
unique_lock< AssertExternallySynchronizedChecker > WriteContext
Instantiate AssertExternallySynchronizedChecker::WriteContext to designate an area of code where prot...
shared_lock< const AssertExternallySynchronizedChecker > ReadContext
Instantiate AssertExternallySynchronizedChecker::ReadContext to designate an area of code where prote...
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:234
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
Definition Throw.inl:54
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
void ThrowError(error_code ec)
throw a SystemErrorException with the given error_code - or std::bad_alloc for errc::not_enough_memor...