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