4#include "Stroika/Foundation/StroikaPreComp.h"
18using namespace Characters;
20using namespace Database;
21using namespace Database::SQL::SQLite;
22using namespace Execution;
29using Memory::MakeSharedPtr;
31#if qStroika_HasComponent_sqlite
33 struct ModuleShutdown_ {
36 Verify (::sqlite3_shutdown () == SQLITE_OK);
39 [[noreturn]]
void ThrowSQLiteError_ (
int errCode, sqlite3* sqliteConnection)
41 Require (errCode != SQLITE_OK);
42 optional<String> errMsgDetails;
43 if (sqliteConnection !=
nullptr) {
44 errMsgDetails = String::FromUTF8 (::sqlite3_errmsg (sqliteConnection));
49 Throw (system_error{make_error_code (errc::device_or_resource_busy)});
53 Throw (system_error{make_error_code (errc::device_or_resource_busy)});
55 case SQLITE_CONSTRAINT: {
60 static const auto kEx_ =
Exception{
"SQLITE_CONSTRAINT"sv};
65 static const auto kEx_ =
Exception{
"SQLITE_TOOBIG"sv};
70 Throw (system_error{make_error_code (errc::no_space_on_device)});
72 case SQLITE_READONLY: {
73 static const auto kEx_ =
Exception{
"SQLITE_READONLY"sv};
81 static const auto kEx_ =
Exception{
"SQLITE_MISUSE"sv};
90 static const auto kEx_ =
Exception{
"SQLITE_ERROR"sv};
95 DbgTrace (
"SQLITE_NOMEM translated to bad_alloc"_f);
100 Throw (
Exception{
"SQLite Error: {} (code {})"_f(errMsgDetails, errCode)});
106 void ThrowSQLiteErrorIfNotOK_ (
int errCode, sqlite3* sqliteConnection)
108 static_assert (SQLITE_OK == 0);
109 if (errCode != SQLITE_OK) [[unlikely]] {
110 ThrowSQLiteError_ (errCode, sqliteConnection);
117 template <invocable<
int,
char**,
char**> CB>
118 struct SQLiteCallback_ {
121 using STATIC_FUNCTION_TYPE = int (*) (
void*, int,
char**,
char**);
123 SQLiteCallback_ (CB&& cb)
124 : fCallback_{forward<CB> (cb)}
127 STATIC_FUNCTION_TYPE GetStaticFunction ()
137 static int STATICFUNC_ (
void* SQLiteCallbackData,
int argc,
char** argv,
char** azColName)
139 SQLiteCallback_* sqc =
reinterpret_cast<SQLiteCallback_*
> (SQLiteCallbackData);
140 return sqc->fCallback_ (argc, argv, azColName);
151 struct VerifyFlags_ {
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"));
159 Assert (CompiledOptions::kThe.ENABLE_JSON1 == !::sqlite3_compileoption_used (
"OMIT_JSON1"));
166 using Connection::Options;
168 Rep_ (
const Options& options)
174 switch (options.fThreadingMode.value_or (Options::kDefault_ThreadingMode)) {
175 case Options::ThreadingMode::eSingleThread:
177 case Options::ThreadingMode::eMultiThread:
178 Require (CompiledOptions::kThe.THREADSAFE);
179 Require (::sqlite3_threadsafe ());
180 flags |= SQLITE_OPEN_NOMUTEX;
182 case Options::ThreadingMode::eSerialized:
183 Require (CompiledOptions::kThe.THREADSAFE);
184 Require (::sqlite3_threadsafe ());
185 flags |= SQLITE_OPEN_FULLMUTEX;
189 if (options.fImmutable) {
192 Require (options.fReadOnly);
194 flags |= options.fReadOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
198 [[maybe_unused]]
int n{};
199 if (options.fDBPath) {
202 if (options.fTemporaryDB) {
205 if (options.fInMemoryDB) {
210 if (options.fDBPath) {
211 uriArg = options.fDBPath->generic_string ();
212 if (uriArg[0] ==
':') {
213 uriArg =
"./" + uriArg;
216 if (options.fTemporaryDB) {
220 Require (not options.fTemporaryDB->empty ());
222 if (options.fInMemoryDB) {
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 ();
235 if (uriArg.empty ()) {
239 u8string safeCharURI = IO::Network::UniformResourceIdentification::PCTEncode (u8string{uriArg.begin (), uriArg.end ()}, {});
240 uriArg =
"file:" +
string{safeCharURI.begin (), safeCharURI.end ()} +
"?mode=memory&cache=shared";
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);
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) {
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);
264 ThrowSQLiteError_ (e, fDB_);
266 SetBusyTimeout (options.fBusyTimeout.value_or (Options::kBusyTimeout_Default));
267 if (options.fJournalMode) {
268 SetJournalMode (*options.fJournalMode);
275 Verify (::sqlite3_close (fDB_) == SQLITE_OK);
277 virtual shared_ptr<const EngineProperties> GetEngineProperties ()
const override
280 virtual String GetEngineName ()
const override
284 virtual String GetSQL (NonStandardSQL n)
const override
287 case NonStandardSQL::eDoesTableExist:
288 return "SELECT name FROM sqlite_master WHERE type='table' AND name="_k + SQL::EngineProperties::kDoesTableExistParameterName;
293 virtual bool RequireStatementResetAfterModifyingStatmentToCompleteTransaction ()
const override
297 virtual bool SupportsNestedTransactions ()
const override
302 static const shared_ptr<const EngineProperties> kProps_ = MakeSharedPtr<const MyEngineProperties_> ();
307 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
308 return Database::SQL::SQLite::Statement{conn, sql};
312 Connection::Ptr conn = Connection::Ptr{Debug::UncheckedDynamicPointerCast<Connection::IRep> (shared_from_this ())};
313 return Database::SQL::SQLite::Transaction{conn};
315 virtual void Exec (
const String& sql)
override
318 int e = ::sqlite3_exec (fDB_, sql.
AsUTF8<
string> ().c_str (),
nullptr,
nullptr,
nullptr);
319 if (e != SQLITE_OK) [[unlikely]] {
320 ThrowSQLiteErrorIfNotOK_ (e, fDB_);
323 virtual ::sqlite3* Peek ()
override
328 virtual Duration GetBusyTimeout ()
const override
332 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
334 Assert (::strcmp (azColName[0],
"timeout") == 0);
335 int val = ::atoi (argv[0]);
340 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma busy_timeout;", callback.GetStaticFunction (), callback.GetData (),
nullptr), fDB_);
342 return Duration{double (*d) / 1000.0};
344 virtual void SetBusyTimeout (
const Duration& timeout)
override
347 ThrowSQLiteErrorIfNotOK_ (::sqlite3_busy_timeout (fDB_, (
int)(timeout.
As<
float> () * 1000)), fDB_);
349 virtual JournalModeType GetJournalMode ()
const override
352 auto callback = SQLiteCallback_{[&] ([[maybe_unused]]
int argc,
char** argv, [[maybe_unused]]
char** azColName) {
354 Assert (::strcmp (azColName[0],
"journal_mode") == 0);
358 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode;", callback.GetStaticFunction (), callback.GetData (),
nullptr), fDB_);
360 if (d ==
"delete"sv) {
361 return JournalModeType::eDelete;
363 if (d ==
"truncate"sv) {
364 return JournalModeType::eTruncate;
366 if (d ==
"persist"sv) {
367 return JournalModeType::ePersist;
369 if (d ==
"memory"sv) {
370 return JournalModeType::eMemory;
373 return JournalModeType::eWAL;
376 return JournalModeType::eWAL2;
379 return JournalModeType::eOff;
382 return JournalModeType::eDelete;
384 virtual void SetJournalMode (JournalModeType journalMode)
override
387 switch (journalMode) {
388 case JournalModeType::eDelete:
389 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'delete';",
nullptr, 0,
nullptr), fDB_);
391 case JournalModeType::eTruncate:
392 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'truncate';",
nullptr, 0,
nullptr), fDB_);
394 case JournalModeType::ePersist:
395 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'persist';",
nullptr, 0,
nullptr), fDB_);
397 case JournalModeType::eMemory:
398 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'memory';",
nullptr, 0,
nullptr), fDB_);
400 case JournalModeType::eWAL:
401 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'wal';",
nullptr, 0,
nullptr), fDB_);
403 case JournalModeType::eWAL2:
404 if (GetJournalMode () == JournalModeType::eWAL) {
405 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'delete';",
nullptr, 0,
nullptr), fDB_);
407 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'wal2';",
nullptr, 0,
nullptr), fDB_);
409 case JournalModeType::eOff:
410 ThrowSQLiteErrorIfNotOK_ (::sqlite3_exec (fDB_,
"pragma journal_mode = 'off';",
nullptr, 0,
nullptr), fDB_);
424SQL::SQLite::Connection::Ptr::Ptr (
const shared_ptr<IRep>& 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);
429 return thisObj->operator->()->GetBusyTimeout ();
431 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
auto* property,
auto timeout) {
432 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::busyTimeout);
434 thisObj->operator->()->SetBusyTimeout (timeout);
436 , journalMode{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
const auto* property) {
437 const Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
439 return thisObj->operator->()->GetJournalMode ();
441 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]]
auto* property,
auto journalMode) {
442 Ptr* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Ptr::journalMode);
444 thisObj->operator->()->SetJournalMode (journalMode);
447#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
448 if (src !=
nullptr) {
449 fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (src->fAssertExternallySynchronizedMutex.GetSharedContext ());
459auto SQL::SQLite::Connection::New (
const Options& options) -> Ptr
461 return Ptr{MakeSharedPtr<Rep_> (options)};
469struct Statement::MyRep_ : IRep {
470 MyRep_ (
const Connection::Ptr& db,
const String& query)
471 : fConnectionPtr_{db}
473#if USE_NOISY_TRACE_IN_THIS_MODULE_
479#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
480 _fAssertExternallySynchronizedMutex.SetAssertExternallySynchronizedMutexContext (
481 fConnectionPtr_.fAssertExternallySynchronizedMutex.GetSharedContext ());
483 string queryUTF8 = query.
AsUTF8<
string> ();
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') {
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
501 :
String::FromUTF8 (::sqlite3_column_decltype (fStatementObj_, i)).c_str ());
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);
509 fParameters_ += ParameterDescription{tmp ==
nullptr ? optional<String>{} : String::FromUTF8 (tmp),
nullptr};
516 (void)::sqlite3_finalize (fStatementObj_);
518 virtual String GetSQL (WhichSQLFlag whichSQL)
const override
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);
533 case WhichSQLFlag::eNormalized:
534 if constexpr (CompiledOptions::kThe.ENABLE_NORMALIZE) {
537#ifdef SQLITE_ENABLE_NORMALIZE
538 return String::FromUTF8 (::sqlite3_normalized_sql (fStatementObj_));
558 virtual void Bind ()
override
561 ThrowSQLiteErrorIfNotOK_ (::sqlite3_clear_bindings (fStatementObj_), fConnectionPtr_->Peek ());
563 virtual void Bind (
unsigned int parameterIndex,
const VariantValue& v)
override
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 ());
575 case VariantValue::eBoolean:
576 case VariantValue::eInteger:
577 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_int64 (fStatementObj_, parameterIndex + 1, v.
As<sqlite3_int64> ()), fConnectionPtr_->Peek ());
579 case VariantValue::eFloat:
580 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_double (fStatementObj_, parameterIndex + 1, v.
As<
double> ()), fConnectionPtr_->Peek ());
582 case VariantValue::eBLOB: {
584 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_blob64 (fStatementObj_, parameterIndex + 1, b.
begin (), b.
size (), SQLITE_TRANSIENT),
585 fConnectionPtr_->Peek ());
587 case VariantValue::eNull:
588 ThrowSQLiteErrorIfNotOK_ (::sqlite3_bind_null (fStatementObj_, parameterIndex + 1), fConnectionPtr_->Peek ());
597 Require (not parameterName.empty ());
599 String pn = parameterName;
603 for (
unsigned int i = 0; i < fParameters_.length (); ++i) {
604 if (fParameters_[i].fName == pn) {
609 DbgTrace (
"Statement::Bind: Parameter '{}' not found in list {}"_f, parameterName,
613 virtual void Reset ()
override
615#if USE_NOISY_TRACE_IN_THIS_MODULE_
620 ThrowSQLiteErrorIfNotOK_ (::sqlite3_reset (fStatementObj_), fConnectionPtr_->Peek ());
622 virtual optional<Row> GetNextRow ()
override
624#if USE_NOISY_TRACE_IN_THIS_MODULE_
630 int rc = ::sqlite3_step (fStatementObj_);
637 for (
unsigned int i = 0; i < fColumns_.size (); ++i) {
640 switch (::sqlite3_column_type (fStatementObj_, i)) {
641 case SQLITE_INTEGER: {
642 v =
VariantValue{::sqlite3_column_int (fStatementObj_, i)};
645 v =
VariantValue{::sqlite3_column_double (fStatementObj_, i)};
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));
658 v =
VariantValue{String::FromUTF8 (
reinterpret_cast<const char*
> (::sqlite3_column_text (fStatementObj_, i)))};
664 row.Add (fColumns_[i].fName, v);
672 ThrowSQLiteError_ (rc, fConnectionPtr_->Peek ());
675 Connection::Ptr fConnectionPtr_;
676 ::sqlite3_stmt* fStatementObj_;
677 vector<ColumnDescription> fColumns_;
681Statement::Statement (
const Connection::Ptr& db,
const String& query)
682 : inherited{make_unique<MyRep_> (db, query)}
692 MyRep_ (
const Connection::Ptr& db, Flag f)
693 : fConnectionPtr_{db}
696 case Flag::eDeferred:
697 db->Exec (
"BEGIN DEFERRED TRANSACTION;"sv);
699 case Flag::eExclusive:
700 db->Exec (
"BEGIN EXCLUSIVE TRANSACTION;"sv);
702 case Flag::eImmediate:
703 db->Exec (
"BEGIN IMMEDIATE TRANSACTION;"sv);
709 virtual void Commit ()
override
711 Require (not fCompleted_);
713 fConnectionPtr_->Exec (
"COMMIT TRANSACTION;"sv);
717 Require (not fCompleted_);
719 fConnectionPtr_->Exec (
"ROLLBACK TRANSACTION;"sv);
724 return fCompleted_ ? Disposition::eCompleted : Disposition::eNone;
726 Connection::Ptr fConnectionPtr_;
727 bool fCompleted_{
false};
729Transaction::Transaction (
const Connection::Ptr& db, Flag f)
730 : inherited{make_unique<MyRep_> (db, f)}
#define AssertNotImplemented()
#define RequireNotReached()
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
#define WeakAssertNotImplemented()
#define RequireNotNull(p)
#define AssertNotReached()
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual T AsUTF8() const
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 ...
nonvirtual RETURNTYPE As() const
EngineProperties captures the features associated with a given database engine (being talked to throu...
virtual void Rollback()=0
virtual Disposition GetDisposition() const =0
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
nonvirtual size_t size() const
Duration is a chrono::duration<double> (=.
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >