Compare commits
5 Commits
979bbfe2ba
...
58840cecd0
| Author | SHA1 | Date | |
|---|---|---|---|
| 58840cecd0 | |||
| ac4f33be1b | |||
| 3a528c00c2 | |||
| 52a19d8db8 | |||
| 3d18027489 |
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -5,8 +5,8 @@
|
|||||||
"name": "gdb_debug",
|
"name": "gdb_debug",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/bin/tsqlite",
|
"program": "${workspaceFolder}/bin/test",
|
||||||
"windows": { "program": "${workspaceFolder}/bin/tsqlite.exe" },
|
"windows": { "program": "${workspaceFolder}/bin/test.exe" },
|
||||||
"preLaunchTask": "build_exec_dbg",
|
"preLaunchTask": "build_exec_dbg",
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}/bin",
|
"cwd": "${workspaceFolder}/bin",
|
||||||
|
|||||||
@@ -1,79 +1,84 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tlibc/tlibc.h"
|
#include "tlibc/tlibc.h"
|
||||||
#include "tlibc/errors.h"
|
#include "tlibc/string/StringBuilder.h"
|
||||||
#include "tlibc/string/str.h"
|
|
||||||
#include "tlibc/collections/Array.h"
|
|
||||||
#include "tlibc/collections/Array_impl/Array_u8.h"
|
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
Result(void) tsqlite_init();
|
Result(void) tsqlite_init();
|
||||||
void tsqlite_deinit();
|
void tsqlite_deinit();
|
||||||
|
|
||||||
/// @param code primary : 8 bits | extended : 24 bits
|
|
||||||
/// @return heap-allocated string
|
|
||||||
str tsqlite_ResultCode_toStr(int code);
|
|
||||||
|
|
||||||
ErrorCodePage_declare(SQLITE);
|
ErrorCodePage_declare(SQLITE);
|
||||||
|
|
||||||
#define RESULT_ERROR_SQLITE_CODE(CODE) RESULT_ERROR_CODE(SQLITE, CODE & 0xff, tsqlite_ResultCode_toStr(CODE), true)
|
/// @param code SQLITE_* result code
|
||||||
|
/// @return heap-allocated string
|
||||||
|
str tsqlite_error_toStr(i32 code, cstr msg);
|
||||||
|
|
||||||
#define _try_sqlite3(CALL, N) do {\
|
#define RESULT_ERROR_SQLITE_CODE(CODE, MSG) RESULT_ERROR_CODE(SQLITE, CODE, tsqlite_error_toStr(CODE, MSG), true)
|
||||||
int _rname(N) = CALL;\
|
|
||||||
|
#define _try_sqlite3(CONN, CALL, N) do {\
|
||||||
|
i32 _rname(N) = CALL;\
|
||||||
if((_rname(N) & 0xff) != SQLITE_OK){\
|
if((_rname(N) & 0xff) != SQLITE_OK){\
|
||||||
return RESULT_ERROR_SQLITE_CODE(_rname(N));\
|
return RESULT_ERROR_SQLITE_CODE(_rname(N), sqlite3_errmsg(CONN));\
|
||||||
}\
|
}\
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
#define try_sqlite3(CALL) _try_sqlite3(CALL, __LINE__)
|
#define try_sqlite3(CONN, CALL) _try_sqlite3(CONN, CALL, __LINE__)
|
||||||
|
|
||||||
typedef void(*sqlite3_error_log_func_t)(void* ctx, int code, cstr msg);
|
|
||||||
|
|
||||||
|
|
||||||
typedef sqlite3 tsqlite_connection;
|
typedef sqlite3 tsqlite_connection;
|
||||||
|
|
||||||
/// @param file_path sqlite file
|
/// @param file_path sqlite file
|
||||||
/// @param logger
|
|
||||||
/// @param log_func
|
|
||||||
/// @return new sqlite connection
|
/// @return new sqlite connection
|
||||||
Result(tsqlite_connection*) tsqlite_connection_open(cstr file_path,
|
Result(tsqlite_connection*) tsqlite_connection_open(cstr file_path);
|
||||||
NULLABLE(void*) logger, NULLABLE(sqlite3_error_log_func_t) log_func);
|
|
||||||
|
/// all statements and blobs should be destroyed before calling this
|
||||||
|
void tsqlite_connection_close(tsqlite_connection* db);
|
||||||
|
|
||||||
/// all statements and blobs must be destroyed before calling this
|
|
||||||
Result(void) tsqlite_connection_close(tsqlite_connection* db);
|
|
||||||
|
|
||||||
typedef struct tsqlite_statement {
|
typedef struct tsqlite_statement {
|
||||||
|
tsqlite_connection* conn;
|
||||||
sqlite3_stmt* st;
|
sqlite3_stmt* st;
|
||||||
i32 bind_arg_pos;
|
|
||||||
i32 result_row;
|
i32 result_row;
|
||||||
i32 result_col;
|
i32 result_col;
|
||||||
} tsqlite_statement;
|
} tsqlite_statement;
|
||||||
|
|
||||||
/// @brief compile SQL statement to bytecode
|
|
||||||
/// @param db
|
/// @brief Compile SQL statement to bytecode. Documentation: https://sqlite.org/c3ref/prepare.html
|
||||||
/// @param sql_code SQL statement code. May contain placeholders
|
/// @param conn a database connection
|
||||||
|
/// @param sql_code SQL statement code. May contain placeholders.
|
||||||
|
/// **PLACEHOLDERS FORMAT**: `:name`, `@name`, `$name`, `$(some name)`
|
||||||
/// @return compiled statement
|
/// @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.
|
void tsqlite_statement_free(tsqlite_statement* st);
|
||||||
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);
|
|
||||||
|
|
||||||
/// @brief execute statement or move to next result row
|
/// @brief execute statement or move to next result row.
|
||||||
/// @return is next result row avaliable
|
/// Documentation: https://sqlite.org/c3ref/step.html
|
||||||
Result(bool) sqlite3_statement_moveNext(tsqlite_statement* self);
|
///
|
||||||
|
/// USAGE:
|
||||||
|
/// ```
|
||||||
|
/// while(true){
|
||||||
|
/// try(bool has_result, i, tsqlite_statement_step(st));
|
||||||
|
/// if(!has_result)
|
||||||
|
/// break;
|
||||||
|
/// /* get result columns */
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// @return is result row avaliable
|
||||||
|
Result(bool) tsqlite_statement_step(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.
|
/// 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_i64(tsqlite_statement* self);
|
||||||
Result(i64) tsqlite_statement_getResult_f64(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_str(tsqlite_statement* self, str* out_v);
|
||||||
Result(void) tsqlite_statement_getResult_blob(tsqlite_statement* self, Array(u8)* 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);
|
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
#include "tsqlite.h"
|
#include "tsqlite.h"
|
||||||
|
|
||||||
Result(tsqlite_connection*) tsqlite_connection_open(cstr file_path,
|
Result(tsqlite_connection*) tsqlite_connection_open(cstr file_path)
|
||||||
NULLABLE(void*) logger, NULLABLE(sqlite3_error_log_func_t) log_func)
|
|
||||||
{
|
{
|
||||||
tsqlite_connection* conn = NULL;
|
tsqlite_connection* self = NULL;
|
||||||
try_sqlite3(sqlite3_open(file_path, &conn));
|
try_sqlite3(self, sqlite3_open(file_path, &self));
|
||||||
try_sqlite3(sqlite3_db_config(conn, SQLITE_CONFIG_LOG, log_func, logger));
|
return RESULT_VALUE(p, self);
|
||||||
try_sqlite3(sqlite3_extended_result_codes(conn, true));
|
|
||||||
return RESULT_VALUE(p, conn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) tsqlite_connection_close(tsqlite_connection* conn){
|
void tsqlite_connection_close(tsqlite_connection* self){
|
||||||
try_sqlite3(sqlite3_close(conn));
|
if(!self)
|
||||||
return RESULT_VOID;
|
return;
|
||||||
|
sqlite3_close_v2(self);
|
||||||
}
|
}
|
||||||
|
|||||||
151
src/errors.c
151
src/errors.c
@@ -1,151 +0,0 @@
|
|||||||
#include "tsqlite.h"
|
|
||||||
#include "tlibc/string/StringBuilder.h"
|
|
||||||
|
|
||||||
#define PRIMARY_CODE_X(X)\
|
|
||||||
X(SQLITE_OK)\
|
|
||||||
X(SQLITE_ERROR)\
|
|
||||||
X(SQLITE_INTERNAL)\
|
|
||||||
X(SQLITE_PERM)\
|
|
||||||
X(SQLITE_ABORT)\
|
|
||||||
X(SQLITE_BUSY)\
|
|
||||||
X(SQLITE_LOCKED)\
|
|
||||||
X(SQLITE_NOMEM)\
|
|
||||||
X(SQLITE_READONLY)\
|
|
||||||
X(SQLITE_INTERRUPT)\
|
|
||||||
X(SQLITE_IOERR)\
|
|
||||||
X(SQLITE_CORRUPT)\
|
|
||||||
X(SQLITE_NOTFOUND)\
|
|
||||||
X(SQLITE_FULL)\
|
|
||||||
X(SQLITE_CANTOPEN)\
|
|
||||||
X(SQLITE_PROTOCOL)\
|
|
||||||
X(SQLITE_EMPTY)\
|
|
||||||
X(SQLITE_SCHEMA)\
|
|
||||||
X(SQLITE_TOOBIG)\
|
|
||||||
X(SQLITE_CONSTRAINT)\
|
|
||||||
X(SQLITE_MISMATCH)\
|
|
||||||
X(SQLITE_MISUSE)\
|
|
||||||
X(SQLITE_NOLFS)\
|
|
||||||
X(SQLITE_AUTH)\
|
|
||||||
X(SQLITE_FORMAT)\
|
|
||||||
X(SQLITE_RANGE)\
|
|
||||||
X(SQLITE_NOTADB)\
|
|
||||||
X(SQLITE_NOTICE)\
|
|
||||||
X(SQLITE_WARNING)\
|
|
||||||
X(SQLITE_ROW)\
|
|
||||||
X(SQLITE_DONE)\
|
|
||||||
|
|
||||||
#define EXTENDED_CODE_X(X)\
|
|
||||||
X(SQLITE_ERROR_MISSING_COLLSEQ)\
|
|
||||||
X(SQLITE_ERROR_RETRY)\
|
|
||||||
X(SQLITE_ERROR_SNAPSHOT)\
|
|
||||||
X(SQLITE_ERROR_RESERVESIZE)\
|
|
||||||
X(SQLITE_ERROR_KEY)\
|
|
||||||
X(SQLITE_ERROR_UNABLE)\
|
|
||||||
X(SQLITE_IOERR_READ)\
|
|
||||||
X(SQLITE_IOERR_SHORT_READ)\
|
|
||||||
X(SQLITE_IOERR_WRITE)\
|
|
||||||
X(SQLITE_IOERR_FSYNC)\
|
|
||||||
X(SQLITE_IOERR_DIR_FSYNC)\
|
|
||||||
X(SQLITE_IOERR_TRUNCATE)\
|
|
||||||
X(SQLITE_IOERR_FSTAT)\
|
|
||||||
X(SQLITE_IOERR_UNLOCK)\
|
|
||||||
X(SQLITE_IOERR_RDLOCK)\
|
|
||||||
X(SQLITE_IOERR_DELETE)\
|
|
||||||
X(SQLITE_IOERR_BLOCKED)\
|
|
||||||
X(SQLITE_IOERR_NOMEM)\
|
|
||||||
X(SQLITE_IOERR_ACCESS)\
|
|
||||||
X(SQLITE_IOERR_CHECKRESERVEDLOCK)\
|
|
||||||
X(SQLITE_IOERR_LOCK)\
|
|
||||||
X(SQLITE_IOERR_CLOSE)\
|
|
||||||
X(SQLITE_IOERR_DIR_CLOSE)\
|
|
||||||
X(SQLITE_IOERR_SHMOPEN)\
|
|
||||||
X(SQLITE_IOERR_SHMSIZE)\
|
|
||||||
X(SQLITE_IOERR_SHMLOCK)\
|
|
||||||
X(SQLITE_IOERR_SHMMAP)\
|
|
||||||
X(SQLITE_IOERR_SEEK)\
|
|
||||||
X(SQLITE_IOERR_DELETE_NOENT)\
|
|
||||||
X(SQLITE_IOERR_MMAP)\
|
|
||||||
X(SQLITE_IOERR_GETTEMPPATH)\
|
|
||||||
X(SQLITE_IOERR_CONVPATH)\
|
|
||||||
X(SQLITE_IOERR_VNODE)\
|
|
||||||
X(SQLITE_IOERR_AUTH)\
|
|
||||||
X(SQLITE_IOERR_BEGIN_ATOMIC)\
|
|
||||||
X(SQLITE_IOERR_COMMIT_ATOMIC)\
|
|
||||||
X(SQLITE_IOERR_ROLLBACK_ATOMIC)\
|
|
||||||
X(SQLITE_IOERR_DATA)\
|
|
||||||
X(SQLITE_IOERR_CORRUPTFS)\
|
|
||||||
X(SQLITE_IOERR_IN_PAGE)\
|
|
||||||
X(SQLITE_IOERR_BADKEY)\
|
|
||||||
X(SQLITE_IOERR_CODEC)\
|
|
||||||
X(SQLITE_LOCKED_SHAREDCACHE)\
|
|
||||||
X(SQLITE_LOCKED_VTAB)\
|
|
||||||
X(SQLITE_BUSY_RECOVERY)\
|
|
||||||
X(SQLITE_BUSY_SNAPSHOT)\
|
|
||||||
X(SQLITE_BUSY_TIMEOUT)\
|
|
||||||
X(SQLITE_CANTOPEN_NOTEMPDIR)\
|
|
||||||
X(SQLITE_CANTOPEN_ISDIR)\
|
|
||||||
X(SQLITE_CANTOPEN_FULLPATH)\
|
|
||||||
X(SQLITE_CANTOPEN_CONVPATH)\
|
|
||||||
X(SQLITE_CANTOPEN_DIRTYWAL)\
|
|
||||||
X(SQLITE_CANTOPEN_SYMLINK)\
|
|
||||||
X(SQLITE_CORRUPT_VTAB)\
|
|
||||||
X(SQLITE_CORRUPT_SEQUENCE)\
|
|
||||||
X(SQLITE_CORRUPT_INDEX)\
|
|
||||||
X(SQLITE_READONLY_RECOVERY)\
|
|
||||||
X(SQLITE_READONLY_CANTLOCK)\
|
|
||||||
X(SQLITE_READONLY_ROLLBACK)\
|
|
||||||
X(SQLITE_READONLY_DBMOVED)\
|
|
||||||
X(SQLITE_READONLY_CANTINIT)\
|
|
||||||
X(SQLITE_READONLY_DIRECTORY)\
|
|
||||||
X(SQLITE_ABORT_ROLLBACK)\
|
|
||||||
X(SQLITE_CONSTRAINT_CHECK)\
|
|
||||||
X(SQLITE_CONSTRAINT_COMMITHOOK)\
|
|
||||||
X(SQLITE_CONSTRAINT_FOREIGNKEY)\
|
|
||||||
X(SQLITE_CONSTRAINT_FUNCTION)\
|
|
||||||
X(SQLITE_CONSTRAINT_NOTNULL)\
|
|
||||||
X(SQLITE_CONSTRAINT_PRIMARYKEY)\
|
|
||||||
X(SQLITE_CONSTRAINT_TRIGGER)\
|
|
||||||
X(SQLITE_CONSTRAINT_UNIQUE)\
|
|
||||||
X(SQLITE_CONSTRAINT_VTAB)\
|
|
||||||
X(SQLITE_CONSTRAINT_ROWID)\
|
|
||||||
X(SQLITE_CONSTRAINT_PINNED)\
|
|
||||||
X(SQLITE_CONSTRAINT_DATATYPE)\
|
|
||||||
X(SQLITE_NOTICE_RECOVER_WAL)\
|
|
||||||
X(SQLITE_NOTICE_RECOVER_ROLLBACK)\
|
|
||||||
X(SQLITE_NOTICE_RBU)\
|
|
||||||
X(SQLITE_WARNING_AUTOINDEX)\
|
|
||||||
X(SQLITE_AUTH_USER)\
|
|
||||||
X(SQLITE_OK_LOAD_PERMANENTLY)\
|
|
||||||
X(SQLITE_OK_SYMLINK)\
|
|
||||||
|
|
||||||
#define PRIMARY_CODE_CASE(C) case C: return STR(#C);
|
|
||||||
|
|
||||||
static str PrimaryCode_toStr(int code){
|
|
||||||
switch(code & 0xff){
|
|
||||||
default: return STR("!! ERROR: INVALID SQLITE_CODE !!");
|
|
||||||
PRIMARY_CODE_X(PRIMARY_CODE_CASE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define EXTENDED_CODE_IF(C) \
|
|
||||||
if(C & code) {\
|
|
||||||
if(multiple_extended_codes){\
|
|
||||||
StringBuilder_append_char(&sb, ',');\
|
|
||||||
StringBuilder_append_char(&sb, ' ');\
|
|
||||||
}\
|
|
||||||
else multiple_extended_codes = true;\
|
|
||||||
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, ' ');
|
|
||||||
|
|
||||||
EXTENDED_CODE_X(EXTENDED_CODE_IF);
|
|
||||||
|
|
||||||
return StringBuilder_getStr(&sb);
|
|
||||||
}
|
|
||||||
108
src/statement.c
108
src/statement.c
@@ -1,12 +1,22 @@
|
|||||||
#include "tsqlite.h"
|
#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;
|
sqlite3_stmt* st = NULL;
|
||||||
i32 flags = SQLITE_PREPARE_PERSISTENT;
|
i32 flags = SQLITE_PREPARE_PERSISTENT;
|
||||||
try_sqlite3(sqlite3_prepare_v3(conn, sql_code.data, sql_code.len, flags, &st, NULL));
|
const char* tail;
|
||||||
|
try_sqlite3(conn, sqlite3_prepare_v3(conn, sql_code.data, sql_code.len, flags, &st, &tail));
|
||||||
|
for(u32 i = (u32)(tail - sql_code.data); i < sql_code.len; i++){
|
||||||
|
char c = sql_code.data[i];
|
||||||
|
if(c != ' ' && c != '\t' && c != '\r' && c != '\n'){
|
||||||
|
sqlite3_finalize(st);
|
||||||
|
return RESULT_ERROR_LITERAL("multiple statements cannot be compiled as one");
|
||||||
|
}
|
||||||
|
}
|
||||||
tsqlite_statement* self = malloc(sizeof(*self));
|
tsqlite_statement* self = malloc(sizeof(*self));
|
||||||
zeroStruct(self);
|
self->conn = conn;
|
||||||
self->st = st;
|
self->st = st;
|
||||||
|
self->result_row = -1;
|
||||||
|
self->result_col = -1;
|
||||||
return RESULT_VALUE(p, self);
|
return RESULT_VALUE(p, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,92 +28,106 @@ void tsqlite_statement_free(tsqlite_statement* self){
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result(void) tsqlite_statement_reset(tsqlite_statement* self){
|
Result(void) tsqlite_statement_reset(tsqlite_statement* self){
|
||||||
try_sqlite3(sqlite3_reset(self->st));
|
try_sqlite3(self->conn, sqlite3_reset(self->st));
|
||||||
self->bind_arg_pos = 0;
|
self->result_row = -1;
|
||||||
self->result_row = 0;
|
self->result_col = -1;
|
||||||
self->result_col = 0;
|
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Result(void) tsqlite_statement_bind_null(tsqlite_statement* self){
|
#define tryGetBindIndex(key) \
|
||||||
try_sqlite3(sqlite3_bind_null(self->st, self->bind_arg_pos));
|
i32 bind_index = sqlite3_bind_parameter_index(self->st, key);\
|
||||||
self->bind_arg_pos++;
|
if(bind_index == 0){\
|
||||||
|
return RESULT_ERROR_CODE_FMT(SQLITE, SQLITE_ERROR, "bind placeholder '%s' not found", key);\
|
||||||
|
}\
|
||||||
|
|
||||||
|
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;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) tsqlite_statement_bind_i32(tsqlite_statement* self, i32 v){
|
Result(void) tsqlite_statement_bind_i64(tsqlite_statement* self, cstr key, i64 v){
|
||||||
try_sqlite3(sqlite3_bind_int(self->st, self->bind_arg_pos, v));
|
tryGetBindIndex(key);
|
||||||
self->bind_arg_pos++;
|
try_sqlite3(self->conn, sqlite3_bind_int64(self->st, bind_index, v));
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) tsqlite_statement_bind_i64(tsqlite_statement* self, i64 v){
|
Result(void) tsqlite_statement_bind_f64(tsqlite_statement* self, cstr key, f64 v){
|
||||||
try_sqlite3(sqlite3_bind_int64(self->st, self->bind_arg_pos, v));
|
tryGetBindIndex(key);
|
||||||
self->bind_arg_pos++;
|
try_sqlite3(self->conn, sqlite3_bind_double(self->st, bind_index, v));
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) tsqlite_statement_bind_f64(tsqlite_statement* self, f64 v){
|
Result(void) tsqlite_statement_bind_str(tsqlite_statement* self, cstr key, str v, NULLABLE(Destructor_t) d){
|
||||||
try_sqlite3(sqlite3_bind_double(self->st, self->bind_arg_pos, v));
|
tryGetBindIndex(key);
|
||||||
self->bind_arg_pos++;
|
try_sqlite3(self->conn, sqlite3_bind_text(self->st, bind_index, v.data, v.len, d));
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) tsqlite_statement_bind_str(tsqlite_statement* self, str v, NULLABLE(Destructor_t) d){
|
Result(void) tsqlite_statement_bind_blob(tsqlite_statement* self, cstr key, Array(u8) v, NULLABLE(Destructor_t) d){
|
||||||
try_sqlite3(sqlite3_bind_text(self->st, self->bind_arg_pos, v.data, v.len, d));
|
tryGetBindIndex(key);
|
||||||
self->bind_arg_pos++;
|
try_sqlite3(self->conn, sqlite3_bind_blob(self->st, bind_index, v.data, v.len, d));
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
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, cstr key, i32 size){
|
||||||
try_sqlite3(sqlite3_bind_blob(self->st, self->bind_arg_pos, v.data, v.len, d));
|
tryGetBindIndex(key);
|
||||||
self->bind_arg_pos++;
|
try_sqlite3(self->conn, sqlite3_bind_zeroblob(self->st, bind_index, size));
|
||||||
return RESULT_VOID;
|
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_step(tsqlite_statement* self){
|
||||||
int r = sqlite3_step(self->st);
|
int r = sqlite3_step(self->st);
|
||||||
if(r == SQLITE_ROW){
|
if(r == SQLITE_ROW){
|
||||||
|
self->result_row++;
|
||||||
|
self->result_col = -1;
|
||||||
return RESULT_VALUE(i, true);
|
return RESULT_VALUE(i, true);
|
||||||
}
|
}
|
||||||
if(r == SQLITE_DONE){
|
if(r == SQLITE_DONE){
|
||||||
|
self->result_row++;
|
||||||
|
self->result_col = -1;
|
||||||
return RESULT_VALUE(i, false);
|
return RESULT_VALUE(i, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return RESULT_ERROR_SQLITE_CODE(r);
|
return RESULT_ERROR_SQLITE_CODE(r, sqlite3_errmsg(self->conn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define validate_column()\
|
||||||
|
i32 errcode = sqlite3_errcode(self->conn);\
|
||||||
|
if(errcode != SQLITE_OK && errcode != SQLITE_ROW){\
|
||||||
|
return RESULT_ERROR_SQLITE_CODE(errcode, sqlite3_errmsg(self->conn));\
|
||||||
|
}
|
||||||
|
|
||||||
Result(i64) tsqlite_statement_getResult_i64(tsqlite_statement* self){
|
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
|
|
||||||
self->result_col++;
|
self->result_col++;
|
||||||
|
i64 r = sqlite3_column_int64(self->st, self->result_col);
|
||||||
|
validate_column();
|
||||||
return RESULT_VALUE(i, r);
|
return RESULT_VALUE(i, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(i64) tsqlite_statement_getResult_f64(tsqlite_statement* self){
|
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
|
|
||||||
self->result_col++;
|
self->result_col++;
|
||||||
|
f64 r = sqlite3_column_double(self->st, self->result_col);
|
||||||
|
validate_column();
|
||||||
return RESULT_VALUE(f, r);
|
return RESULT_VALUE(f, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) tsqlite_statement_getResult_str(tsqlite_statement* self, str* out_v){
|
Result(void) tsqlite_statement_getResult_str(tsqlite_statement* self, str* out_v){
|
||||||
(void)self;
|
self->result_col++;
|
||||||
(void)out_v;
|
void* data = (void*)sqlite3_column_text(self->st, self->result_col);
|
||||||
// TODO: tsqlite_statement_getResult_str
|
validate_column();
|
||||||
|
u32 size = sqlite3_column_bytes(self->st, self->result_col);
|
||||||
|
*out_v = str_construct(data, size, true);
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) tsqlite_statement_getResult_blob(tsqlite_statement* self, Array(u8)* out_v){
|
Result(void) tsqlite_statement_getResult_blob(tsqlite_statement* self, Array(u8)* out_v){
|
||||||
(void)self;
|
self->result_col++;
|
||||||
(void)out_v;
|
void* data = (void*)sqlite3_column_blob(self->st, self->result_col);
|
||||||
// TODO: tsqlite_statement_getResult_blob
|
validate_column();
|
||||||
|
u32 size = sqlite3_column_bytes(self->st, self->result_col);
|
||||||
|
*out_v = Array_u8_construct(data, size);
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,3 +13,15 @@ Result(void) tsqlite_init(){
|
|||||||
void tsqlite_deinit(){
|
void tsqlite_deinit(){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
str tsqlite_error_toStr(i32 code, cstr msg_cstr){
|
||||||
|
str msg = str_from_cstr(msg_cstr);
|
||||||
|
StringBuilder sb = StringBuilder_alloc(64 + msg.len);
|
||||||
|
|
||||||
|
StringBuilder_append_cstr(&sb, sqlite3_errstr(code));
|
||||||
|
StringBuilder_append_char(&sb, ':');
|
||||||
|
StringBuilder_append_char(&sb, ' ');
|
||||||
|
StringBuilder_append_str(&sb, msg);
|
||||||
|
|
||||||
|
return StringBuilder_getStr(&sb);
|
||||||
|
}
|
||||||
|
|||||||
56
tests/main.c
56
tests/main.c
@@ -1,5 +1,59 @@
|
|||||||
#include "tsqlite.h"
|
#include "tsqlite.h"
|
||||||
|
|
||||||
|
#define _create_statement(SQL, TMP_VAR){\
|
||||||
|
try(tsqlite_statement* TMP_VAR, p, tsqlite_statement_compile(conn, STR(SQL)));\
|
||||||
|
Defer(tsqlite_statement_free(TMP_VAR));\
|
||||||
|
st = TMP_VAR;\
|
||||||
|
}
|
||||||
|
#define create_statement(SQL) _create_statement(SQL, CAT2(_st_, __LINE__))
|
||||||
|
|
||||||
|
#define exec_statement_no_result() \
|
||||||
|
printf("executing SQL statement:\n%s\n", sqlite3_sql(st->st));\
|
||||||
|
try_void(tsqlite_statement_step(st));\
|
||||||
|
|
||||||
|
Result(void) test_connection(){
|
||||||
|
Deferral(8);
|
||||||
|
|
||||||
|
try(tsqlite_connection* conn, p, tsqlite_connection_open("db.sqlite"));
|
||||||
|
Defer(tsqlite_connection_close(conn));
|
||||||
|
|
||||||
|
tsqlite_statement* st;
|
||||||
|
create_statement("DROP TABLE IF EXISTS Test;");
|
||||||
|
exec_statement_no_result();
|
||||||
|
|
||||||
|
create_statement(
|
||||||
|
"CREATE TABLE Test (\n"
|
||||||
|
" id INTEGER PRIMARY KEY,\n"
|
||||||
|
" a TEXT\n"
|
||||||
|
");");
|
||||||
|
exec_statement_no_result();
|
||||||
|
|
||||||
|
create_statement(
|
||||||
|
"INSERT INTO Test(a) VALUES\n"
|
||||||
|
" ('ooeeoo'),\n"
|
||||||
|
" ('wiwiwi');");
|
||||||
|
exec_statement_no_result();
|
||||||
|
|
||||||
|
create_statement("SELECT * FROM Test WHERE id IS NOT $nul;");
|
||||||
|
try_void(tsqlite_statement_bind_null(st, "$nul"));
|
||||||
|
printf("executing SQL statement:\n%s\n", sqlite3_sql(st->st));
|
||||||
|
while(true) {
|
||||||
|
try(bool has_result, i, tsqlite_statement_step(st));
|
||||||
|
if(!has_result)
|
||||||
|
break;
|
||||||
|
i32 column_count = sqlite3_column_count(st->st);
|
||||||
|
str cell_str;
|
||||||
|
printf("ROW(%i):", st->result_row);
|
||||||
|
for(i32 i = 0; i < column_count; i++){
|
||||||
|
try_void(tsqlite_statement_getResult_str(st, &cell_str));
|
||||||
|
printf(" [%i]='"FMT_str"'", st->result_col, str_expand(cell_str));
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
int main(){
|
int main(){
|
||||||
Deferral(32);
|
Deferral(32);
|
||||||
|
|
||||||
@@ -8,5 +62,7 @@ int main(){
|
|||||||
Defer(tlibc_deinit());
|
Defer(tlibc_deinit());
|
||||||
Defer(tsqlite_deinit());
|
Defer(tsqlite_deinit());
|
||||||
|
|
||||||
|
try_fatal_void(test_connection());
|
||||||
|
|
||||||
Return 0;
|
Return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user