diff scid/db.c @ 27:dae2de19ca5d

misc: switch to JSON everywhere
author David Demelier <markand@malikania.fr>
date Wed, 03 Aug 2022 15:18:09 +0200
parents lib/db.c@7e10cace67a3
children 081e1c258e64
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scid/db.c	Wed Aug 03 15:18:09 2022 +0200
@@ -0,0 +1,413 @@
+/*
+ * 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 "util.h"
+
+#include "sql/init.h"
+#include "sql/job-add.h"
+#include "sql/job-list.h"
+#include "sql/job-todo.h"
+#include "sql/jobresult-add.h"
+#include "sql/jobresult-list-by-job.h"
+#include "sql/jobresult-list-by-job-group.h"
+#include "sql/jobresult-list-by-worker.h"
+#include "sql/project-find.h"
+#include "sql/project-list.h"
+#include "sql/project-save.h"
+#include "sql/worker-find.h"
+#include "sql/worker-list.h"
+#include "sql/worker-save.h"
+
+#define CHAR(v) (const char *)(v)
+
+static sqlite3 *db;
+
+static json_t *
+project_packer(sqlite3_stmt *stmt)
+{
+	return json_pack("{ss ss ss ss sI}",
+		"name",         sqlite3_column_text(stmt, 0),
+		"desc",         sqlite3_column_text(stmt, 1),
+		"url",          sqlite3_column_text(stmt, 2),
+		"script",       sqlite3_column_text(stmt, 3),
+		"date",         (json_int_t)sqlite3_column_int64(stmt, 4)
+	);
+}
+
+static int
+project_binder(json_t *doc, sqlite3_stmt *stmt)
+{
+	const char *name, *desc, *url, *script;
+	int ret;
+
+	ret = json_unpack(doc, "{ss ss ss ss}",
+		"name",         &name,
+		"desc",         &desc,
+		"url",          &url,
+		"script",       &script
+	);
+
+	if (ret < 0)
+		return -1;
+
+	sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 2, desc, -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 3, url, -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 4, script, -1, SQLITE_STATIC);
+
+	return 0;
+}
+
+static json_t *
+worker_packer(sqlite3_stmt *stmt)
+{
+	return json_pack("{ss ss}",
+		"name",         sqlite3_column_text(stmt, 0),
+		"desc",         sqlite3_column_text(stmt, 1)
+	);
+}
+
+static int
+worker_binder(json_t *doc, sqlite3_stmt *stmt)
+{
+	const char *name, *desc;
+	int ret;
+
+	ret = json_unpack(doc, "{ss ss}",
+		"name",         &name,
+		"desc",         &desc
+	);
+
+	if (ret < 0)
+		return -1;
+
+	sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 2, desc, -1, SQLITE_STATIC);
+
+	return 0;
+}
+
+static json_t *
+job_packer(sqlite3_stmt *stmt)
+{
+	return json_pack("{sI ss ss sI}",
+		"id",           (json_int_t)sqlite3_column_int64(stmt, 0),
+		"tag",          sqlite3_column_text(stmt, 1),
+		"project_name", sqlite3_column_text(stmt, 2),
+		"date",         (json_int_t)sqlite3_column_int64(stmt, 3)
+	);
+}
+
+static int
+job_binder(json_t *doc, sqlite3_stmt *stmt)
+{
+	const char *tag, *project_name;
+	int ret;
+
+	ret = json_unpack(doc, "{ss ss}",
+		"tag",          &tag,
+		"project_name", &project_name
+	);
+
+	if (ret)
+		return -1;
+
+	sqlite3_bind_text(stmt, 1, tag, -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 2, project_name, -1, SQLITE_STATIC);
+
+	return 0;
+}
+
+static json_t *
+jobresult_packer(sqlite3_stmt *stmt)
+{
+	return json_pack("{sI sI ss ss si si sI}",
+		"id",           (json_int_t)sqlite3_column_int64(stmt, 0),
+		"job_id",       (json_int_t)sqlite3_column_int64(stmt, 1),
+		"worker_name",  sqlite3_column_text(stmt, 2),
+		"console",      sqlite3_column_text(stmt, 3),
+		"exitcode",     sqlite3_column_int(stmt, 4),
+		"sigcode",      sqlite3_column_int(stmt, 5),
+		"date",         (json_int_t)sqlite3_column_int64(stmt, 6)
+	);
+}
+
+static int
+jobresult_binder(json_t *doc, sqlite3_stmt *stmt)
+{
+	json_int_t job_id;
+	int exitcode, sigcode, ret;
+	const char *worker_name, *console;
+
+	ret = json_unpack(doc, "{sI ss ss si si}",
+		"job_id",       &job_id,
+		"worker_name",  &worker_name,
+		"console",      &console,
+		"exitcode",     &exitcode,
+		"sigcode",      &sigcode
+	);
+
+	if (ret < 0)
+		return -1;
+
+	sqlite3_bind_int64(stmt, 1, job_id);
+	sqlite3_bind_text(stmt, 2, worker_name, -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 3, console, -1, SQLITE_STATIC);
+	sqlite3_bind_int(stmt, 4, exitcode);
+	sqlite3_bind_int(stmt, 5, sigcode);
+
+	return 0;
+}
+
+static void
+bindva(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 'j':
+			sqlite3_bind_int64(stmt, index++, va_arg(ap, intmax_t));
+			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(int (*binder)(json_t *, sqlite3_stmt *), json_t *obj, const char *sql)
+{
+	assert(binder);
+	assert(obj);
+	assert(sql);
+
+	sqlite3_stmt *stmt = NULL;
+
+	if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK)
+		return log_warn("db: %s", sqlite3_errmsg(db)), -1;
+
+	if (binder(obj, stmt))
+		log_warn("db: unable to bind parameter");
+	else {
+		if (sqlite3_step(stmt) != SQLITE_DONE)
+			log_warn("db: %s", sqlite3_errmsg(db));
+		else
+			json_object_set(obj, "id", json_integer(sqlite3_last_insert_rowid(db)));
+	}
+
+	sqlite3_finalize(stmt);
+
+	return 0;
+}
+
+static json_t *
+listva(json_t * (*unpacker)(sqlite3_stmt *), const char *sql, const char *args, va_list ap)
+{
+	sqlite3_stmt *stmt = NULL;
+	json_t *array, *obj;
+
+	if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK)
+		return log_warn("db: %s", sqlite3_errmsg(db)), NULL;
+
+	bindva(stmt, args, ap);
+	array = json_array();
+
+	while (sqlite3_step(stmt) == SQLITE_ROW)
+		if ((obj = unpacker(stmt)))
+			json_array_append(array, obj);
+
+	sqlite3_finalize(stmt);
+
+	return array;
+}
+
+static json_t *
+list(json_t * (*unpacker)(sqlite3_stmt *), const char *sql, const char *args, ...)
+{
+	va_list ap;
+	json_t *ret;
+
+	va_start(ap, args);
+	ret = listva(unpacker, sql, args, ap);
+	va_end(ap);
+
+	return ret;
+}
+
+/*
+ * Same as list but the array should have only one element that we extract for
+ * convenience.
+ */
+static json_t *
+get(json_t * (*unpacker)(sqlite3_stmt *), const char *sql, const char *args, ...)
+{
+	va_list ap;
+	json_t *ret, *obj = NULL;
+
+	va_start(ap, args);
+	ret = listva(unpacker, sql, args, ap);
+	va_end(ap);
+
+	if (json_array_size(ret) == 1) {
+		obj = json_array_get(ret, 0);
+		json_incref(obj);
+	}
+
+	if (ret)
+		json_decref(ret);
+
+	return obj;
+}
+
+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_job_add(json_t *job)
+{
+	assert(job);
+
+	return insert(job_binder, job, CHAR(sql_job_add));
+}
+
+json_t *
+db_job_todo(const char *worker)
+{
+	assert(worker);
+
+	return list(job_packer, CHAR(sql_job_todo), "ss", worker, worker);
+}
+
+json_t *
+db_job_list(const char *project)
+{
+	assert(project);
+
+	return list(job_packer, CHAR(sql_job_list), "s", project);
+}
+
+int
+db_jobresult_add(json_t *res)
+{
+	assert(res);
+
+	return insert(jobresult_binder, res, CHAR(sql_jobresult_add));
+}
+
+json_t *
+db_jobresult_list_by_job(intmax_t job_id)
+{
+	return list(jobresult_packer, CHAR(sql_jobresult_list_by_job), "j", job_id);
+}
+
+json_t *
+db_jobresult_list_by_job_group(intmax_t job_id)
+{
+	return list(jobresult_packer, CHAR(sql_jobresult_list_by_job_group), "j", job_id);
+}
+
+json_t *
+db_jobresult_list_by_worker(const char *worker)
+{
+	assert(worker);
+
+	return list(jobresult_packer, CHAR(sql_jobresult_list_by_worker), "s", worker);
+}
+
+int
+db_project_save(json_t *p)
+{
+	assert(p);
+
+	return insert(project_binder, p, CHAR(sql_project_save));
+}
+
+json_t *
+db_project_list(void)
+{
+	return list(project_packer, CHAR(sql_project_list), "");
+}
+
+json_t *
+db_project_find(const char *name)
+{
+	return get(project_packer, CHAR(sql_project_find), "s", name);
+}
+
+int
+db_worker_save(json_t *wk)
+{
+	assert(wk);
+
+	return insert(worker_binder, wk, CHAR(sql_worker_save));
+}
+
+json_t *
+db_worker_list(void)
+{
+	return list(worker_packer, CHAR(sql_worker_list), "");
+}
+
+json_t *
+db_worker_find(const char *name)
+{
+	assert(name);
+
+	return get(worker_packer, CHAR(sql_worker_find), "s", name);
+}
+
+void
+db_finish(void)
+{
+	if (db) {
+		sqlite3_close(db);
+		db = NULL;
+	}
+}