connection, bind, error codes

This commit is contained in:
2025-12-13 07:42:44 +05:00
parent 979bbfe2ba
commit 3d18027489
6 changed files with 162 additions and 102 deletions

4
.vscode/launch.json vendored
View File

@@ -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",

View File

@@ -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 <sqlite3.h>
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);

View File

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

View File

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

View File

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

View File

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