summaryrefslogtreecommitdiff
path: root/src/db.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/db.c')
-rw-r--r--src/db.c153
1 files changed, 153 insertions, 0 deletions
diff --git a/src/db.c b/src/db.c
new file mode 100644
index 0000000..76ebaf7
--- /dev/null
+++ b/src/db.c
@@ -0,0 +1,153 @@
+#include "db.h"
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+#include <assert.h>
+
+#include <sqlite3.h>
+#include <unistd.h>
+
+static int
+busy_handler(void *dummy, int times_invoked)
+{
+ (void)dummy;
+ uint32_t sleep_s = (times_invoked > 4 ? 4 : times_invoked) + 1;
+ warnx("sqlite3 db is busy :(... sleeping for %u seconds", sleep_s);
+ sleep(sleep_s);
+ return 1;
+}
+
+void
+db_init(struct db *db, const char *target)
+{
+ assert(db && target);
+ *db = (struct db){0};
+
+ if (sqlite3_open(target, (sqlite3**)&db->handle) != SQLITE_OK)
+ errx(EXIT_FAILURE, "sqlite3_open: %s", sqlite3_errmsg(db->handle));
+
+ if (sqlite3_busy_handler(db->handle, busy_handler, NULL) != SQLITE_OK)
+ errx(EXIT_FAILURE, "sqlite3_busy_handler");
+}
+
+static struct db_query_arg
+stmt_column_to_arg(sqlite3_stmt *stmt, const int index)
+{
+ assert(stmt);
+ switch (sqlite3_column_type(stmt, index)) {
+ case SQLITE_NULL:
+ return (struct db_query_arg){ .type = DB_ARG_NULL };
+ case SQLITE_INTEGER:
+ return (struct db_query_arg){ .u.i32 = sqlite3_column_int(stmt, index), .type = DB_ARG_I32 };
+ case SQLITE_FLOAT:
+ return (struct db_query_arg){ .u.f64 = sqlite3_column_double(stmt, index), .type = DB_ARG_F64 };
+ case SQLITE_BLOB:
+ return (struct db_query_arg){ .u.blob = { .data = sqlite3_column_blob(stmt, index), .sz = sqlite3_column_bytes(stmt, index) }, .type = DB_ARG_BLOB };
+ case SQLITE_TEXT:
+ return (struct db_query_arg){ .u.blob = { .data = sqlite3_column_text(stmt, index), .sz = sqlite3_column_bytes(stmt, index) }, .type = DB_ARG_UTF8 };
+ }
+ assert(0 && "should not happen");
+ return (struct db_query_arg){0};
+}
+
+static int
+bind_arg_to_stmt(const struct db_query_arg *arg, const int index, sqlite3_stmt *stmt)
+{
+ assert(arg && stmt);
+ switch (arg->type) {
+ case DB_ARG_NULL:
+ return sqlite3_bind_null(stmt, index);
+ case DB_ARG_I32:
+ return sqlite3_bind_int(stmt, index, arg->u.i32);
+ case DB_ARG_F64:
+ return sqlite3_bind_double(stmt, index, arg->u.f64);
+ case DB_ARG_BLOB:
+ if (arg->u.blob.sz && !arg->u.blob.data)
+ return sqlite3_bind_zeroblob(stmt, index, arg->u.blob.sz);
+ return sqlite3_bind_blob(stmt, index, arg->u.blob.data, arg->u.blob.sz, NULL);
+ case DB_ARG_UTF8:
+ assert(!arg->u.blob.sz || arg->u.blob.data);
+ return sqlite3_bind_text64(stmt, index, arg->u.blob.data, arg->u.blob.sz, NULL, SQLITE_UTF8);
+ }
+ assert(0 && "should not happen");
+ return SQLITE_ERROR;
+}
+
+struct db_query
+db_query_begin(struct db *db, const char *sql, const struct db_query_arg args[], const int num_args)
+{
+ assert(db && sql);
+ assert(args || !num_args);
+
+ sqlite3_stmt *stmt;
+ if (sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL) != SQLITE_OK)
+ errx(EXIT_FAILURE, "sqlite3_prepare_v2: %s", sqlite3_errmsg(db->handle));
+
+ for (int i = 0; i < num_args; ++i) {
+ if (bind_arg_to_stmt(&args[i], i + 1, stmt) != SQLITE_OK)
+ errx(EXIT_FAILURE, "sqlite3_bind: %s", sqlite3_errmsg(db->handle));
+ }
+
+ return (struct db_query){stmt};
+}
+
+int
+db_query_fetch(struct db_query *query, struct db_query_arg columns[], const int num_columns)
+{
+ assert(query);
+ assert(columns || !num_columns);
+
+ int ret;
+ if ((ret = sqlite3_step(query->handle)) != SQLITE_ROW && ret != SQLITE_DONE)
+ errx(EXIT_FAILURE, "sqlite3_step: %s", sqlite3_errstr(ret));
+
+ if (ret == SQLITE_DONE)
+ return 0;
+
+ int count = sqlite3_column_count(query->handle);
+
+ if (!num_columns)
+ return count;
+
+ count = (count > num_columns ? num_columns : count);
+ for (int i = 0; i < count; ++i)
+ columns[i] = stmt_column_to_arg(query->handle, i);
+
+ return count;
+}
+
+void
+db_query_end(struct db_query *query)
+{
+ if (!query)
+ return;
+
+ sqlite3_finalize(query->handle);
+ *query = (struct db_query){0};
+}
+
+int
+db_query_single(struct db *db, const char *sql, const struct db_query_arg args[], const int num_args, struct db_query_arg columns[], const int num_columns)
+{
+ db_query_end(&db->single);
+ db->single = db_query_begin(db, sql, args, num_args);
+ return db_query_fetch(&db->single, columns, num_columns);
+}
+
+void
+db_gc(struct db *db)
+{
+ assert(db);
+ db_query_end(&db->single);
+}
+
+void
+db_release(struct db *db)
+{
+ if (!db)
+ return;
+
+ db_query_end(&db->single);
+ sqlite3_close(db->handle);
+ *db = (struct db){0};
+}