Stroika Library 3.0d23
 
Loading...
Searching...
No Matches
Document/SQLite.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Database_Document_SQLite_h_
5#define _Stroika_Foundation_Database_Document_SQLite_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <filesystem>
10#include <optional>
11
12#if qStroika_HasComponent_sqlite
13#include <sqlite/sqlite3.h>
14#endif
15
17#include "Stroika/Foundation/Common/Property.h"
18#include "Stroika/Foundation/Containers/Mapping.h"
19#include "Stroika/Foundation/Containers/Sequence.h"
29
30/**
31 * \file
32 *
33 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
34 *
35 * TODO
36 * @todo Create SQLite Exception class and use sqlite3_errstr () to generate good string
37 * message (that seems to return threadsafe static const strings)
38 */
39
40namespace Stroika::Foundation::Database::Document::SQLite {
41
42 using Characters::String;
43 using Containers::Mapping;
44 using Containers::Sequence;
45 using DataExchange::VariantValue;
46 using IO::Network::URI;
47 using Time::Duration;
48
49#if qStroika_HasComponent_sqlite
50
51 /**
52 * This defines what options SQLite was compiled with.
53 *
54 * For a full list of possible options, see <https://www.sqlite.org/compile.html>
55 * (though we only capture a limited subset of these). To check the rest, callers
56 * can use ::sqlite3_compileoption_used ()
57 *
58 * Fields correspond to names looked up with ::sqlite3_compileoption_used () - only this is constexpr (and an incomplete replica).
59 * This is checked to correspond to the sqlite3_compileoption_used() values at startup with assertions.
60 *
61 * \todo Find a better way to define! We want this to be available as a constexpr object. But the defines
62 * are just done in the .c file that gets defined and the API to lookup values is very non-constexpr.
63 *
64 * So instead we take a good guess at the values (based on defaults and #defines done in this file)
65 * and check with asserts we got the right value.
66 */
67 class CompiledOptions final {
68 public:
69 /**
70 * c++ #define SQLITE_ENABLE_NORMALIZE (not in docs file but does appear as a compile time option - we need to check)
71 */
72 bool ENABLE_NORMALIZE;
73
74 /**
75 * SQLITE_THREADSAFE = 0, 1, 2 (0 means no)
76 */
77 uint8_t THREADSAFE;
78
79 /**
80 * SQLITE_ENABLE_JSON1 = true (required)
81 */
82
83 /**
84 * Defined constexpr
85 */
86 static const CompiledOptions kThe;
87 };
88
89 /**
90 * https://www.sqlite.org/pragma.html#pragma_journal_mode
91 *
92 * In my experience, it appears WAL provides the best performance, for multithreaded applications.
93 * \see https://sqlite.org/wal.html
94 * "WAL provides more concurrency as readers do not block writers and a writer"
95 * "does not block readers. Reading and writing can proceed concurrently."
96 */
97 enum JournalModeType {
98 eDelete,
99 eTruncate,
100 ePersist,
101 eMemory,
102 eWAL,
103 eWAL2,
104 eOff
105 };
106
107 /**
108 * \brief SQLite::Connection namespace contains SQL::Connection::Ptr subclass, specific to SQLite, and ::New function factory.
109 */
110 namespace Connection {
111
112 using namespace Database::Document::Connection;
113
114 class IRep;
115
116 /**
117 * These are options used to create a database Connection::Ptr object (with Connection::New).
118 *
119 * Since this is also how you create a database, in a sense, its those options too.
120 */
121 struct Options final : Database::Document::Connection::Options {
122 /**
123 * \brief use eInternallySynchronized to make letter internally synchronized
124 *
125 * \note this refers to in-process synchronization. Future flags/fields/options will be needed
126 * in other impls to assure cross-process synchronization (not sure if even appropriate for this impl but maybe something simple with flock).
127 *
128 * \note if set eNotKnownInternallySynchronized (the default), in debug mode, the system uses AssertExternallySynchronizedMutex
129 * to check for unsafe thread usage.
130 */
131 Execution::InternallySynchronized fInternallySynchronizedLetter{Execution::eNotKnownInternallySynchronized};
132
133 /**
134 * NOTE - we choose to only support a PATH, and not the URI syntax, because the URI syntax is used to pass
135 * extra parameters (as from a GUI) and those can conflict with what is specified here (making it unclear or
136 * surprising how to interpret). @todo perhaps provide an API to 'parse' an SQLite URI into one of these Stroika
137 * SQLite options objects?
138 *
139 * \note - fInMemoryDB and fDBPath and fTemporaryDB are mutually exclusive options.
140 */
141 optional<filesystem::path> fDBPath;
142
143 /**
144 * This option only applies if fDBPath is set.
145 * \pre fCreateDBPathIfDoesNotExist => not fReadOnly
146 */
147 bool fCreateDBPathIfDoesNotExist{true};
148
149 /**
150 * fTemporaryDB is just like fInMemoryDB, except that it will be written to disk. But its like temporaryDB in that
151 * it will be automatically deleted when this connection (that created it) closes.
152 *
153 * \note - fInMemoryDB and fDBPath and fTemporaryDB are mutually exclusive options.
154 */
155 optional<String> fTemporaryDB;
156
157 /**
158 * If provided, the database will not be stored to disk, but just saved in memory. The name still must be provided to allow
159 * for sharing the same (in memory) database between different connections). If the name is the empty string (String{}) then
160 * it is guaranteed unique.
161 *
162 * \note - fInMemoryDB and fDBPath and fTemporaryDB are mutually exclusive options.
163 *
164 * \note using a named in-memory-db allows two separate threads in the same process, to share the same database.
165 */
166 optional<String> fInMemoryDB;
167
168 /**
169 * @see https://www.sqlite.org/compile.html#threadsafe
170 *
171 * Note this refers to the threading mode for the underlying database. A Connection object is always single-threaded/externally
172 * synchronized.
173 */
174 enum class ThreadingMode {
175 /**
176 * SQLITE_OPEN_FULLMUTEX
177 * In this mode, all mutexes are disabled and SQLite is unsafe to use in more than a single thread at once
178 */
179 eSingleThread,
180
181 /**
182 * SQLITE_OPEN_NOMUTEX
183 * In this mode, SQLite can be safely used by multiple threads provided that no single database connection is used simultaneously in two or more threads.
184 * (Stroika Debug::AssertExternallySynchronizedMutex enforces this)
185 *
186 * This may not always be available depending on how SQLite was compiled, but we dont have access to SQLITE_THREADSAFE at compile time
187 * (since just defined in C file from Stroika/ThirdPartyComponents/sqlite/Makefile);
188 * call sqlite3_threadsafe, to see if this is enabled
189 */
190 eMultiThread,
191
192 /**
193 * SQLITE_OPEN_FULLMUTEX
194 * In serialized mode, SQLite can be safely used by multiple threads with no restriction.
195 * (note even in this mode, each connection is Debug::AssertExternallySynchronizedMutex)
196 *
197 * This may not always be available depending on how SQLite was compiled, but we dont have access to SQLITE_THREADSAFE at compile time
198 * (since just defined in C file from Stroika/ThirdPartyComponents/sqlite/Makefile);
199 * call sqlite3_threadsafe, to see if this is enabled
200 *
201 * \note Use of this API, as of Stroika 2.1b12, may result in poor error messages, due to how errors are stored (and maybe other such
202 * issues - maybe we need to do lock around call to each function to avoid making this mode nearly pointless).
203 */
204 eSerialized,
205 };
206 optional<ThreadingMode> fThreadingMode;
207
208 /**
209 * I'm quite unsure I have this right, since seems to work so badly (frequent busy timeouts) - but from the docs this seems
210 * clear to be the best answer (best fit with the rest of how Stroika works).
211 */
212 static inline constexpr auto kDefault_ThreadingMode = ThreadingMode::eMultiThread;
213
214 /**
215 * This can generally be ignored, and primarily affects low level OS interface locking choices.
216 * @see https://www.sqlite.org/vfs.html
217 */
218 optional<String> fVFS;
219
220 /**
221 * If a database is opened readonly, updates will fail, and if the database doesn't exist, it will not be automatically created.
222 */
223 bool fReadOnly{false};
224
225 /**
226 * The immutable query parameter is a boolean that signals to SQLite that the underlying database file is held on read-only media and
227 * cannot be modified, even by another process with elevated privileges.
228 *
229 * \pre fImmutable ==> fReadOnly
230 */
231 bool fImmutable{false};
232
233 /**
234 * This is only useful if the database can be opened by multiple threads of control (multiple threads with connections
235 * within the same app, or multiple applications).
236 *
237 * @see also https://www.sqlite.org/c3ref/busy_timeout.html
238 *
239 * This seems black magic. I try different values at random, and get inscrutable results when used (or not used).
240 * Example in SQLite docs says 100ms. It turns out, in my limited testing, that appears to work best. But what makes
241 * no sense, is that I generally get MORE busy timeout errors if I use a much larger value (like 500ms, or 5000ms).
242 * Its ALMOST as if the database was holding a lock (one connection) while retrying?
243 *
244 * SQLITE appears to default this value to 0s, but that causes sporadic busy_timeouts in many tests. I think (empirically) - 1s, or maybe 10s
245 * maybe better. Still early to say. Specify your own if you care, else see kBusyTimeout_Default.
246 */
247 optional<Duration> fBusyTimeout;
248
249 /**
250 */
251 static constexpr inline chrono::seconds kBusyTimeout_Default = 10s;
252
253 /**
254 * \note - see JournalModeType and Connection::Ptr::journalMode
255 */
256 optional<JournalModeType> fJournalMode;
257 };
258
259 /**
260 * Connection provides an API for accessing an SQLite database.
261 *
262 * A new Connection::Ptr is typically created SQLite::Connection::New()
263 *
264 * \note \em Thread-Safety <a href="Thread-Safety.md#C++-Standard-Thread-Safety-For-Envelope-Plus-Must-Externally-Synchronize-Letter">C++-Standard-Thread-Safety-For-Envelope-Plus-Must-Externally-Synchronize-Letter</a>
265 * But though each connection can only be accessed from a single thread at a time, the underlying database may be
266 * threadsafe (even if accessed across processes) - depending on its construction Options::ThreadSafety
267 *
268 * The Connection itself is standardC++ thread safety. The thread-safety of the underlying database depends on the setting
269 * of Options::fThreadingMode when the database is constructed.
270 *
271 * @see https://www.sqlite.org/threadsafe.html
272 * We set SQLITE_OPEN_NOMUTEX on open (so mode Multi-thread, but not Serialized).
273 *
274 * NOTE - two Connection::Ptr objects referring to the same underlying REP is NOT (probably) safe with SQLITE. But referring
275 * to the same database is safe.
276 *
277 */
278 class Ptr : public Database::Document::Connection::Ptr {
279 private:
280 using inherited = Database::Document::Connection::Ptr;
281
282 public:
283 /**
284 */
285 Ptr (const Ptr& src);
286 Ptr (const shared_ptr<IRep>& src);
287 Ptr (nullptr_t);
288
289 public:
290 ~Ptr () = default;
291
292 public:
293 /**
294 */
295 nonvirtual Ptr& operator= (const Ptr& src);
296 nonvirtual Ptr& operator= (Ptr&& src) noexcept;
297
298 public:
299 /**
300 */
301 nonvirtual IRep* operator->() const noexcept;
302
303 public:
304 /**
305 * When doing a query that would have failed due to SQL_BUSY timeout, SQLite will wait
306 * and retry up to this long, to avoid the timeout.
307 */
308 Common::Property<Duration> busyTimeout;
309
310 public:
311 /**
312 * This can significantly affect database performance, and reliability.
313 */
314 Common::Property<JournalModeType> journalMode;
315
316 public:
317 /**
318 */
319 nonvirtual void Exec (const String& sql);
320 };
321
322 /**
323 * \brief create an SQLite database connection object, guided by argument Options.
324 */
325 Ptr New (const Options& options);
326
327 /**
328 * Connection provides an API for accessing an SQLite database.
329 *
330 * Typically don't use this directly, but use Connection::Ptr, a smart ptr wrapper on this interface.
331 *
332 * \note \em Thread-Safety <a href="Thread-Safety.md#C++-Standard-Thread-Safety">C++-Standard-Thread-Safety</a>
333 * But though each connection can only be accessed from a single thread at a time, the underlying database may be
334 * threadsafe (even if accessed across processes) - depending on its construction OOptions::ThreadSafety
335 *
336 * The Connection itself is standardC++ thread safety. The thread-safety of the underlying database depends on the setting
337 * of Options::fThreadingMode when the database is constructed.
338 *
339 * @see https://www.sqlite.org/threadsafe.html
340 * We set SQLITE_OPEN_NOMUTEX on open (so mode Multi-thread, but not Serialized).
341 *
342 * NOTE ALSO - its POSSIBLE we could lift this Debug::AssertExternallySynchronizedMutex code / restriction.
343 * But sqlite docs not super clear. Maybe I need to use their locking APIs myself internally to use
344 * those locks to make a sequence of bindings safe? But for now just don't assume this is threadsafe and we'll be OK.
345 */
346 class IRep : public Database::Document::Connection::IRep {
347 public:
348 /**
349 * Fetched dynamically with pragma busy_timeout;
350 */
351 virtual Duration GetBusyTimeout () const = 0;
352
353 public:
354 /**
355 * \pre timeout >= 0
356 */
357 virtual void SetBusyTimeout (const Duration& timeout) = 0;
358
359 public:
360 /**
361 */
362 virtual JournalModeType GetJournalMode () const = 0;
363
364 public:
365 /**
366 */
367 virtual void SetJournalMode (JournalModeType journalMode) = 0;
368
369 public:
370 /**
371 */
372 virtual void Exec (const String& sql) = 0;
373
374 public:
375 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE Debug::AssertExternallySynchronizedMutex fAssertExternallySynchronizedMutex;
376
377 private:
378 friend class Ptr;
379 };
380
381 }
382
383 /**
384 * \see https://www.sqlite.org/lang_transaction.html
385 *
386 * \note Transactions are not required. This is for explicit transactions. If you omit
387 * using transactions, SQLite creates mini transactions automatically for each statement.
388 *
389 * \note Nested transactions not supported
390 *
391 * \todo Consider supporting SQLITE SAVEPOINT (like nested transaction)
392 */
393 class Transaction : public Database::Document::Transaction {
394 private:
395 using inherited = Database::Document::Transaction;
396
397 public:
398 enum Flag {
399 /**
400 * Don't really start the transaction until the command to read/update the database
401 */
402 eDeferred,
403
404 /**
405 * Start writing to the DB immediately (as of the transaction start); note this affects when you might
406 * get SQL_BUSY errors.
407 */
408 eImmediate,
409
410 /**
411 * Depends on WAL mode, but generally prevents other database connections from reading the
412 * database while the transaction is underway.
413 */
414 eExclusive,
415
416 eDEFAULT = eDeferred
417 };
418
419 public:
420 /**
421 */
422 Transaction () = delete;
423 Transaction (const Connection::Ptr& db, Flag f = Flag::eDEFAULT);
424 Transaction (const Transaction&) = delete;
425
426 private:
427 struct MyRep_;
428 };
429#endif
430
431}
432
433/*
434 ********************************************************************************
435 ***************************** Implementation Details ***************************
436 ********************************************************************************
437 */
438#include "SQLite.inl"
439
440#endif /*_Stroika_Foundation_Database_Document_SQLite_h_*/
#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE
[[msvc::no_unique_address]] isn't always broken in MSVC. Annotate with this on things where its not b...
Definition StdCompat.h:445