view db.c @ 3:215c0c3b3609

misc: use JSON everywhere (scictl/sciwebd)
author David Demelier <markand@malikania.fr>
date Mon, 14 Jun 2021 22:08:24 +0200
parents 5fa3d2f479b2
children 9c4fea43803c
line wrap: on
line source

#include <sys/queue.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include <sqlite3.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-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 str {
	char *str;
	SLIST_ENTRY(str) link;
};

SLIST_HEAD(strlist, str);

static struct strlist *
strlist_new(void)
{
	struct strlist *l;

	l = util_calloc(1, sizeof (*l));
	SLIST_INIT(l);

	return l;
}

static const char *
strlist_add(struct strlist *l, const char *text)
{
	struct str *s;

	s = util_calloc(1, sizeof (*s));
	s->str = util_strdup(text);

	SLIST_INSERT_HEAD(l, s, link);

	return s->str;
}

static void
strlist_free(struct strlist *l)
{
	struct str *s, *tmp;

	SLIST_FOREACH_SAFE(s, l, link, tmp) {
		free(s->str);
		free(s);
	}

	SLIST_INIT(l);
}

static inline void
convert_project(struct db_ctx *ctx, struct project *project, sqlite3_stmt *stmt)
{
	project->id = sqlite3_column_int(stmt, 0);
	project->name = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1)));
	project->desc = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 2)));
	project->url = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 3)));
	project->script = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 4)));
}

static int
insert(const char *sql, const char *fmt, ...)
{
	assert(sql);
	assert(fmt);

	sqlite3_stmt *stmt = NULL;
	va_list ap;

	if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK)
		return log_warn("db: %s", sqlite3_errmsg(db)), -1;

	va_start(ap, fmt);

	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;
		default:
			break;
		}
	}

	va_end(ap);

	if (sqlite3_step(stmt) != SQLITE_DONE) {
		log_warn("db: %s", sqlite3_errmsg(db));
		sqlite3_finalize(stmt);
		return -1;
	}

	return sqlite3_last_insert_rowid(db);
}

int
db_open(const char *path)
{
	assert(path);

	if (sqlite3_open(path, &db) != SQLITE_OK) {
		log_warn("db: open error: %s", sqlite3_errmsg(db));
		return -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) {
		log_warn("db: initialization error: %s", sqlite3_errmsg(db));
		return -1;
	}

	return 0;
}

int
db_project_add(struct project *pj)
{
	assert(pj);

	sqlite3_stmt *stmt = NULL;
	int ret = -1;

	if (sqlite3_prepare(db, CHAR(sql_project_add), -1, &stmt, NULL) != SQLITE_OK)
		goto sqlite3_err;

	sqlite3_bind_text(stmt, 1, pj->name, -1, SQLITE_STATIC);
	sqlite3_bind_text(stmt, 2, pj->desc, -1, SQLITE_STATIC);
	sqlite3_bind_text(stmt, 3, pj->url, -1, SQLITE_STATIC);
	sqlite3_bind_text(stmt, 4, pj->script, -1, SQLITE_STATIC);

	if (sqlite3_step(stmt) != SQLITE_DONE)
		goto sqlite3_err;

	pj->id = sqlite3_last_insert_rowid(db);
	ret = 0;

sqlite3_err:
	if (stmt)
		sqlite3_finalize(stmt);

	return ret;
}

ssize_t
db_project_list(struct db_ctx *ctx, struct project *projects, size_t projectsz)
{
	assert(projects);

	sqlite3_stmt *stmt = NULL;
	struct project *p = projects;
	ssize_t ret = 0;

	if (sqlite3_prepare(db, CHAR(sql_project_list), -1, &stmt, NULL) != SQLITE_OK) {
		log_warn("db: %s", sqlite3_errmsg(db));
		return -1;
	}

	sqlite3_bind_int(stmt, 1, projectsz);
	ctx->handle = strlist_new();

	for (; sqlite3_step(stmt) == SQLITE_ROW && (size_t)ret < projectsz; ++ret, ++p)
		convert_project(ctx, p, stmt);

	if (stmt)
		sqlite3_finalize(stmt);

	return ret;
}

int
db_project_find(struct db_ctx *ctx, struct project *project)
{
	assert(ctx);
	assert(project);

	sqlite3_stmt *stmt = NULL;
	int ret = -1;

	ctx->handle = NULL;

	if (sqlite3_prepare(db, CHAR(sql_project_find), -1, &stmt, NULL) != SQLITE_OK)
		goto sqlite3_err;

	sqlite3_bind_text(stmt, 1, project->name, -1, SQLITE_STATIC);

	if (sqlite3_step(stmt) != SQLITE_ROW)
		goto sqlite3_err;

	ret = 0;
	ctx->handle = strlist_new();
	convert_project(ctx, project, stmt);

sqlite3_err:
	if (ret < 0) {
		if (ctx->handle)
			db_ctx_finish(ctx);

		log_warn("db: %s", sqlite3_errmsg(db));
	}
	if (stmt)
		sqlite3_finalize(stmt);

	return ret;
}

int
db_project_find_id(struct db_ctx *ctx, struct project *project)
{
	assert(ctx);
	assert(project);

	sqlite3_stmt *stmt = NULL;
	int ret = -1;

	ctx->handle = NULL;

	if (sqlite3_prepare(db, CHAR(sql_project_find_id), -1, &stmt, NULL) != SQLITE_OK)
		goto sqlite3_err;

	sqlite3_bind_int(stmt, 1, project->id);

	if (sqlite3_step(stmt) != SQLITE_ROW)
		goto sqlite3_err;

	ret = 0;
	ctx->handle = strlist_new();
	convert_project(ctx, project, stmt);

sqlite3_err:
	if (ret < 0) {
		if (ctx->handle)
			db_ctx_finish(ctx);

		log_warn("db: %s", sqlite3_errmsg(db));
	}
	if (stmt)
		sqlite3_finalize(stmt);

	return ret;
}

