summaryrefslogtreecommitdiff
path: root/core/sql.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/sql.c')
-rw-r--r--core/sql.c105
1 files changed, 105 insertions, 0 deletions
diff --git a/core/sql.c b/core/sql.c
new file mode 100644
index 0000000..59fc38e
--- /dev/null
+++ b/core/sql.c
@@ -0,0 +1,105 @@
+// file : core/sql.c
+// project : Salis-VM
+// author : Paul Oliver <contact@pauloliver.dev>
+
+// index:
+// [section] includes
+// [section] macros
+// [section] globals
+// [section] definitions
+
+// ----------------------------------------------------------------------------
+// [section] includes
+// ----------------------------------------------------------------------------
+#include <assert.h>
+#include <sqlite3.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "logger.h"
+#include "sql.h"
+
+// ----------------------------------------------------------------------------
+// [section] macros
+// ----------------------------------------------------------------------------
+#define DATA_PUSH_BUSY_TIMEOUT 600000
+
+// ----------------------------------------------------------------------------
+// [section] globals
+// ----------------------------------------------------------------------------
+sqlite3 *g_sim_db;
+
+// ----------------------------------------------------------------------------
+// [section] definitions
+// ----------------------------------------------------------------------------
+void sql_open(void) {
+ sqlite3_open(DATA_PUSH_PATH, &g_sim_db);
+ assert(g_sim_db);
+
+ // Install busy handler to retry transactions if DB is locked
+ sqlite3_busy_timeout(g_sim_db, DATA_PUSH_BUSY_TIMEOUT);
+
+ // Enable Write-Ahead Logging (WAL)
+ // This seems to help prevent DB locks when displaying live data.
+ // See: https://sqlite.org/wal.html
+ sql_exec(NULL, NULL, "pragma journal_mode=wal;");
+}
+
+void sql_close(void) {
+ assert(g_sim_db);
+ sqlite3_close(g_sim_db);
+}
+
+void sql_exec(void (*callback)(sqlite3_stmt *sql_stmt, void *data), void *data, const char *sql_format, ...) {
+ assert(sql_format);
+
+ va_list args;
+ va_start(args, sql_format);
+ int sql_len = vsnprintf(NULL, 0, sql_format, args) + 1;
+ char *sql_str = malloc(sql_len);
+ assert(sql_str);
+ va_end(args);
+
+ va_start(args, sql_format);
+ vsprintf(sql_str, sql_format, args);
+ va_end(args);
+
+ int sql_res;
+ sqlite3_stmt *sql_stmt;
+
+ sql_res = sqlite3_prepare_v2(g_sim_db, sql_str, -1, &sql_stmt, NULL);
+ assert(sql_res == SQLITE_OK);
+ free(sql_str);
+
+ while (true) {
+ sql_res = sqlite3_step(sql_stmt);
+
+ if (sql_res == SQLITE_ROW) {
+ if (callback) {
+ callback(sql_stmt, data);
+ }
+
+ continue;
+ }
+
+ if (sql_res == SQLITE_DONE) {
+ break;
+ }
+
+ log_attention("SQLite database returned error %d with message:", sql_res);
+ log_attention(sqlite3_errmsg(g_sim_db));
+
+ // Only handle SQLITE_BUSY error, in which case we retry the query.
+ // Setting 'journal_mode=wal;' should help prevent busy database errors.
+ if (sql_res == SQLITE_BUSY) {
+ log_info("Will retry query");
+ continue;
+ }
+
+ assert(false);
+ }
+
+ sqlite3_finalize(sql_stmt);
+}