diff lib/db.c @ 19:de4bf839b565

misc: revamp SQL
author David Demelier <markand@malikania.fr>
date Fri, 15 Jul 2022 11:11:48 +0200
parents
children f98ea578b1ef
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/db.c	Fri Jul 15 11:11:48 2022 +0200
@@ -0,0 +1,404 @@
+/*
+ * db.c -- scid database access
+ *
+ * Copyright (c) 2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sqlite3.h>
+
+#include <utlist.h>
+
+#include "db.h"
+#include "log.h"
+#include "types.h"
+#include "util.h"
+
+#include "sql/init.h"
+#include "sql/job-add.h"
+#include "sql/job-todo.h"
+#include "sql/jobresult-add.h"
+#include "sql/project-add.h"
+#include "sql/project-update.h"
+#include "sql/project-find.h"
+#include "sql/project-find-id.h"
+#include "sql/project-list.h"
+#include "sql/worker-add.h"
+#include "sql/worker-find.h"
+#include "sql/worker-find-id.h"
+#include "sql/worker-list.h"
+
+#define CHAR(v) (const char *)(v)
+
+static sqlite3 *db;
+
+typedef void (*unpacker)(sqlite3_stmt *, struct db_ctx *, void *);
+
+struct str {
+	char *str;
+	struct str *next;
+};
+
+struct list {
+	unpacker unpack;
+	void *data;
+	size_t datasz;
+	size_t elemwidth;
+	struct db_ctx *ctx;
+};
+
+static const char *
+strlist_add(struct db_ctx *ctx, const char *text)
+{
+	struct str *s, *list = ctx->handle;
+
+	s = util_calloc(1, sizeof (*s));
+	s->str = util_strdup(text);
+	LL_APPEND(list, s);
+
+	return s->str;
+}
+
+static void
+strlist_free(struct db_ctx *ctx)
+{
+	struct str *s, *tmp, *list = ctx->handle;
+
+	LL_FOREACH_SAFE(list, s, tmp) {
+		free(s->str);
+		free(s);
+	}
+
+	ctx->handle = NULL;
+}
+
+static void
+project_unpacker(sqlite3_stmt *stmt, struct db_ctx *ctx, struct project *project)
+{
+	project->id = sqlite3_column_int(stmt, 0);
+	project->name = strlist_add(ctx, CHAR(sqlite3_column_text(stmt, 1)));
+	project->desc = strlist_add(ctx, CHAR(sqlite3_column_text(stmt, 2)));
+	project->url = strlist_add(ctx, CHAR(sqlite3_column_text(stmt, 3)));
+	project->script = strlist_add(ctx, CHAR(sqlite3_column_text(stmt, 4)));
+}
+
+static void
+worker_unpacker(sqlite3_stmt *stmt, struct db_ctx *ctx, struct worker *w)
+{
+	w->id = sqlite3_column_int(stmt, 0);
+	w->name = strlist_add(ctx, CHAR(sqlite3_column_text(stmt, 1)));
+	w->desc = strlist_add(ctx, CHAR(sqlite3_column_text(stmt, 2)));
+}
+
+static void
+job_unpacker(sqlite3_stmt *stmt, struct db_ctx *ctx, struct job *job)
+{
+	job->id = sqlite3_column_int(stmt, 0);
+	job->tag = strlist_add(ctx, CHAR(sqlite3_column_text(stmt, 1)));
+	job->project_id = sqlite3_column_int(stmt, 2);
+}
+
+static void
+vbind(sqlite3_stmt *stmt, const char *fmt, va_list ap)
+{
+	for (int index = 1; *fmt; ++fmt) {
+		switch (*fmt) {
+		case 'i':
+			sqlite3_bind_int(stmt, index++, va_arg(ap, int));
+			break;
+		case 's':
+			sqlite3_bind_text(stmt, index++, va_arg(ap, const char *), -1, SQLITE_STATIC);
+			break;
+		case 'z':
+			sqlite3_bind_int64(stmt, index++, va_arg(ap, size_t));
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+static int
+insert(const char *sql, const char *fmt, ...)
+{
+	assert(sql);
+	assert(fmt);
+
+	sqlite3_stmt *stmt = NULL;
+	va_list ap;
+	int ret = -1;
+
+	if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK)
+		return log_warn("db: %s", sqlite3_errmsg(db)), -1;
+
+	va_start(ap, fmt);
+	vbind(stmt, fmt, ap);
+	va_end(ap);
+
+	if (sqlite3_step(stmt) != SQLITE_DONE)
+		log_warn("db: %s", sqlite3_errmsg(db));
+	else
+		ret = sqlite3_last_insert_rowid(db);
+
+	sqlite3_finalize(stmt);
+
+	return ret;
+}
+
+static int
+update(const char *sql, const char *fmt, ...)
+{
+	assert(sql);
+	assert(fmt);
+
+	sqlite3_stmt *stmt = NULL;
+	va_list ap;
+	int ret = 1;
+
+	if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK)
+		return log_warn("db: %s", sqlite3_errmsg(db)), -1;
+
+	va_start(ap, fmt);
+	vbind(stmt, fmt, ap);
+	va_end(ap);
+
+	if (sqlite3_step(stmt) != SQLITE_DONE)
+		log_warn("db: %s", sqlite3_errmsg(db));
+	else
+		ret = 0;
+
+	sqlite3_finalize(stmt);
+
+	return ret;
+}
+
+static ssize_t
+list(struct list *sel, const char *sql, const char *args, ...)
+{
+	sqlite3_stmt *stmt = NULL;
+
+	va_list ap;
+	int step;
+	ssize_t ret = -1;
+	size_t tot = 0;
+
+	sel->ctx->handle = NULL;
+
+	if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK)
+		return log_warn("db: %s", sqlite3_errmsg(db)), -1;
+
+	va_start(ap, args);
+	vbind(stmt, args, ap);
+	va_end(ap);
+
+	while (tot < sel->datasz && (step = sqlite3_step(stmt)) == SQLITE_ROW)
+		sel->unpack(stmt, sel->ctx, (unsigned char *)sel->data + (tot++ * sel->elemwidth));
+
+	if (step == SQLITE_OK || step == SQLITE_DONE || step == SQLITE_ROW)
+		ret = tot;
+	else {
+		memset(sel->data, 0, sel->datasz * sel->elemwidth);
+		strlist_free(sel->ctx->handle);
+		sel->ctx->handle = NULL;
+	}
+
+	sqlite3_finalize(stmt);
+
+	return ret;
+}
+
+int
+db_open(const char *path)
+{
+	assert(path);
+
+	if (sqlite3_open(path, &db) != SQLITE_OK)
+		return log_warn("db: open error: %s", sqlite3_errmsg(db)), -1;
+
+	/* Wait for 30 seconds to lock the database. */
+	sqlite3_busy_timeout(db, 30000);
+
+	if (sqlite3_exec(db, CHAR(sql_init), NULL, NULL, NULL) != SQLITE_OK)
+		return log_warn("db: initialization error: %s", sqlite3_errmsg(db)), -1;
+
+	return 0;
+}
+
+int
+db_project_add(struct project *p)
+{
+	return (p->id = insert(CHAR(sql_project_add), "ssss", p->name, p->desc,
+	    p->url, p->script)) < 0 ? -1 : 0;
+}
+
+int
+db_project_update(const struct project *p)
+{
+	assert(p);
+
+	return update(CHAR(sql_project_update), "ssssi", p->name, p->desc,
+	    p->url, p->script, p->id);
+}
+
+ssize_t
+db_project_list(struct db_ctx *ctx, struct project *projects, size_t projectsz)
+{
+	struct list sel = {
+		.unpack = (unpacker)project_unpacker,
+		.data = projects,
+		.datasz = projectsz,
+		.elemwidth = sizeof (*projects),
+		.ctx = ctx
+	};
+
+	return list(&sel, CHAR(sql_project_list), "z", projectsz);
+}
+
+int
+db_project_find(struct db_ctx *ctx, struct project *project)
+{
+	struct list sel = {
+		.unpack = (unpacker)project_unpacker,
+		.data = project,
+		.datasz = 1,
+		.elemwidth = sizeof (*project),
+		.ctx = ctx
+	};
+
+	return list(&sel, CHAR(sql_project_find), "s", project->name) == 1 ? 0 : -1;
+}
+
+int
+db_project_find_id(struct db_ctx *ctx, struct project *project)
+{
+	struct list sel = {
+		.unpack = (unpacker)project_unpacker,
+		.data = project,
+		.datasz = 1,
+		.elemwidth = sizeof (*project),
+		.ctx = ctx
+	};
+
+	return list(&sel, CHAR(sql_project_find_id), "i", project->id) == 1 ? 0 : -1;
+}
+
+int
+db_worker_add(struct worker *wk)
+{
+	assert(wk);
+
+	return (wk->id = insert(CHAR(sql_worker_add), "ss", wk->name, wk->desc)) < 0 ? -1 : 0;
+}
+
+ssize_t
+db_worker_list(struct db_ctx *ctx, struct worker *wk, size_t wksz)
+{
+	assert(ctx);
+	assert(wk);
+
+	struct list sel = {
+		.unpack = (unpacker)worker_unpacker,
+		.data = wk,
+		.datasz = wksz,
+		.elemwidth = sizeof (*wk),
+		.ctx = ctx
+	};
+
+	return list(&sel, CHAR(sql_worker_list), "z", wksz);
+}
+
+int
+db_worker_find(struct db_ctx *ctx, struct worker *wk)
+{
+	struct list sel = {
+		.unpack = (unpacker)worker_unpacker,
+		.data = wk,
+		.datasz = 1,
+		.elemwidth = sizeof (*wk),
+		.ctx = ctx
+	};
+
+	return list(&sel, CHAR(sql_worker_find), "s", wk->name) == 1 ? 0 : -1;
+}
+
+int
+db_worker_find_id(struct db_ctx *ctx, struct worker *wk)
+{
+	struct list sel = {
+		.unpack = (unpacker)worker_unpacker,
+		.data = wk,
+		.datasz = 1,
+		.elemwidth = sizeof (*wk),
+		.ctx = ctx
+	};
+
+	return list(&sel, CHAR(sql_worker_find_id), "i", wk->id) == 1 ? 0 : -1;
+}
+
+int
+db_job_add(struct job *job)
+{
+	assert(job);
+
+	return (job->id = insert(CHAR(sql_job_add),
+	    "si", job->tag, job->project_id)) < 0 ? -1 : 0;
+}
+
+ssize_t
+db_job_todo(struct db_ctx *ctx, struct job *jobs, size_t jobsz, int worker_id)
+{
+	assert(ctx);
+	assert(jobs);
+
+	struct list sel = {
+		.unpack = (unpacker)job_unpacker,
+		.data = jobs,
+		.datasz = jobsz,
+		.elemwidth = sizeof (*jobs),
+		.ctx = ctx
+	};
+
+	return list(&sel, CHAR(sql_job_todo), "iiz", worker_id, worker_id, jobsz);
+}
+
+int
+db_jobresult_add(struct jobresult *r)
+{
+	assert(r);
+
+	return (r->id = insert(CHAR(sql_jobresult_add), "iiis", r->job_id,
+	    r->worker_id, r->exitcode, r->log)) < 0 ? -1 : 0;
+}
+
+void
+db_finish(void)
+{
+	if (db) {
+		sqlite3_close(db);
+		db = NULL;
+	}
+}
+
+void
+db_ctx_finish(struct db_ctx *ctx)
+{
+	if (ctx->handle) {
+		strlist_free(ctx->handle);
+		ctx->handle = NULL;
+	}
+}