int
db_worker_add(struct worker *wk)
{
	assert(wk);

	sqlite3_stmt *stmt = NULL;
	int ret = -1;

	if (sqlite3_prepare(db, CHAR(sql_worker_add), -1, &stmt, NULL) != SQLITE_OK)
		goto sqlite3_err;

	sqlite3_bind_text(stmt, 1, wk->name, -1, SQLITE_STATIC);
	sqlite3_bind_text(stmt, 2, wk->desc, -1, SQLITE_STATIC);

	if (sqlite3_step(stmt) != SQLITE_DONE)
		goto sqlite3_err;

	wk->id = sqlite3_last_insert_rowid(db);
	ret = 0;

sqlite3_err:
	if (stmt)
		sqlite3_finalize(stmt);

	return ret;
}

ssize_t
db_worker_list(struct db_ctx *ctx, struct worker *wk, size_t wksz)
{
	assert(ctx);
	assert(wk);

	sqlite3_stmt *stmt = NULL;
	struct worker *w = wk;
	ssize_t ret = -1;

	ctx->handle = NULL;

	if (sqlite3_prepare(db, CHAR(sql_worker_list), -1, &stmt, NULL) != SQLITE_OK)
		goto sqlite3_err;

	sqlite3_bind_int(stmt, 1, wksz);
	ctx->handle = strlist_new();

	for (ret = 0; sqlite3_step(stmt) == SQLITE_ROW && (size_t)ret < wksz; ++ret, ++w) {
		w->id = sqlite3_column_int(stmt, 0);
		w->name = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1)));
		w->desc = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 2)));
	}

sqlite3_err:
	if (ret < 0) {
		if (ctx->handle)
			db_ctx_finish(ctx);

		log_warn("db: %s", sqlite3_errmsg(db));
	}
	if (stmt)
		sqlite3_finalize(stmt);

	return ret;
}

int
db_worker_find(struct db_ctx *ctx, struct worker *w)
{
	assert(ctx);
	assert(w);

	sqlite3_stmt *stmt = NULL;
	int ret = -1;

	ctx->handle = NULL;

	if (sqlite3_prepare(db, CHAR(sql_worker_find), -1, &stmt, NULL) != SQLITE_OK)
		goto sqlite3_err;

	sqlite3_bind_text(stmt, 1, w->name, -1, SQLITE_STATIC);

	if (sqlite3_step(stmt) != SQLITE_ROW)
		goto sqlite3_err;

	ret = 0;
	ctx->handle = strlist_new();
	w->id = sqlite3_column_int(stmt, 0);
	w->name = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1)));
	w->desc = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 2)));

sqlite3_err:
	if (ret < 0) {
		if (ctx->handle)
			db_ctx_finish(ctx);

		log_warn("db: %s", sqlite3_errmsg(db));
	}
	if (stmt)
		sqlite3_finalize(stmt);

	return ret;
}

int
db_worker_find_id(struct db_ctx *ctx, struct worker *w)
{
	assert(ctx);
	assert(w);

	sqlite3_stmt *stmt = NULL;
	int ret = -1;

	ctx->handle = NULL;

	if (sqlite3_prepare(db, CHAR(sql_worker_find_id), -1, &stmt, NULL) != SQLITE_OK)
		goto sqlite3_err;

	sqlite3_bind_int(stmt, 1, w->id);

	if (sqlite3_step(stmt) != SQLITE_ROW)
		goto sqlite3_err;

	ret = 0;
	ctx->handle = strlist_new();
	w->id = sqlite3_column_int(stmt, 0);
	w->name = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1)));
	w->desc = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 2)));

sqlite3_err:
	if (ret < 0) {
		if (ctx->handle)
			db_ctx_finish(ctx);

		log_warn("db: %s", sqlite3_errmsg(db));
	}
	if (stmt)
		sqlite3_finalize(stmt);

	return ret;
}

int
db_job_add(struct job *job)
{
	assert(job);

	job->id = insert(CHAR(sql_job_add), "si", job->tag, job->project_id);

	return job->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);

	sqlite3_stmt *stmt = NULL;
	ssize_t ret = 0;

	if (sqlite3_prepare(db, CHAR(sql_job_todo), -1, &stmt, NULL) != SQLITE_OK) {
		log_warn("db: %s", sqlite3_errmsg(db));
		return -1;
	}

	sqlite3_bind_int(stmt, 1, worker_id);
	sqlite3_bind_int(stmt, 2, jobsz);
	ctx->handle = strlist_new();

	while (sqlite3_step(stmt) == SQLITE_ROW && (size_t)ret++ < jobsz) {
		jobs->id = sqlite3_column_int(stmt, 0);
		jobs->tag = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1)));
		jobs++->project_id = sqlite3_column_int(stmt, 2);
	};

	sqlite3_finalize(stmt);

	return ret;
}

int
db_jobresult_add(struct jobresult *r)
{
	assert(r);

	r->id = insert(CHAR(sql_jobresult_add), "iiis", r->job_id,
	    r->worker_id, r->exitcode, r->log);

	return r->id < 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;
	}
}