view lib/db.c @ 20:f98ea578b1ef

misc: revamp database
author David Demelier <markand@malikania.fr>
date Tue, 19 Jul 2022 21:52:42 +0200
parents de4bf839b565
children dd078aea5d02
line wrap: on
line source

/*
 * 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;

struct list {
	void (*unpack)(sqlite3_stmt *, void *);
	void *data;
	size_t datasz;
	size_t elemwidth;
};

static void
project_unpacker(sqlite3_stmt *stmt, void *data)
{
	struct project *project = data;

	project->id = sqlite3_column_int(stmt, 0);
	project->name = util_strdup(CHAR(sqlite3_column_text(stmt, 1)));
	project->desc = util_strdup(CHAR(sqlite3_column_text(stmt, 2)));
	project->url = util_strdup(CHAR(sqlite3_column_text(stmt, 3)));
	project->script = util_strdup(CHAR(sqlite3_column_text(stmt, 4)));
}

static void
worker_unpacker(sqlite3_stmt *stmt, void *data)
{
	struct worker *w = data;

	w->id = sqlite3_column_int(stmt, 0);
	w->name = util_strdup(CHAR(sqlite3_column_text(stmt, 1)));
	w->desc = util_strdup(CHAR(sqlite3_column_text(stmt, 2)));
}

static void
job_unpacker(sqlite3_stmt *stmt, void *data)
{
	struct job *job = data;

	job->id = sqlite3_column_int(stmt, 0);
	job->tag = util_strdup(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 '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(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;

	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, (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);

	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 project *projects, size_t projectsz)
{
	struct list sel = {
		.unpack = project_unpacker,
		.data = projects,
		.datasz = projectsz,
		.elemwidth = sizeof (*projects),
	};

	return list(&sel, CHAR(sql_project_list), "z", projectsz);
}

int
db_project_find(struct project *project, const char *name)
{
	struct list sel = {
		.unpack = project_unpacker,
		.data = project,
		.datasz = 1,
		.elemwidth = sizeof (*project),
	};

	return list(&sel, CHAR(sql_project_find), "s", name) == 1 ? 0 : -1;
}

int
db_project_find_id(struct project *project, intmax_t id)
{
	struct list sel = {
		.unpack = project_unpacker,
		.data = project,
		.datasz = 1,
		.elemwidth = sizeof (*project),
	};

	return list(&sel, CHAR(sql_project_find_id), "i", 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 worker *wk, size_t wksz)
{
	assert(wk);

	struct list sel = {
		.unpack = worker_unpacker,
		.data = wk,
		.datasz = wksz,
		.elemwidth = sizeof (*wk),
	};

	return list(&sel, CHAR(sql_worker_list), "z", wksz);
}

int
db_worker_find(struct worker *wk, const char *name)
{
	struct list sel = {
		.unpack = worker_unpacker,
		.data = wk,
		.datasz = 1,
		.elemwidth = sizeof (*wk),
	};

	return list(&sel, CHAR(sql_worker_find), "s", name) == 1 ? 0 : -1;
}

int
db_worker_find_id(struct worker *wk, intmax_t id)
{
	struct list sel = {
		.unpack = worker_unpacker,
		.data = wk,
		.datasz = 1,
		.elemwidth = sizeof (*wk),
	};

	return list(&sel, CHAR(sql_worker_find_id), "i", 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 job *jobs, size_t jobsz, int worker_id)
{
	assert(jobs);

	struct list sel = {
		.unpack = job_unpacker,
		.data = jobs,
		.datasz = jobsz,
		.elemwidth = sizeof (*jobs),
	};

	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;
	}
}