From 3d18027489e6bfa07630f45a5995cd5474ff2c81 Mon Sep 17 00:00:00 2001 From: Timerix Date: Sat, 13 Dec 2025 07:42:44 +0500 Subject: [PATCH] connection, bind, error codes --- .vscode/launch.json | 4 +-- include/tsqlite.h | 74 +++++++++++++++++++-------------------- src/connection.c | 17 +++++---- src/errors.c | 41 ++++++++++++++-------- src/statement.c | 85 +++++++++++++++++++++++++-------------------- tests/main.c | 43 +++++++++++++++++++++++ 6 files changed, 162 insertions(+), 102 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 77216a1..20644dc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,8 +5,8 @@ "name": "gdb_debug", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/bin/tsqlite", - "windows": { "program": "${workspaceFolder}/bin/tsqlite.exe" }, + "program": "${workspaceFolder}/bin/test", + "windows": { "program": "${workspaceFolder}/bin/test.exe" }, "preLaunchTask": "build_exec_dbg", "stopAtEntry": false, "cwd": "${workspaceFolder}/bin", diff --git a/include/tsqlite.h b/include/tsqlite.h index 17f9190..e60dadd 100644 --- a/include/tsqlite.h +++ b/include/tsqlite.h @@ -1,79 +1,75 @@ #pragma once #include "tlibc/tlibc.h" -#include "tlibc/errors.h" -#include "tlibc/string/str.h" -#include "tlibc/collections/Array.h" -#include "tlibc/collections/Array_impl/Array_u8.h" +#include "tlibc/string/StringBuilder.h" #include Result(void) tsqlite_init(); void tsqlite_deinit(); -/// @param code primary : 8 bits | extended : 24 bits -/// @return heap-allocated string -str tsqlite_ResultCode_toStr(int code); - ErrorCodePage_declare(SQLITE); -#define RESULT_ERROR_SQLITE_CODE(CODE) RESULT_ERROR_CODE(SQLITE, CODE & 0xff, tsqlite_ResultCode_toStr(CODE), true) +#define RESULT_ERROR_SQLITE_CODE(CONN, SHORT_CODE) RESULT_ERROR_CODE(SQLITE, SHORT_CODE, tsqlite_error_toStr(CONN, SHORT_CODE), true) -#define _try_sqlite3(CALL, N) do {\ - int _rname(N) = CALL;\ +#define _try_sqlite3(CONN, CALL, N) do {\ + i32 _rname(N) = CALL;\ if((_rname(N) & 0xff) != SQLITE_OK){\ - return RESULT_ERROR_SQLITE_CODE(_rname(N));\ + return RESULT_ERROR_SQLITE_CODE(CONN, _rname(N));\ }\ } while(0) -#define try_sqlite3(CALL) _try_sqlite3(CALL, __LINE__) - -typedef void(*sqlite3_error_log_func_t)(void* ctx, int code, cstr msg); +#define try_sqlite3(CONN, CALL) _try_sqlite3(CONN, CALL, __LINE__) typedef sqlite3 tsqlite_connection; /// @param file_path sqlite file -/// @param logger -/// @param log_func /// @return new sqlite connection -Result(tsqlite_connection*) tsqlite_connection_open(cstr file_path, - NULLABLE(void*) logger, NULLABLE(sqlite3_error_log_func_t) log_func); +Result(tsqlite_connection*) tsqlite_connection_open(cstr file_path); /// all statements and blobs must be destroyed before calling this Result(void) tsqlite_connection_close(tsqlite_connection* db); +/// @param conn a database connection with error code and error message +/// @return heap-allocated string +str tsqlite_error_toStr(tsqlite_connection* conn, i32 short_code); + +void tsqlite_resultCode_append(StringBuilder* sb, i32 short_code, i32 extended_code); +void tsqlite_resultCodeAndMsg_append(StringBuilder* sb, i32 short_code, i32 extended_code, str msg); + typedef struct tsqlite_statement { + tsqlite_connection* conn; sqlite3_stmt* st; - i32 bind_arg_pos; i32 result_row; i32 result_col; } tsqlite_statement; -/// @brief compile SQL statement to bytecode -/// @param db -/// @param sql_code SQL statement code. May contain placeholders + +/// @brief Compile SQL statement to bytecode. Documentation: https://sqlite.org/c3ref/prepare.html +/// @param conn a database connection +/// @param sql_code SQL statement code. May contain placeholders. +/// **PLACEHOLDERS FORMAT**: `:name`, `@name`, `$name`, `$(some name)` /// @return compiled statement -Result(tsqlite_statement*) tsqlite_statement_prepare(tsqlite_connection* db, str sql_code); +Result(tsqlite_statement*) tsqlite_statement_compile(tsqlite_connection* conn, str sql_code); -/// Bind value to placeholder at `self->bind_arg_pos`. Increases `self->bind_arg_pos` on success. -Result(void) tsqlite_statement_bind_null(tsqlite_statement* self); -Result(void) tsqlite_statement_bind_i32(tsqlite_statement* self, i32 v); -Result(void) tsqlite_statement_bind_i64(tsqlite_statement* self, i64 v); -Result(void) tsqlite_statement_bind_f64(tsqlite_statement* self, f64 v); -Result(void) tsqlite_statement_bind_str(tsqlite_statement* self, str v, NULLABLE(Destructor_t) d); -Result(void) tsqlite_statement_bind_blob(tsqlite_statement* self, Array(u8) v, NULLABLE(Destructor_t) d); -Result(void) tsqlite_statement_bind_zeroblob(tsqlite_statement* self, i32 size); +void tsqlite_statement_free(tsqlite_statement* st); -/// @brief execute statement or move to next result row +/// @brief execute statement or move to next result row. Documentation: https://sqlite.org/c3ref/step.html /// @return is next result row avaliable -Result(bool) sqlite3_statement_moveNext(tsqlite_statement* self); +Result(bool) tsqlite_statement_execNext(tsqlite_statement* self); + +/// call this after executing a compiled statement to use it again +Result(void) tsqlite_statement_reset(tsqlite_statement* st); + +/// Bind value to a placeholder +Result(void) tsqlite_statement_bind_null(tsqlite_statement* self, cstr key); +Result(void) tsqlite_statement_bind_i64(tsqlite_statement* self, cstr key, i64 v); +Result(void) tsqlite_statement_bind_f64(tsqlite_statement* self, cstr key, f64 v); +Result(void) tsqlite_statement_bind_str(tsqlite_statement* self, cstr key, str v, NULLABLE(Destructor_t) d); +Result(void) tsqlite_statement_bind_blob(tsqlite_statement* self, cstr key, Array(u8) v, NULLABLE(Destructor_t) d); +Result(void) tsqlite_statement_bind_zeroblob(tsqlite_statement* self, cstr key, i32 size); /// Get value at `self->result_col`. Increases `self->result_col` on success. Result(i64) tsqlite_statement_getResult_i64(tsqlite_statement* self); Result(i64) tsqlite_statement_getResult_f64(tsqlite_statement* self); Result(void) tsqlite_statement_getResult_str(tsqlite_statement* self, str* out_v); Result(void) tsqlite_statement_getResult_blob(tsqlite_statement* self, Array(u8)* out_v); - -/// call this after executing prepared statement to use it again -Result(void) tsqlite_statement_reset(tsqlite_statement* st); - -void tsqlite_statement_free(tsqlite_statement* st); diff --git a/src/connection.c b/src/connection.c index f4225e5..d283937 100644 --- a/src/connection.c +++ b/src/connection.c @@ -1,16 +1,15 @@ #include "tsqlite.h" -Result(tsqlite_connection*) tsqlite_connection_open(cstr file_path, - NULLABLE(void*) logger, NULLABLE(sqlite3_error_log_func_t) log_func) +Result(tsqlite_connection*) tsqlite_connection_open(cstr file_path) { - tsqlite_connection* conn = NULL; - try_sqlite3(sqlite3_open(file_path, &conn)); - try_sqlite3(sqlite3_db_config(conn, SQLITE_CONFIG_LOG, log_func, logger)); - try_sqlite3(sqlite3_extended_result_codes(conn, true)); - return RESULT_VALUE(p, conn); + tsqlite_connection* self = NULL; + try_sqlite3(self, sqlite3_open(file_path, &self)); + return RESULT_VALUE(p, self); } -Result(void) tsqlite_connection_close(tsqlite_connection* conn){ - try_sqlite3(sqlite3_close(conn)); +Result(void) tsqlite_connection_close(tsqlite_connection* self){ + if(!self) + return RESULT_VOID; + try_sqlite3(self, sqlite3_close(self)); return RESULT_VOID; } diff --git a/src/errors.c b/src/errors.c index 9a883b0..bf8c92b 100644 --- a/src/errors.c +++ b/src/errors.c @@ -1,5 +1,4 @@ #include "tsqlite.h" -#include "tlibc/string/StringBuilder.h" #define PRIMARY_CODE_X(X)\ X(SQLITE_OK)\ @@ -120,7 +119,7 @@ #define PRIMARY_CODE_CASE(C) case C: return STR(#C); -static str PrimaryCode_toStr(int code){ +static str PrimaryCode_toStr(i32 code){ switch(code & 0xff){ default: return STR("!! ERROR: INVALID SQLITE_CODE !!"); PRIMARY_CODE_X(PRIMARY_CODE_CASE) @@ -128,24 +127,38 @@ static str PrimaryCode_toStr(int code){ } #define EXTENDED_CODE_IF(C) \ - if(C & code) {\ + if(C & extended_code_part) {\ if(multiple_extended_codes){\ - StringBuilder_append_char(&sb, ',');\ - StringBuilder_append_char(&sb, ' ');\ + StringBuilder_append_char(sb, ',');\ + StringBuilder_append_char(sb, ' ');\ }\ else multiple_extended_codes = true;\ - StringBuilder_append_str(&sb, STR(#C));\ + StringBuilder_append_str(sb, STR(#C));\ }\ -str tsqlite_ResultCode_toStr(int code){ - StringBuilder sb = StringBuilder_alloc(128); - bool multiple_extended_codes = false; - - StringBuilder_append_str(&sb, PrimaryCode_toStr(code)); - StringBuilder_append_char(&sb, ':'); - StringBuilder_append_char(&sb, ' '); +void tsqlite_resultCode_append(StringBuilder* sb, i32 short_code, i32 extended_code){ + i32 extended_code_part = extended_code ^ (extended_code & 0xff); + if(extended_code_part == 0){ + StringBuilder_append_str(sb, PrimaryCode_toStr(short_code)); + } + else { + bool multiple_extended_codes = false; + EXTENDED_CODE_X(EXTENDED_CODE_IF); + } +} - EXTENDED_CODE_X(EXTENDED_CODE_IF); +; +void tsqlite_resultCodeAndMsg_append(StringBuilder* sb, i32 short_code, i32 extended_code, str msg){ + tsqlite_resultCode_append(sb, short_code, extended_code); + StringBuilder_append_char(sb, ':'); + StringBuilder_append_char(sb, ' '); + StringBuilder_append_str(sb, msg); +} +str tsqlite_error_toStr(tsqlite_connection* conn, i32 short_code){ + i32 extended_code = sqlite3_extended_errcode(conn); + str msg = str_from_cstr(sqlite3_errmsg(conn)); + StringBuilder sb = StringBuilder_alloc(64 + msg.len); + tsqlite_resultCodeAndMsg_append(&sb, short_code, extended_code, msg); return StringBuilder_getStr(&sb); } diff --git a/src/statement.c b/src/statement.c index e86d20d..6d81901 100644 --- a/src/statement.c +++ b/src/statement.c @@ -1,12 +1,14 @@ #include "tsqlite.h" -Result(tsqlite_statement*) tsqlite_statement_prepare(sqlite3* conn, str sql_code){ +Result(tsqlite_statement*) tsqlite_statement_compile(tsqlite_connection* conn, str sql_code){ sqlite3_stmt* st = NULL; i32 flags = SQLITE_PREPARE_PERSISTENT; - try_sqlite3(sqlite3_prepare_v3(conn, sql_code.data, sql_code.len, flags, &st, NULL)); + try_sqlite3(conn, sqlite3_prepare_v3(conn, sql_code.data, sql_code.len, flags, &st, NULL)); tsqlite_statement* self = malloc(sizeof(*self)); - zeroStruct(self); + self->conn = conn; self->st = st; + self->result_row = 0; + self->result_col = 0; return RESULT_VALUE(p, self); } @@ -18,92 +20,99 @@ void tsqlite_statement_free(tsqlite_statement* self){ } Result(void) tsqlite_statement_reset(tsqlite_statement* self){ - try_sqlite3(sqlite3_reset(self->st)); - self->bind_arg_pos = 0; + try_sqlite3(self->conn, sqlite3_reset(self->st)); self->result_row = 0; self->result_col = 0; return RESULT_VOID; } -Result(void) tsqlite_statement_bind_null(tsqlite_statement* self){ - try_sqlite3(sqlite3_bind_null(self->st, self->bind_arg_pos)); - self->bind_arg_pos++; +#define tryGetBindIndex(key) \ + i32 bind_index = sqlite3_bind_parameter_index(self->st, key);\ + if(bind_index == 0){\ + return RESULT_ERROR_SQLITE_CODE(self->conn, sqlite3_errcode(self->conn));\ + }\ + +Result(void) tsqlite_statement_bind_null(tsqlite_statement* self, cstr key){ + tryGetBindIndex(key); + try_sqlite3(self->conn, sqlite3_bind_null(self->st, bind_index)); return RESULT_VOID; } -Result(void) tsqlite_statement_bind_i32(tsqlite_statement* self, i32 v){ - try_sqlite3(sqlite3_bind_int(self->st, self->bind_arg_pos, v)); - self->bind_arg_pos++; +Result(void) tsqlite_statement_bind_i64(tsqlite_statement* self, cstr key, i64 v){ + tryGetBindIndex(key); + try_sqlite3(self->conn, sqlite3_bind_int64(self->st, bind_index, v)); return RESULT_VOID; } -Result(void) tsqlite_statement_bind_i64(tsqlite_statement* self, i64 v){ - try_sqlite3(sqlite3_bind_int64(self->st, self->bind_arg_pos, v)); - self->bind_arg_pos++; +Result(void) tsqlite_statement_bind_f64(tsqlite_statement* self, cstr key, f64 v){ + tryGetBindIndex(key); + try_sqlite3(self->conn, sqlite3_bind_double(self->st, bind_index, v)); return RESULT_VOID; } -Result(void) tsqlite_statement_bind_f64(tsqlite_statement* self, f64 v){ - try_sqlite3(sqlite3_bind_double(self->st, self->bind_arg_pos, v)); - self->bind_arg_pos++; +Result(void) tsqlite_statement_bind_str(tsqlite_statement* self, cstr key, str v, NULLABLE(Destructor_t) d){ + tryGetBindIndex(key); + try_sqlite3(self->conn, sqlite3_bind_text(self->st, bind_index, v.data, v.len, d)); return RESULT_VOID; } -Result(void) tsqlite_statement_bind_str(tsqlite_statement* self, str v, NULLABLE(Destructor_t) d){ - try_sqlite3(sqlite3_bind_text(self->st, self->bind_arg_pos, v.data, v.len, d)); - self->bind_arg_pos++; +Result(void) tsqlite_statement_bind_blob(tsqlite_statement* self, cstr key, Array(u8) v, NULLABLE(Destructor_t) d){ + tryGetBindIndex(key); + try_sqlite3(self->conn, sqlite3_bind_blob(self->st, bind_index, v.data, v.len, d)); return RESULT_VOID; } -Result(void) tsqlite_statement_bind_blob(tsqlite_statement* self, Array(u8) v, NULLABLE(Destructor_t) d){ - try_sqlite3(sqlite3_bind_blob(self->st, self->bind_arg_pos, v.data, v.len, d)); - self->bind_arg_pos++; +Result(void) tsqlite_statement_bind_zeroblob(tsqlite_statement* self, cstr key, i32 size){ + tryGetBindIndex(key); + try_sqlite3(self->conn, sqlite3_bind_zeroblob(self->st, bind_index, size)); return RESULT_VOID; } -Result(void) tsqlite_statement_bind_zeroblob(tsqlite_statement* self, i32 size){ - try_sqlite3(sqlite3_bind_zeroblob(self->st, self->bind_arg_pos, size)); - self->bind_arg_pos++; - return RESULT_VOID; -} -Result(bool) sqlite3_statement_moveNext(tsqlite_statement* self){ +Result(bool) tsqlite_statement_execNext(tsqlite_statement* self){ int r = sqlite3_step(self->st); if(r == SQLITE_ROW){ + self->result_row++; return RESULT_VALUE(i, true); } if(r == SQLITE_DONE){ + self->result_row++; return RESULT_VALUE(i, false); } - return RESULT_ERROR_SQLITE_CODE(r); + return RESULT_ERROR_SQLITE_CODE(self->conn, r); } + Result(i64) tsqlite_statement_getResult_i64(tsqlite_statement* self){ i64 r = sqlite3_column_int64(self->st, self->result_col); - // TODO: error checking in sqlite3_column + try_sqlite3(self->conn, sqlite3_errcode(self->conn)); self->result_col++; return RESULT_VALUE(i, r); } Result(i64) tsqlite_statement_getResult_f64(tsqlite_statement* self){ f64 r = sqlite3_column_double(self->st, self->result_col); - // TODO: error checking in sqlite3_column + try_sqlite3(self->conn, sqlite3_errcode(self->conn)); self->result_col++; return RESULT_VALUE(f, r); } Result(void) tsqlite_statement_getResult_str(tsqlite_statement* self, str* out_v){ - (void)self; - (void)out_v; - // TODO: tsqlite_statement_getResult_str + void* data = (void*)sqlite3_column_text(self->st, self->result_col); + try_sqlite3(self->conn, sqlite3_errcode(self->conn)); + u32 size = sqlite3_column_bytes(self->st, self->result_col); + *out_v = str_construct(data, size, true); + self->result_col++; return RESULT_VOID; } Result(void) tsqlite_statement_getResult_blob(tsqlite_statement* self, Array(u8)* out_v){ - (void)self; - (void)out_v; - // TODO: tsqlite_statement_getResult_blob + void* data = (void*)sqlite3_column_blob(self->st, self->result_col); + try_sqlite3(self->conn, sqlite3_errcode(self->conn)); + u32 size = sqlite3_column_bytes(self->st, self->result_col); + *out_v = Array_u8_construct(data, size); + self->result_col++; return RESULT_VOID; } diff --git a/tests/main.c b/tests/main.c index 61ce0dd..f1b1397 100644 --- a/tests/main.c +++ b/tests/main.c @@ -1,5 +1,45 @@ #include "tsqlite.h" +Result(void) test_connection(){ + Deferral(8); + + try(tsqlite_connection* conn, p, tsqlite_connection_open("db.sqlite")); + Defer(if(conn != NULL) { IGNORE_RESULT tsqlite_connection_close(conn); }); + + // close manually to test tsqlite_connection_close + try_void(tsqlite_connection_close(conn)); + conn = NULL; + + Return RESULT_VOID; +} + +Result(void) test_statement(){ + Deferral(8); + + try(tsqlite_connection* conn, p, tsqlite_connection_open("db.sqlite")); + Defer(if(conn != NULL) { IGNORE_RESULT tsqlite_connection_close(conn); }); + + try(tsqlite_statement* st, p, tsqlite_statement_compile(conn, STR("meow meow"))); + Defer(tsqlite_statement_free(st)); + + try_void(tsqlite_statement_bind_null(st, "nul")); + + bool has_next_row = false; + do { + try(has_next_row, i, tsqlite_statement_execNext(st)); + str cell_str = str_null; + try_void(tsqlite_statement_getResult_str(st, &cell_str)); + printf("RESULT(%i, %i): '"FMT_str"'\n", + st->result_row, st->result_col, str_expand(cell_str)); + } while(has_next_row); + + // close manually to test tsqlite_connection_close + try_void(tsqlite_connection_close(conn)); + conn = NULL; + + Return RESULT_VOID; +} + int main(){ Deferral(32); @@ -7,6 +47,9 @@ int main(){ try_fatal_void(tsqlite_init()); Defer(tlibc_deinit()); Defer(tsqlite_deinit()); + + try_fatal_void(test_connection()); + try_fatal_void(test_statement()); Return 0; }