fixed tsqlite_statement bugs
This commit is contained in:
@@ -53,8 +53,19 @@ Result(tsqlite_statement*) tsqlite_statement_compile(tsqlite_connection* conn, s
|
|||||||
|
|
||||||
void tsqlite_statement_free(tsqlite_statement* st);
|
void tsqlite_statement_free(tsqlite_statement* st);
|
||||||
|
|
||||||
/// @brief execute statement or move to next result row. Documentation: https://sqlite.org/c3ref/step.html
|
/// @brief execute statement or move to next result row.
|
||||||
/// @return is next result row avaliable
|
/// Documentation: https://sqlite.org/c3ref/step.html
|
||||||
|
///
|
||||||
|
/// USAGE:
|
||||||
|
/// ```
|
||||||
|
/// while(true){
|
||||||
|
/// try(bool has_result, i, tsqlite_statement_execNext(st));
|
||||||
|
/// if(!has_result)
|
||||||
|
/// break;
|
||||||
|
/// /* get result columns */
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// @return is result row avaliable
|
||||||
Result(bool) tsqlite_statement_execNext(tsqlite_statement* self);
|
Result(bool) tsqlite_statement_execNext(tsqlite_statement* self);
|
||||||
|
|
||||||
/// call this after executing a compiled statement to use it again
|
/// call this after executing a compiled statement to use it again
|
||||||
|
|||||||
@@ -3,12 +3,20 @@
|
|||||||
Result(tsqlite_statement*) tsqlite_statement_compile(tsqlite_connection* 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(conn, 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));
|
||||||
self->conn = conn;
|
self->conn = conn;
|
||||||
self->st = st;
|
self->st = st;
|
||||||
self->result_row = 0;
|
self->result_row = -1;
|
||||||
self->result_col = 0;
|
self->result_col = -1;
|
||||||
return RESULT_VALUE(p, self);
|
return RESULT_VALUE(p, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,8 +29,8 @@ 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(self->conn, sqlite3_reset(self->st));
|
try_sqlite3(self->conn, sqlite3_reset(self->st));
|
||||||
self->result_row = 0;
|
self->result_row = -1;
|
||||||
self->result_col = 0;
|
self->result_col = -1;
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +38,7 @@ Result(void) tsqlite_statement_reset(tsqlite_statement* self){
|
|||||||
#define tryGetBindIndex(key) \
|
#define tryGetBindIndex(key) \
|
||||||
i32 bind_index = sqlite3_bind_parameter_index(self->st, key);\
|
i32 bind_index = sqlite3_bind_parameter_index(self->st, key);\
|
||||||
if(bind_index == 0){\
|
if(bind_index == 0){\
|
||||||
return RESULT_ERROR_SQLITE_CODE(self->conn, sqlite3_errcode(self->conn));\
|
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){
|
Result(void) tsqlite_statement_bind_null(tsqlite_statement* self, cstr key){
|
||||||
@@ -74,45 +82,52 @@ Result(bool) tsqlite_statement_execNext(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_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_row++;
|
||||||
|
self->result_col = -1;
|
||||||
return RESULT_VALUE(i, false);
|
return RESULT_VALUE(i, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return RESULT_ERROR_SQLITE_CODE(self->conn, r);
|
return RESULT_ERROR_SQLITE_CODE(self->conn, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define validate_column()\
|
||||||
|
i32 errcode = sqlite3_errcode(self->conn);\
|
||||||
|
if(errcode != SQLITE_OK && errcode != SQLITE_ROW){\
|
||||||
|
return RESULT_ERROR_SQLITE_CODE(self->conn, errcode);\
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
try_sqlite3(self->conn, sqlite3_errcode(self->conn));
|
|
||||||
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);
|
|
||||||
try_sqlite3(self->conn, sqlite3_errcode(self->conn));
|
|
||||||
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){
|
||||||
|
self->result_col++;
|
||||||
void* data = (void*)sqlite3_column_text(self->st, self->result_col);
|
void* data = (void*)sqlite3_column_text(self->st, self->result_col);
|
||||||
try_sqlite3(self->conn, sqlite3_errcode(self->conn));
|
validate_column();
|
||||||
u32 size = sqlite3_column_bytes(self->st, self->result_col);
|
u32 size = sqlite3_column_bytes(self->st, self->result_col);
|
||||||
*out_v = str_construct(data, size, true);
|
*out_v = str_construct(data, size, true);
|
||||||
self->result_col++;
|
|
||||||
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){
|
||||||
|
self->result_col++;
|
||||||
void* data = (void*)sqlite3_column_blob(self->st, self->result_col);
|
void* data = (void*)sqlite3_column_blob(self->st, self->result_col);
|
||||||
try_sqlite3(self->conn, sqlite3_errcode(self->conn));
|
validate_column();
|
||||||
u32 size = sqlite3_column_bytes(self->st, self->result_col);
|
u32 size = sqlite3_column_bytes(self->st, self->result_col);
|
||||||
*out_v = Array_u8_construct(data, size);
|
*out_v = Array_u8_construct(data, size);
|
||||||
self->result_col++;
|
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|||||||
61
tests/main.c
61
tests/main.c
@@ -13,29 +13,56 @@ Result(void) test_connection(){
|
|||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) test_statement(){
|
#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_execNext(st));\
|
||||||
|
|
||||||
|
Result(void) test_statements(){
|
||||||
Deferral(8);
|
Deferral(8);
|
||||||
|
|
||||||
try(tsqlite_connection* conn, p, tsqlite_connection_open("db.sqlite"));
|
try(tsqlite_connection* conn, p, tsqlite_connection_open("db.sqlite"));
|
||||||
Defer(if(conn != NULL) { IGNORE_RESULT tsqlite_connection_close(conn); });
|
Defer(IGNORE_RESULT tsqlite_connection_close(conn));
|
||||||
|
|
||||||
try(tsqlite_statement* st, p, tsqlite_statement_compile(conn, STR("meow meow")));
|
tsqlite_statement* st;
|
||||||
Defer(tsqlite_statement_free(st));
|
create_statement("DROP TABLE IF EXISTS Test;");
|
||||||
|
exec_statement_no_result();
|
||||||
|
|
||||||
try_void(tsqlite_statement_bind_null(st, "nul"));
|
create_statement(
|
||||||
|
"CREATE TABLE Test (\n"
|
||||||
|
" id INTEGER PRIMARY KEY,\n"
|
||||||
|
" a TEXT\n"
|
||||||
|
");");
|
||||||
|
exec_statement_no_result();
|
||||||
|
|
||||||
bool has_next_row = false;
|
create_statement(
|
||||||
do {
|
"INSERT INTO Test(a) VALUES\n"
|
||||||
try(has_next_row, i, tsqlite_statement_execNext(st));
|
" ('ooeeoo'),\n"
|
||||||
str cell_str = str_null;
|
" ('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_execNext(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));
|
try_void(tsqlite_statement_getResult_str(st, &cell_str));
|
||||||
printf("RESULT(%i, %i): '"FMT_str"'\n",
|
printf(" [%i]='"FMT_str"'", st->result_col, str_expand(cell_str));
|
||||||
st->result_row, st->result_col, str_expand(cell_str));
|
}
|
||||||
} while(has_next_row);
|
printf("\n");
|
||||||
|
};
|
||||||
// close manually to test tsqlite_connection_close
|
|
||||||
try_void(tsqlite_connection_close(conn));
|
|
||||||
conn = NULL;
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
@@ -49,7 +76,7 @@ int main(){
|
|||||||
Defer(tsqlite_deinit());
|
Defer(tsqlite_deinit());
|
||||||
|
|
||||||
try_fatal_void(test_connection());
|
try_fatal_void(test_connection());
|
||||||
try_fatal_void(test_statement());
|
try_fatal_void(test_statements());
|
||||||
|
|
||||||
Return 0;
|
Return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user