Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
sqlite_backend.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5#include "sqlite_backend.h"
6#include "../core/result.h"
7
8#ifdef USE_SQLITE
9#include <sqlite3.h>
10#endif
11
12#include <sstream>
13#include <iomanip>
14#include <variant>
15#include <iostream>
16#include <vector>
17
19
20namespace
21{
22const database::utils::backend_logger logger_("SQLite");
23}
24
25namespace database
26{
27namespace backends
28{
29
31 : connection_(nullptr)
32{
33}
34
35kcenon::common::VoidResult sqlite_backend::do_initialize(const core::connection_config& config)
36{
37 connection_config_ = config;
38
39 // Use database field as file path, default to ":memory:" for in-memory database
40 std::string db_path = config.database.empty() ? ":memory:" : config.database;
41
42#ifdef USE_SQLITE
43 std::lock_guard<std::recursive_mutex> lock(sqlite_mutex_);
44 try {
45 sqlite3* db = nullptr;
46
47 // Open or create the database
48 int result = sqlite3_open(db_path.c_str(), &db);
49
50 if (result != SQLITE_OK) {
51 last_error_ = std::string("Connection failed: ") + sqlite3_errmsg(db);
52 logger_.error("do_initialize", last_error_);
53 if (db) {
54 sqlite3_close(db);
55 }
56 return kcenon::common::error_info{
59 "sqlite_backend"
60 };
61 }
62
63 connection_ = db;
64
65 // Enable foreign key constraints
66 char* error_msg = nullptr;
67 if (sqlite3_exec(db, "PRAGMA foreign_keys = ON", nullptr, nullptr, &error_msg) != SQLITE_OK) {
68 logger_.warning(std::string("Failed to enable foreign key constraints: ") + (error_msg ? error_msg : ""));
69 if (error_msg) sqlite3_free(error_msg);
70 }
71
72 last_error_.clear();
73 return kcenon::common::ok();
74 } catch (const std::exception& e) {
75 last_error_ = std::string("Connection error: ") + e.what();
76 logger_.error("do_initialize", last_error_);
77 }
78#else
79 logger_.warning("SQLite support not compiled. Mock mode enabled.");
80 // Mock mode for testing without SQLite
81 last_error_.clear();
82 return kcenon::common::ok();
83#endif
84
85 if (last_error_.empty()) {
86 last_error_ = "Failed to connect to SQLite database";
87 }
88 return kcenon::common::error_info{
91 "sqlite_backend"
92 };
93}
94
95kcenon::common::VoidResult sqlite_backend::do_shutdown()
96{
97 // Rollback any active transaction before disconnecting
98 if (in_transaction_) {
100 }
101
102#ifdef USE_SQLITE
103 std::lock_guard<std::recursive_mutex> lock(sqlite_mutex_);
104 if (connection_) {
105 sqlite3* db = static_cast<sqlite3*>(connection_);
106 int result = sqlite3_close(db);
107 connection_ = nullptr;
108 if (result != SQLITE_OK) {
109 last_error_ = "Failed to close SQLite database";
110 return kcenon::common::error_info{
113 "sqlite_backend"
114 };
115 }
116 }
117#endif
118
119 last_error_.clear();
120 return kcenon::common::ok();
121}
122
124{
125#ifdef USE_SQLITE
126 sqlite3_stmt* sqlite_stmt = static_cast<sqlite3_stmt*>(stmt);
127
128 int sqlite_type = sqlite3_column_type(sqlite_stmt, column_index);
129
130 switch (sqlite_type) {
131 case SQLITE_INTEGER:
132 return static_cast<int64_t>(sqlite3_column_int64(sqlite_stmt, column_index));
133
134 case SQLITE_FLOAT:
135 return sqlite3_column_double(sqlite_stmt, column_index);
136
137 case SQLITE_TEXT:
138 {
139 const char* text = reinterpret_cast<const char*>(sqlite3_column_text(sqlite_stmt, column_index));
140 return std::string(text ? text : "");
141 }
142
143 case SQLITE_BLOB:
144 {
145 // For BLOB data, convert to string representation
146 const void* blob = sqlite3_column_blob(sqlite_stmt, column_index);
147 int blob_size = sqlite3_column_bytes(sqlite_stmt, column_index);
148 if (blob && blob_size > 0) {
149 const char* blob_chars = static_cast<const char*>(blob);
150 return std::string(blob_chars, blob_size);
151 }
152 return std::string();
153 }
154
155 case SQLITE_NULL:
156 default:
157 return nullptr;
158 }
159#endif
160 return nullptr;
161}
162
163unsigned int sqlite_backend::execute_modification_query(const std::string& query_string)
164{
165#ifdef USE_SQLITE
166 if (!connection_) return 0;
167 std::lock_guard<std::recursive_mutex> lock(sqlite_mutex_);
168 try {
169 sqlite3* db = static_cast<sqlite3*>(connection_);
170 char* error_msg = nullptr;
171
172 int result = sqlite3_exec(db, query_string.c_str(), nullptr, nullptr, &error_msg);
173
174 if (result != SQLITE_OK) {
175 last_error_ = std::string("Modification query failed: ") + (error_msg ? error_msg : "Unknown error");
176 logger_.error("execute_modification_query", last_error_);
177 if (error_msg) sqlite3_free(error_msg);
178 return 0;
179 }
180
181 last_error_.clear();
182 return static_cast<unsigned int>(sqlite3_changes(db));
183 } catch (const std::exception& e) {
184 last_error_ = std::string("Modification query error: ") + e.what();
185 logger_.error("execute_modification_query", last_error_);
186 }
187#else
188 logger_.warning("SQLite support not compiled. Modification query: " + query_string.substr(0, 20) + "...");
189 return 1; // Mock: return 1 affected row
190#endif
191 return 0;
192}
193
194kcenon::common::Result<core::database_result> sqlite_backend::select_query(const std::string& query_string)
195{
196 if (!is_initialized()) {
197 last_error_ = "Backend not initialized";
198 return kcenon::common::error_info{
199 static_cast<int>(database::error_code::invalid_state),
201 "sqlite_backend"
202 };
203 }
204
206
207#ifdef USE_SQLITE
208 if (!connection_) {
209 last_error_ = "No active connection";
210 return kcenon::common::error_info{
213 "sqlite_backend"
214 };
215 }
216 std::lock_guard<std::recursive_mutex> lock(sqlite_mutex_);
217 try {
218 sqlite3* db = static_cast<sqlite3*>(connection_);
219 sqlite3_stmt* stmt = nullptr;
220
221 // Prepare the statement
222 int prepare_result = sqlite3_prepare_v2(db, query_string.c_str(), -1, &stmt, nullptr);
223 if (prepare_result != SQLITE_OK) {
224 last_error_ = std::string("Prepare failed: ") + sqlite3_errmsg(db);
225 logger_.error("select_query", last_error_);
226 return kcenon::common::error_info{
227 static_cast<int>(database::error_code::query_failed),
229 "sqlite_backend"
230 };
231 }
232
233 // Get column count and names
234 int column_count = sqlite3_column_count(stmt);
235 std::vector<std::string> column_names;
236 for (int i = 0; i < column_count; i++) {
237 column_names.push_back(sqlite3_column_name(stmt, i));
238 }
239
240 // Execute and fetch results
241 while (sqlite3_step(stmt) == SQLITE_ROW) {
243
244 for (int i = 0; i < column_count; i++) {
245 const std::string& column_name = column_names[i];
246 row[column_name] = convert_sqlite_value(stmt, i);
247 }
248
249 result.push_back(std::move(row));
250 }
251
252 // Clean up
253 sqlite3_finalize(stmt);
254
255 } catch (const std::exception& e) {
256 last_error_ = std::string("Select query error: ") + e.what();
257 logger_.error("select_query", last_error_);
258 return kcenon::common::error_info{
259 static_cast<int>(database::error_code::query_failed),
261 "sqlite_backend"
262 };
263 }
264#else
265 logger_.warning("SQLite support not compiled. Select query: " + query_string.substr(0, 20) + "...");
266 // Return mock data for testing
267 if (query_string.find("SELECT") != std::string::npos) {
268 core::database_row mock_row;
269 mock_row["id"] = int64_t(1);
270 mock_row["name"] = std::string("sqlite_mock_data");
271 mock_row["active"] = true;
272 result.push_back(mock_row);
273 }
274#endif
275
276 last_error_.clear();
277 return result;
278}
279
280kcenon::common::VoidResult sqlite_backend::execute_query(const std::string& query_string)
281{
282 if (!is_initialized()) {
283 last_error_ = "Backend not initialized";
284 return kcenon::common::error_info{
285 static_cast<int>(database::error_code::invalid_state),
287 "sqlite_backend"
288 };
289 }
290
291#ifdef USE_SQLITE
292 if (!connection_) {
293 last_error_ = "No active SQLite connection";
294 logger_.error("execute_query", last_error_);
295 return kcenon::common::error_info{
298 "sqlite_backend"
299 };
300 }
301
302 std::lock_guard<std::recursive_mutex> lock(sqlite_mutex_);
303 sqlite3* db = static_cast<sqlite3*>(connection_);
304
305 char* error_msg = nullptr;
306 int result = sqlite3_exec(db, query_string.c_str(), nullptr, nullptr, &error_msg);
307
308 if (result != SQLITE_OK) {
309 last_error_ = std::string("Execute error: ") + (error_msg ? error_msg : "Unknown error");
310 logger_.error("execute_query", last_error_);
311 if (error_msg) sqlite3_free(error_msg);
312 return kcenon::common::error_info{
313 static_cast<int>(database::error_code::query_failed),
315 "sqlite_backend"
316 };
317 }
318
319 last_error_.clear();
320 return kcenon::common::ok();
321#else
322 // Mock execution
323 logger_.info("SQLite support not compiled. Mock execute: " + query_string);
324 last_error_.clear();
325 return kcenon::common::ok();
326#endif
327}
328
329kcenon::common::Result<core::database_result> sqlite_backend::select_prepared(
330 const std::string& query,
331 const std::vector<core::database_value>& params)
332{
333 if (!is_initialized()) {
334 last_error_ = "Backend not initialized";
335 return kcenon::common::error_info{
336 static_cast<int>(database::error_code::invalid_state),
338 "sqlite_backend"
339 };
340 }
341
343
344#ifdef USE_SQLITE
345 if (!connection_) {
346 last_error_ = "No active connection";
347 return kcenon::common::error_info{
350 "sqlite_backend"
351 };
352 }
353
354 std::lock_guard<std::recursive_mutex> lock(sqlite_mutex_);
355 sqlite3* db = static_cast<sqlite3*>(connection_);
356 sqlite3_stmt* stmt = nullptr;
357
358 int rc = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr);
359 if (rc != SQLITE_OK) {
360 last_error_ = std::string("Prepare error: ") + sqlite3_errmsg(db);
361 logger_.error("select_prepared", last_error_);
362 return kcenon::common::error_info{
363 static_cast<int>(database::error_code::query_failed),
365 "sqlite_backend"
366 };
367 }
368
369 // Bind parameters
370 for (size_t i = 0; i < params.size(); ++i) {
371 int bind_idx = static_cast<int>(i + 1); // SQLite uses 1-based indexing
372 int bind_rc = SQLITE_OK;
373
374 std::visit([&bind_rc, stmt, bind_idx](const auto& v) {
375 using T = std::decay_t<decltype(v)>;
376 if constexpr (std::is_same_v<T, std::nullptr_t>) {
377 bind_rc = sqlite3_bind_null(stmt, bind_idx);
378 } else if constexpr (std::is_same_v<T, bool>) {
379 bind_rc = sqlite3_bind_int(stmt, bind_idx, v ? 1 : 0);
380 } else if constexpr (std::is_same_v<T, int64_t>) {
381 bind_rc = sqlite3_bind_int64(stmt, bind_idx, v);
382 } else if constexpr (std::is_same_v<T, double>) {
383 bind_rc = sqlite3_bind_double(stmt, bind_idx, v);
384 } else if constexpr (std::is_same_v<T, std::string>) {
385 bind_rc = sqlite3_bind_text(stmt, bind_idx, v.c_str(),
386 static_cast<int>(v.size()), SQLITE_TRANSIENT);
387 }
388 }, params[i]);
389
390 if (bind_rc != SQLITE_OK) {
391 last_error_ = std::string("Bind error at param ") + std::to_string(i + 1)
392 + ": " + sqlite3_errmsg(db);
393 logger_.error("select_prepared", last_error_);
394 sqlite3_finalize(stmt);
395 return kcenon::common::error_info{
396 static_cast<int>(database::error_code::query_failed),
398 "sqlite_backend"
399 };
400 }
401 }
402
403 // Execute and collect results
404 int col_count = sqlite3_column_count(stmt);
405 while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
406 core::database_row db_row;
407 for (int col = 0; col < col_count; ++col) {
408 std::string column_name = sqlite3_column_name(stmt, col);
409 db_row[column_name] = convert_sqlite_value(stmt, col);
410 }
411 result.push_back(std::move(db_row));
412 }
413
414 sqlite3_finalize(stmt);
415
416 if (rc != SQLITE_DONE) {
417 last_error_ = std::string("Step error: ") + sqlite3_errmsg(db);
418 logger_.error("select_prepared", last_error_);
419 return kcenon::common::error_info{
420 static_cast<int>(database::error_code::query_failed),
422 "sqlite_backend"
423 };
424 }
425#else
426 return database_backend::select_prepared(query, params);
427#endif
428
429 last_error_.clear();
430 return result;
431}
432
433kcenon::common::VoidResult sqlite_backend::execute_prepared(
434 const std::string& query,
435 const std::vector<core::database_value>& params)
436{
437 if (!is_initialized()) {
438 last_error_ = "Backend not initialized";
439 return kcenon::common::error_info{
440 static_cast<int>(database::error_code::invalid_state),
442 "sqlite_backend"
443 };
444 }
445
446#ifdef USE_SQLITE
447 if (!connection_) {
448 last_error_ = "No active SQLite connection";
449 logger_.error("execute_prepared", last_error_);
450 return kcenon::common::error_info{
453 "sqlite_backend"
454 };
455 }
456
457 std::lock_guard<std::recursive_mutex> lock(sqlite_mutex_);
458 sqlite3* db = static_cast<sqlite3*>(connection_);
459 sqlite3_stmt* stmt = nullptr;
460
461 int rc = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr);
462 if (rc != SQLITE_OK) {
463 last_error_ = std::string("Prepare error: ") + sqlite3_errmsg(db);
464 logger_.error("execute_prepared", last_error_);
465 return kcenon::common::error_info{
466 static_cast<int>(database::error_code::query_failed),
468 "sqlite_backend"
469 };
470 }
471
472 for (size_t i = 0; i < params.size(); ++i) {
473 int bind_idx = static_cast<int>(i + 1);
474 int bind_rc = SQLITE_OK;
475
476 std::visit([&bind_rc, stmt, bind_idx](const auto& v) {
477 using T = std::decay_t<decltype(v)>;
478 if constexpr (std::is_same_v<T, std::nullptr_t>) {
479 bind_rc = sqlite3_bind_null(stmt, bind_idx);
480 } else if constexpr (std::is_same_v<T, bool>) {
481 bind_rc = sqlite3_bind_int(stmt, bind_idx, v ? 1 : 0);
482 } else if constexpr (std::is_same_v<T, int64_t>) {
483 bind_rc = sqlite3_bind_int64(stmt, bind_idx, v);
484 } else if constexpr (std::is_same_v<T, double>) {
485 bind_rc = sqlite3_bind_double(stmt, bind_idx, v);
486 } else if constexpr (std::is_same_v<T, std::string>) {
487 bind_rc = sqlite3_bind_text(stmt, bind_idx, v.c_str(),
488 static_cast<int>(v.size()), SQLITE_TRANSIENT);
489 }
490 }, params[i]);
491
492 if (bind_rc != SQLITE_OK) {
493 last_error_ = std::string("Bind error at param ") + std::to_string(i + 1)
494 + ": " + sqlite3_errmsg(db);
495 logger_.error("execute_prepared", last_error_);
496 sqlite3_finalize(stmt);
497 return kcenon::common::error_info{
498 static_cast<int>(database::error_code::query_failed),
500 "sqlite_backend"
501 };
502 }
503 }
504
505 rc = sqlite3_step(stmt);
506 sqlite3_finalize(stmt);
507
508 if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
509 last_error_ = std::string("Execute prepared error: ") + sqlite3_errmsg(db);
510 logger_.error("execute_prepared", last_error_);
511 return kcenon::common::error_info{
512 static_cast<int>(database::error_code::query_failed),
514 "sqlite_backend"
515 };
516 }
517
518 last_error_.clear();
519 return kcenon::common::ok();
520#else
521 return database_backend::execute_prepared(query, params);
522#endif
523}
524
525kcenon::common::VoidResult sqlite_backend::begin_transaction()
526{
527 if (!is_initialized()) {
528 last_error_ = "Backend not initialized";
529 return kcenon::common::error_info{
530 static_cast<int>(database::error_code::invalid_state),
532 "sqlite_backend"
533 };
534 }
535
536 if (in_transaction_) {
537 last_error_ = "Transaction already active";
538 return kcenon::common::error_info{
539 static_cast<int>(database::error_code::invalid_state),
541 "sqlite_backend"
542 };
543 }
544
545 auto result = execute_query("BEGIN TRANSACTION");
546 if (result.is_err()) {
547 return result;
548 }
549
550 in_transaction_ = true;
551 last_error_.clear();
552 return kcenon::common::ok();
553}
554
555kcenon::common::VoidResult sqlite_backend::commit_transaction()
556{
557 if (!is_initialized()) {
558 last_error_ = "Backend not initialized";
559 return kcenon::common::error_info{
560 static_cast<int>(database::error_code::invalid_state),
562 "sqlite_backend"
563 };
564 }
565
566 if (!in_transaction_) {
567 last_error_ = "No active transaction";
568 return kcenon::common::error_info{
569 static_cast<int>(database::error_code::invalid_state),
571 "sqlite_backend"
572 };
573 }
574
575 auto result = execute_query("COMMIT");
576 if (result.is_err()) {
577 return result;
578 }
579
580 in_transaction_ = false;
581 last_error_.clear();
582 return kcenon::common::ok();
583}
584
585kcenon::common::VoidResult sqlite_backend::rollback_transaction()
586{
587 if (!is_initialized()) {
588 last_error_ = "Backend not initialized";
589 return kcenon::common::error_info{
590 static_cast<int>(database::error_code::invalid_state),
592 "sqlite_backend"
593 };
594 }
595
596 if (!in_transaction_) {
597 // Not an error - already rolled back or never started
598 return kcenon::common::ok();
599 }
600
601 auto result = execute_query("ROLLBACK");
602 in_transaction_ = false; // Force state reset even on error
603
604 if (result.is_err()) {
605 return result;
606 }
607
608 last_error_.clear();
609 return kcenon::common::ok();
610}
611
613{
614 return in_transaction_;
615}
616
617std::string sqlite_backend::last_error() const
618{
619 return last_error_;
620}
621
622std::map<std::string, std::string> sqlite_backend::connection_info() const
623{
624 std::map<std::string, std::string> info;
625 info["backend"] = "sqlite";
626 info["database"] = connection_config_.database;
627 info["initialized"] = initialized_ ? "true" : "false";
628 info["in_transaction"] = in_transaction_ ? "true" : "false";
629 return info;
630}
631
632} // namespace backends
633} // namespace database
634
635// Auto-registration with backend_registry when SQLite support is compiled in
636#ifdef USE_SQLITE
637namespace {
639}
640#endif
Centralized logging utility for database backends.
core::database_value convert_sqlite_value(void *stmt, int column_index)
Convert SQLite column value to database_value.
core::connection_config connection_config_
Cached connection config.
kcenon::common::VoidResult do_initialize(const core::connection_config &config)
Database-specific initialization logic.
kcenon::common::VoidResult execute_prepared(const std::string &query, const std::vector< core::database_value > &params) override
Execute a parameterized DML/DDL query (prepared statement)
kcenon::common::VoidResult rollback_transaction() override
Rollback the current transaction.
std::map< std::string, std::string > connection_info() const override
Get backend-specific connection information.
kcenon::common::VoidResult begin_transaction() override
Begin a transaction.
kcenon::common::VoidResult do_shutdown()
Database-specific shutdown logic.
unsigned int execute_modification_query(const std::string &query_string)
Execute a modification query (INSERT, UPDATE, DELETE)
kcenon::common::Result< core::database_result > select_prepared(const std::string &query, const std::vector< core::database_value > &params) override
Execute a parameterized SELECT query (prepared statement)
kcenon::common::VoidResult commit_transaction() override
Commit the current transaction.
kcenon::common::VoidResult execute_query(const std::string &query_string) override
Execute a general SQL query (DDL, DML)
bool in_transaction() const override
Check if backend is currently in a transaction.
void * connection_
SQLite connection (sqlite3*)
std::string last_error() const override
Get last error message from backend.
kcenon::common::Result< core::database_result > select_query(const std::string &query_string) override
Execute a SELECT query.
std::string last_error_
Last error message.
std::recursive_mutex sqlite_mutex_
Mutex for thread safety.
std::atomic< bool > in_transaction_
Transaction state.
Helper class for automatic backend registration.
Centralized logging utility for database backends.
std::vector< database_row > database_result
std::map< std::string, database_value > database_row
std::variant< std::string, int64_t, double, bool, std::nullptr_t > database_value
Result<T> type for database_system error handling.
SQLite database backend plugin implementation.
Configuration for database connection.