SQLite Containers
Loading...
Searching...
No Matches
BaseDB.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include "Config.hpp"
7#include "Utils.hpp"
8#include "SqliteStmt.hpp"
9#include <filesystem>
10#include <future>
11#include <mutex>
12#include <atomic>
13#ifdef _WIN32
14#include <codecvt>
15#include <locale>
16#endif
17
18#if SQLITE_THREADSAFE != 1
19#error "The project must be built for sqlite multithreading! Set the SQLITE_THREADSAFE=1"
20#endif
21
22namespace sqlite_containers {
23 namespace fs = std::filesystem;
24
27 class BaseDB {
28 public:
29
31 BaseDB() = default;
32
35 virtual ~BaseDB() {
36 disconnect();
37 }
38
41 void set_config(const Config& config) {
42 std::lock_guard<std::mutex> locker(m_config_mutex);
43 m_config_new = config;
44 m_config_update = true;
45 }
46
50 std::lock_guard<std::mutex> locker(m_config_mutex);
51 return m_config;
52 }
53
57 void connect() {
58 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
60 throw sqlite_exception("Database connection already exists and no configuration update required.");
61 }
62 if (m_sqlite_db) {
63 if (!m_config_update) return;
65 sqlite3_close_v2(m_sqlite_db);
66 m_sqlite_db = nullptr;
67 }
68
69 std::unique_lock<std::mutex> config_locker(m_config_mutex);
71 m_config_update = false;
72 config_locker.unlock();
73
74 try {
77 on_db_open();
80 } catch(const sqlite_exception &e) {
81 sqlite3_close_v2(m_sqlite_db);
82 m_sqlite_db = nullptr;
83 throw e;
84 } catch(...) {
85 sqlite3_close_v2(m_sqlite_db);
86 m_sqlite_db = nullptr;
87 throw sqlite_exception("An unspecified error occurred in the database operation.");
88 }
89 }
90
94 void connect(const Config& config) {
95 set_config(config);
96 connect();
97 }
98
101 void disconnect() {
102 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
103 if (!m_sqlite_db) return;
104
105 on_db_close();
106 sqlite3_close_v2(m_sqlite_db);
107 m_sqlite_db = nullptr;
108
109 try {
110 if (m_future.valid()) {
111 while (m_future.wait_for(std::chrono::milliseconds(1)) == std::future_status::timeout) {
112 std::this_thread::sleep_for(std::chrono::milliseconds(1));
113 }
114 m_future.get();
115 }
116 } catch(const sqlite_exception &e) {
117 throw e;
118 } catch(const std::exception &e) {
119 throw sqlite_exception(e.what());
120 } catch(...) {
121 throw sqlite_exception("Error occurred during async operation.");
122 }
123 }
124
129 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
130 db_begin(mode);
131 }
132
135 void commit() {
136 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
137 db_commit();
138 }
139
142 void rollback() {
143 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
144 db_rollback();
145 }
146
151 template<typename Func>
152 void execute_in_transaction(Func operation, const TransactionMode& mode) {
153 std::lock_guard<std::mutex> locker(m_sqlite_mutex);
154 try {
155 db_begin(mode);
156 operation();
157 db_commit();
158 } catch(const sqlite_exception &e) {
159 db_rollback();
160 throw e;
161 } catch(const std::exception &e) {
162 db_rollback();
163 throw sqlite_exception(e.what());
164 } catch(...) {
165 db_rollback();
166 throw sqlite_exception("Unknown error occurred.");
167 }
168 }
169
171 virtual void process() {};
172
173 protected:
174 sqlite3* m_sqlite_db = nullptr;
175 mutable std::mutex m_sqlite_mutex;
176
181 m_stmt_begin[static_cast<size_t>(mode)].execute(m_sqlite_db);
182 }
183
186 void db_commit() {
187 m_stmt_commit.execute(m_sqlite_db);
188 }
189
192 void db_rollback() {
194 }
195
202 std::exception_ptr ex,
203 const std::vector<SqliteStmt*>& stmts,
204 const std::string& message = "Unknown error occurred.") const {
205 try {
206 for (auto* stmt : stmts) {
207 stmt->reset();
208 stmt->clear_bindings();
209 }
210 if (ex) {
211 std::rethrow_exception(ex);
212 }
213 } catch (const sqlite_exception&) {
214 throw;
215 } catch (const std::exception& e) {
216 throw sqlite_exception(e.what());
217 } catch (...) {
218 throw sqlite_exception(message);
219 }
220 }
221
222 private:
223
224 std::array<SqliteStmt, 3> m_stmt_begin;
227
230 mutable std::mutex m_config_mutex;
231 std::atomic<bool> m_config_update = ATOMIC_VAR_INIT(false);
232
233 std::shared_future<void> m_future;
234
239 void db_create_directories(const Config &config) {
240# ifdef _WIN32
241 // Convert UTF-8 string to wide string for Windows
242 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
243 std::wstring wide_path = converter.from_bytes(config.db_path);
244 std::filesystem::path file_path(wide_path);
245# else
246 std::filesystem::path file_path(config.db_path);
247# endif
248 fs::path parent_dir = file_path.parent_path();
249 if (!std::filesystem::exists(parent_dir)) {
250 std::error_code ec;
251 if (!std::filesystem::create_directories(parent_dir, ec)) {
252 throw std::runtime_error("Failed to create directories for path: " + config.db_path);
253 }
254 }
255 }
256
260 void db_open(const Config &config) {
261 int flags = 0;
262 flags |= config.read_only ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
263 flags |= config.use_uri ? SQLITE_OPEN_URI : 0;
264 flags |= config.in_memory ? SQLITE_OPEN_MEMORY : 0;
265 flags |= SQLITE_OPEN_FULLMUTEX;
266 int err = 0;
267 const char* db_name = config.in_memory ? ":memory:" : config.db_path.c_str();
268 if ((err = sqlite3_open_v2(db_name, &m_sqlite_db, flags, nullptr)) != SQLITE_OK) {
269 std::string error_message = "Cannot open database: ";
270 error_message += sqlite3_errmsg(m_sqlite_db);
271 error_message += " (Error code: ";
272 error_message += std::to_string(err);
273 error_message += ")";
274 throw sqlite_exception(error_message);
275 }
276 }
277
281 void db_init(const Config &config) {
282 execute(m_sqlite_db, "PRAGMA busy_timeout = " + std::to_string(config.busy_timeout) + ";");
283 execute(m_sqlite_db, "PRAGMA page_size = " + std::to_string(config.page_size) + ";");
284 execute(m_sqlite_db, "PRAGMA cache_size = " + std::to_string(config.cache_size) + ";");
285 execute(m_sqlite_db, "PRAGMA analysis_limit = " + std::to_string(config.analysis_limit) + ";");
286 execute(m_sqlite_db, "PRAGMA wal_autocheckpoint = " + std::to_string(config.wal_autocheckpoint) + ";");
287 execute(m_sqlite_db, "PRAGMA journal_mode = " + to_string(config.journal_mode) + ";");
288 execute(m_sqlite_db, "PRAGMA synchronous = " + to_string(config.synchronous) + ";");
289 execute(m_sqlite_db, "PRAGMA locking_mode = " + to_string(config.locking_mode) + ";");
290 execute(m_sqlite_db, "PRAGMA auto_vacuum = " + to_string(config.auto_vacuum_mode) + ";");
291
292 for (size_t i = 0; i < m_stmt_begin.size(); ++i) {
293 m_stmt_begin[i].init(m_sqlite_db, "BEGIN " + to_string(static_cast<TransactionMode>(i)) + " TRANSACTION");
294 }
295 m_stmt_commit.init(m_sqlite_db, "COMMIT");
296 m_stmt_rollback.init(m_sqlite_db, "ROLLBACK");
297
298 if (config.user_version > 0) {
299 execute(m_sqlite_db, "PRAGMA user_version = " + std::to_string(config.user_version) + ";");
300 }
301 if (config.use_async) {
302 m_future = std::async(std::launch::async,
303 [this] {
304 process();
305 }).share();
306 }
307 }
308
309 protected:
310
314 virtual void db_create_table(const Config &config) = 0;
315
318 virtual void on_db_open() {}
319
322 virtual void on_db_close() {}
323
324 }; // BaseDB
325
326}; // namespace sqlite_containers
Contains the declaration of Config class for SQLite database configuration.
Declaration of the SqliteStmt class for managing SQLite prepared statements.
Utility functions for working with SQLite in sqlite_containers.
void connect()
Connects to the database using the current configuration. Initializes a connection to the database by...
Definition BaseDB.hpp:57
std::shared_future< void > m_future
Definition BaseDB.hpp:233
void begin(const TransactionMode &mode=TransactionMode::DEFERRED)
Begins a database transaction.
Definition BaseDB.hpp:128
Config get_config() const
Gets the current configuration of the database.
Definition BaseDB.hpp:49
void commit()
Commits the current transaction.
Definition BaseDB.hpp:135
void db_init(const Config &config)
Initializes the database with the given configuration. Sets database parameters such as busy timeout,...
Definition BaseDB.hpp:281
virtual void db_create_table(const Config &config)=0
Creates tables in the database. Must be implemented in derived classes.
std::atomic< bool > m_config_update
Definition BaseDB.hpp:231
virtual void on_db_close()
Called before the database is closed. Can be overridden in derived classes.
Definition BaseDB.hpp:322
virtual void process()
Processes asynchronous database requests (can be overridden).
Definition BaseDB.hpp:171
std::array< SqliteStmt, 3 > m_stmt_begin
Definition BaseDB.hpp:224
void db_rollback()
Rolls back the current transaction.
Definition BaseDB.hpp:192
void db_open(const Config &config)
Opens the database with the specified configuration.
Definition BaseDB.hpp:260
virtual void on_db_open()
Called after the database is opened. Can be overridden in derived classes.
Definition BaseDB.hpp:318
void db_commit()
Commits the current transaction.
Definition BaseDB.hpp:186
void disconnect()
Disconnects from the database.
Definition BaseDB.hpp:101
void connect(const Config &config)
Connects to the database with the given configuration.
Definition BaseDB.hpp:94
BaseDB()=default
Default constructor.
void execute_in_transaction(Func operation, const TransactionMode &mode)
Executes an operation inside a transaction.
Definition BaseDB.hpp:152
void db_begin(const TransactionMode &mode=TransactionMode::DEFERRED)
Begins a transaction with the given mode.
Definition BaseDB.hpp:180
void rollback()
Rolls back the current transaction.
Definition BaseDB.hpp:142
void set_config(const Config &config)
Sets the configuration for the database.
Definition BaseDB.hpp:41
void db_create_directories(const Config &config)
Creates necessary directories for the database. This method checks if the parent directory of the dat...
Definition BaseDB.hpp:239
virtual ~BaseDB()
Destructor. Disconnects from the database if connected.
Definition BaseDB.hpp:35
void db_handle_exception(std::exception_ptr ex, const std::vector< SqliteStmt * > &stmts, const std::string &message="Unknown error occurred.") const
Handles an exception by resetting and clearing bindings of prepared SQL statements.
Definition BaseDB.hpp:201
Configuration class for SQLite database settings.
Definition Config.hpp:11
int wal_autocheckpoint
WAL auto-checkpoint threshold.
Definition Config.hpp:24
std::string db_path
Path to the SQLite database file.
Definition Config.hpp:13
int analysis_limit
Maximum number of rows to analyze.
Definition Config.hpp:23
AutoVacuumMode auto_vacuum_mode
SQLite auto-vacuum mode.
Definition Config.hpp:28
JournalMode journal_mode
SQLite journal mode.
Definition Config.hpp:25
bool read_only
Whether the database is in read-only mode.
Definition Config.hpp:15
int user_version
User-defined version number for the database schema.
Definition Config.hpp:19
SynchronousMode synchronous
SQLite synchronous mode.
Definition Config.hpp:26
bool use_uri
Whether to use URI for opening the database.
Definition Config.hpp:16
bool use_async
Whether to use asynchronous write.
Definition Config.hpp:18
bool in_memory
Whether the database should be in-memory.
Definition Config.hpp:17
LockingMode locking_mode
SQLite locking mode.
Definition Config.hpp:27
int busy_timeout
Timeout in milliseconds for busy handler.
Definition Config.hpp:20
int page_size
SQLite page size.
Definition Config.hpp:21
int cache_size
SQLite cache size (in pages).
Definition Config.hpp:22
Class for managing SQLite prepared statements.
Exception class for SQLite errors.
Definition Utils.hpp:27
std::string to_string(const JournalMode &mode)
Converts JournalMode enum to string representation.
Definition Enums.hpp:65
void execute(sqlite3_stmt *stmt)
Executes a SQLite statement.
Definition Utils.hpp:47
TransactionMode
Defines SQLite transaction modes.
Definition Enums.hpp:56
@ DEFERRED
Waits to lock the database until a write operation is requested.
Definition Enums.hpp:57