diff --git a/include/tsqlite.h b/include/tsqlite.h index e60dadd..12e25e2 100644 --- a/include/tsqlite.h +++ b/include/tsqlite.h @@ -53,8 +53,19 @@ Result(tsqlite_statement*) tsqlite_statement_compile(tsqlite_connection* conn, s void tsqlite_statement_free(tsqlite_statement* st); -/// @brief execute statement or move to next result row. Documentation: https://sqlite.org/c3ref/step.html -/// @return is next result row avaliable +/// @brief execute statement or move to next result row. +/// 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); /// call this after executing a compiled statement to use it again diff --git a/src/statement.c b/src/statement.c index 6d81901..f64d53a 100644 --- a/src/statement.c +++ b/src/statement.c @@ -3,12 +3,20 @@ Result(tsqlite_statement*) tsqlite_statement_compile(tsqlite_connection* conn, str sql_code){ sqlite3_stmt* st = NULL; 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)); self->conn = conn; self->st = st; - self->result_row = 0; - self->result_col = 0; + self->result_row = -1; + self->result_col = -1; return RESULT_VALUE(p, self); } @@ -21,8 +29,8 @@ void tsqlite_statement_free(tsqlite_statement* self){ Result(void) tsqlite_statement_reset(tsqlite_statement* self){ try_sqlite3(self->conn, sqlite3_reset(self->st)); - self->result_row = 0; - self->result_col = 0; + self->result_row = -1; + self->result_col = -1; return RESULT_VOID; } @@ -30,7 +38,7 @@ Result(void) tsqlite_statement_reset(tsqlite_statement* self){ #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));\ + 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){ @@ -74,45 +82,52 @@ Result(bool) tsqlite_statement_execNext(tsqlite_statement* self){ int r = sqlite3_step(self->st); if(r == SQLITE_ROW){ self->result_row++; + self->result_col = -1; return RESULT_VALUE(i, true); } if(r == SQLITE_DONE){ self->result_row++; + self->result_col = -1; return RESULT_VALUE(i, false); } 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){ - i64 r = sqlite3_column_int64(self->st, self->result_col); - try_sqlite3(self->conn, sqlite3_errcode(self->conn)); self->result_col++; + i64 r = sqlite3_column_int64(self->st, self->result_col); + validate_column(); return RESULT_VALUE(i, r); } 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++; + f64 r = sqlite3_column_double(self->st, self->result_col); + validate_column(); return RESULT_VALUE(f, r); } 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); - try_sqlite3(self->conn, sqlite3_errcode(self->conn)); + validate_column(); 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){ + 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); *out_v = Array_u8_construct(data, size); - self->result_col++; return RESULT_VOID; } diff --git a/tests/main.c b/tests/main.c index f1b1397..f67dc4d 100644 --- a/tests/main.c +++ b/tests/main.c @@ -13,29 +13,56 @@ Result(void) test_connection(){ 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); 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"))); - Defer(tsqlite_statement_free(st)); + tsqlite_statement* 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; - 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); + create_statement( + "INSERT INTO Test(a) VALUES\n" + " ('ooeeoo'),\n" + " ('wiwiwi');"); + exec_statement_no_result(); - // close manually to test tsqlite_connection_close - try_void(tsqlite_connection_close(conn)); - conn = NULL; + 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)); + printf(" [%i]='"FMT_str"'", st->result_col, str_expand(cell_str)); + } + printf("\n"); + }; Return RESULT_VOID; } @@ -49,7 +76,7 @@ int main(){ Defer(tsqlite_deinit()); try_fatal_void(test_connection()); - try_fatal_void(test_statement()); + try_fatal_void(test_statements()); Return 0; }