/* * Copyright (c) 2022 Egor Tensin * This file is part of the "cimple" project. * For details, see https://github.com/egor-tensin/cimple. * Distributed under the MIT License. */ #include "sqlite.h" #include "compiler.h" #include "log.h" #include #include #include #include #include #define sqlite_errno(var, fn) \ do { \ log_err("%s: %s\n", fn, sqlite3_errstr(var)); \ var = -var; \ } while (0) #define sqlite_errno_if(expr, fn) \ do { \ int CONCAT(ret, __LINE__) = expr; \ if (CONCAT(ret, __LINE__)) \ sqlite_errno(CONCAT(ret, __LINE__), fn); \ } while (0) int sqlite_init(void) { int ret = 0; ret = sqlite3_initialize(); if (ret) { sqlite_errno(ret, "sqlite3_initialize"); return ret; } return ret; } void sqlite_destroy(void) { sqlite_errno_if(sqlite3_shutdown(), "sqlite3_shutdown"); } static int sqlite_open(const char *path, sqlite3 **db, int flags) { int ret = 0; ret = sqlite3_open_v2(path, db, flags, NULL); if (ret) { sqlite_errno(ret, "sqlite3_open_v2"); return ret; } return ret; } int sqlite_open_rw(const char *path, sqlite3 **db) { static const int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE; return sqlite_open(path, db, flags); } int sqlite_open_ro(const char *path, sqlite3 **db) { static const int flags = SQLITE_OPEN_READONLY; return sqlite_open(path, db, flags); } void sqlite_close(sqlite3 *db) { sqlite_errno_if(sqlite3_close(db), "sqlite3_close"); } int sqlite_exec(sqlite3 *db, const char *stmt, sqlite3_callback callback, void *arg) { int ret = 0; ret = sqlite3_exec(db, stmt, callback, arg, NULL); if (ret) { sqlite_errno(ret, "sqlite3_exec"); return ret; } return ret; } int sqlite_exec_log_result(UNUSED void *arg, int numof_columns, char **values, char **column_names) { log("Row:\n"); for (int i = 0; i < numof_columns; ++i) { log("\t%s: %s\n", column_names[i], values[i]); } return 0; } int sqlite_prepare(sqlite3 *db, const char *stmt, sqlite3_stmt **result) { int ret = 0; ret = sqlite3_prepare_v2(db, stmt, -1, result, NULL); if (ret) { sqlite_errno(ret, "sqlite3_prepare_v2"); return ret; } return ret; } void sqlite_reset(sqlite3_stmt *stmt) { sqlite_errno_if(sqlite3_reset(stmt), "sqlite3_reset"); sqlite_errno_if(sqlite3_clear_bindings(stmt), "sqlite3_clear_bindings"); } void sqlite_finalize(sqlite3_stmt *stmt) { sqlite_errno_if(sqlite3_finalize(stmt), "sqlite3_finalize"); } int sqlite_step(sqlite3_stmt *stmt) { int ret = 0; ret = sqlite3_step(stmt); switch (ret) { case SQLITE_ROW: return 1; case SQLITE_DONE: return 0; default: sqlite_errno(ret, "sqlite3_step"); return ret; } } int sqlite_column_int(sqlite3_stmt *stmt, int index) { return sqlite3_column_int(stmt, index); } int sqlite_column_text(sqlite3_stmt *stmt, int index, char **_result) { int ret = 0; const unsigned char *value = sqlite3_column_text(stmt, index); if (!value) { ret = sqlite3_errcode(sqlite3_db_handle(stmt)); if (ret) { sqlite_errno(ret, "sqlite3_column_text"); return ret; } *_result = NULL; return 0; } ret = sqlite3_column_bytes(stmt, index); size_t nb = (size_t)ret; char *result = calloc(nb + 1, 1); if (!result) { log_errno("calloc"); return -1; } memcpy(result, value, nb); *_result = result; return 0; } int sqlite_column_blob(sqlite3_stmt *stmt, int index, unsigned char **_result) { int ret = 0; const unsigned char *value = sqlite3_column_blob(stmt, index); if (!value) { ret = sqlite3_errcode(sqlite3_db_handle(stmt)); if (ret) { sqlite_errno(ret, "sqlite3_column_text"); return ret; } *_result = NULL; return 0; } ret = sqlite3_column_bytes(stmt, index); size_t nb = (size_t)ret; unsigned char *result = malloc(nb); if (!result) { log_errno("malloc"); return -1; } memcpy(result, value, nb); *_result = result; return 0; } int sqlite_bind_int(sqlite3_stmt *stmt, int index, int value) { int ret = 0; ret = sqlite3_bind_int(stmt, index, value); if (ret) { sqlite_errno(ret, "sqlite3_bind_int"); return ret; } return ret; } int sqlite_bind_text(sqlite3_stmt *stmt, int index, const char *value) { int ret = 0; ret = sqlite3_bind_text(stmt, index, value, -1, SQLITE_STATIC); if (ret) { sqlite_errno(ret, "sqlite3_bind_text"); return ret; } return ret; } int sqlite_bind_blob(sqlite3_stmt *stmt, int index, unsigned char *value, size_t nb) { int ret = 0; ret = sqlite3_bind_blob64(stmt, index, value, nb, SQLITE_STATIC); if (ret) { sqlite_errno(ret, "sqlite3_bind_blob64"); return ret; } return ret; } int sqlite_exec_as_transaction(sqlite3 *db, const char *stmt) { static const char *const fmt = "BEGIN; %s COMMIT;"; int ret = 0; ret = snprintf(NULL, 0, fmt, stmt); size_t nb = (size_t)ret + 1; ret = 0; char *full_stmt = malloc(nb); if (!full_stmt) { log_errno("malloc"); return -1; } snprintf(full_stmt, nb, fmt, stmt); ret = sqlite_exec(db, stmt, NULL, NULL); goto free; free: free(full_stmt); return ret; } int sqlite_get_user_version(sqlite3 *db, unsigned int *output) { static const char *const sql = "PRAGMA user_version;"; sqlite3_stmt *stmt = NULL; int result = -1, ret = 0; ret = sqlite_prepare(db, sql, &stmt); if (ret < 0) return ret; ret = sqlite_step(stmt); if (ret < 0) goto finalize; if (!ret) { ret = -1; log_err("Failed to read database version\n"); goto finalize; } result = sqlite_column_int(stmt, 0); if (result < 0) { log_err("Invalid database version: %d\n", result); ret = -1; goto finalize; } *output = (unsigned int)result; goto finalize; finalize: sqlite_finalize(stmt); return ret; } int sqlite_set_foreign_keys(sqlite3 *db) { static const char *const sql = "PRAGMA foreign_keys = ON;"; return sqlite_exec(db, sql, NULL, NULL); }