From 662d78b9a5bebdb98dc178ebd1f8e3384793c104 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Sun, 17 Mar 2019 15:30:43 +0200 Subject: Initial commit --- src/db.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 src/db.c (limited to 'src/db.c') 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 +#include +#include +#include + +#include +#include + +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}; +} -- cgit v1.2.3