diff options
Diffstat (limited to 'db.c')
| -rw-r--r-- | db.c | 188 |
1 files changed, 188 insertions, 0 deletions
@@ -0,0 +1,188 @@ +/* + * nanobbs, a tiny forums software. + * Copyright (C) 2025-2026 Xavier Del Campo Romero + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "db.h" +#include "defs.h" +#include <dynstr.h> +#include <sqlite3.h> +#include <sys/stat.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static int column(sqlite3 *const db, sqlite3_stmt *const stmt, + const char *const name) +{ + const int n = sqlite3_data_count(stmt); + + if (!n) + { + fprintf(stderr, "%s: sqlite3_data_count: %s\n", __func__, + sqlite3_errmsg(db)); + return -1; + } + + for (int i = 0; i < n; i++) + { + const char *const col = sqlite3_column_name(stmt, i); + + if (!col) + { + fprintf(stderr, "%s: sqlite3_column_name: %s\n", __func__, + sqlite3_errmsg(db)); + return -1; + } + else if (!strcmp(col, name)) + return i; + } + + fprintf(stderr, "%s: could not find column \"%s\"\n", __func__, name); + return -1; +} + +int db_int(sqlite3 *const db, sqlite3_stmt *const stmt, const char *const name, + int *const out) +{ + const int col = column(db, stmt, name); + + if (col < 0) + return -1; + + *out = sqlite3_column_int(stmt, col); + return 0; +} + +int db_uint(sqlite3 *const db, sqlite3_stmt *const stmt, const char *const name, + unsigned *const out) +{ + int v; + + if (db_int(db, stmt, name, &v)) + return -1; + else if (v < 0) + { + fprintf(stderr, "%s: unexpected negative value for %s: %d\n", __func__, + name, v); + return -1; + } + + *out = v; + return 0; +} + +int db_bigint(sqlite3 *const db, sqlite3_stmt *const stmt, + const char *const name, long long *const out) +{ + const int col = column(db, stmt, name); + + if (col < 0) + return -1; + + *out = sqlite3_column_int64(stmt, col); + return 0; +} + +int db_biguint(sqlite3 *const db, sqlite3_stmt *const stmt, + const char *const name, unsigned long long *const out) +{ + const int col = column(db, stmt, name); + sqlite3_int64 v; + + if (col < 0 || (v = sqlite3_column_int64(stmt, col)) < 0) + return -1; + + *out = v; + return 0; +} + +char *db_str(sqlite3 *const db, sqlite3_stmt *const stmt, + const char *const name) +{ + char *ret = NULL; + const int col = column(db, stmt, name); + + if (col < 0) + return NULL; + + const unsigned char *const s = sqlite3_column_text(stmt, col); + + if (!s) + { + fprintf(stderr, "%s: sqlite3_column_text: %s\n", __func__, + sqlite3_errmsg(db)); + goto failure; + } + else if (!(ret = strndup((const char *)s, sqlite3_column_bytes(stmt, col)))) + { + fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno)); + goto failure; + } + + return ret; + +failure: + free(ret); + return NULL; +} + +int db_rollback(sqlite3 *const db) +{ + char *msg = NULL; + const int e = sqlite3_exec(db, "ROLLBACK;", NULL, NULL, &msg); + + if (e != SQLITE_OK) + fprintf(stderr, "%s: sqlite3_exec: %s, msg=%s\n", __func__, + sqlite3_errstr(e), msg); + + sqlite3_free(msg); + return e != SQLITE_OK; +} + +int db_open(const char *const dir, sqlite3 **const out) +{ + int ret = SQLITE_ERROR; + sqlite3 *db; + struct dynstr d; + const mode_t m = umask(S_IWGRP | S_IWOTH | S_IROTH); + + dynstr_init(&d); + + if (dynstr_append(&d, "%s/" PROJECT_NAME ".db", dir)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if ((ret = sqlite3_open(d.str, &db)) != SQLITE_OK) + { + fprintf(stderr, "%s: sqlite3_open %s: %s\n", __func__, d.str, + sqlite3_errstr(ret)); + goto end; + } + + *out = db; + ret = 0; + +end: + umask(m); + dynstr_free(&d); + return ret; +} |
