diff --git a/sqlite/capi.kk b/sqlite/capi.kk index 3124760..098a441 100644 --- a/sqlite/capi.kk +++ b/sqlite/capi.kk @@ -80,20 +80,29 @@ pub extern column-text(^stmt: any, col: int32): string // pub extern bind-blob(^stmt: any, col: int32, ^value: bytes): int32 // c "kk_sqlite3_bind_blob" -pub extern bind-double(^stmt: any, col: int32, ^value: float64): int32 +pub extern bind-double(^stmt: any, param: int32, ^value: float64): int32 c "kk_sqlite3_bind_double" -pub extern bind-int64(^stmt: any, col: int32, value: int64): int32 +pub extern bind-int64(^stmt: any, param: int32, value: int64): int32 c "kk_sqlite3_bind_int64" -pub extern bind-null(^stmt: any, col: int32): int32 +pub extern bind-null(^stmt: any, param: int32): int32 c "kk_sqlite3_bind_null" -pub extern bind-text(^stmt: any, col: int32, ^value: string): int32 +pub extern bind-text(^stmt: any, param: int32, ^value: string): int32 c "kk_sqlite3_bind_text" -pub extern bind-zeroblob(^stmt: any, col: int32, length: int64): int32 +pub extern bind-zeroblob(^stmt: any, param: int32, length: int64): int32 c "kk_sqlite3_bind_zeroblob" pub extern clear-bindings(^stmt: any): int32 c "kk_sqlite3_clear_bindings" + +pub extern parameter-count(^stmt: any): int32 + c "kk_sqlite3_parameter_count" + +pub extern parameter-name(^stmt: any, param: int32): string + c "kk_sqlite3_parameter_name" + +pub extern parameter-index(^stmt: any, name: string): int32 + c "kk_sqlite3_parameter_index" diff --git a/sqlite/sqlite-inline.c b/sqlite/sqlite-inline.c index 0c22e5b..569e600 100644 --- a/sqlite/sqlite-inline.c +++ b/sqlite/sqlite-inline.c @@ -7,6 +7,8 @@ static inline sqlite3 *borrow_db(kk_box_t cref, kk_context_t *_ctx) { kk_string_t kk_sqlite3_errstr(int32_t x, kk_context_t *_ctx) { const char *s = sqlite3_errstr((int)x); + // NOTE(zephyr): kk_string_alloc_from_utf8 calls C stdlib strlen(), + // which is not guaranteed to handle a NULL pointer. if (s == NULL) { return kk_string_empty(); } @@ -116,34 +118,54 @@ kk_string_t kk_sqlite3_column_text(kk_box_t cref, int32_t col, kk_context_t *_ct return kk_string_alloc_from_qutf8n((kk_ssize_t)len, (const char *)text, _ctx); } -int32_t kk_sqlite3_bind_double(kk_box_t cref, int32_t col, double value, kk_context_t *_ctx) { +int32_t kk_sqlite3_bind_double(kk_box_t cref, int32_t param, double value, kk_context_t *_ctx) { sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); - return sqlite3_bind_double(stmt, col, value); + return sqlite3_bind_double(stmt, param, value); } -int32_t kk_sqlite3_bind_int64(kk_box_t cref, int32_t col, int64_t value, kk_context_t *_ctx) { +int32_t kk_sqlite3_bind_int64(kk_box_t cref, int32_t param, int64_t value, kk_context_t *_ctx) { sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); - return sqlite3_bind_int64(stmt, col, (sqlite3_int64)value); + return sqlite3_bind_int64(stmt, param, (sqlite3_int64)value); } -int32_t kk_sqlite3_bind_null(kk_box_t cref, int32_t col, kk_context_t *_ctx) { +int32_t kk_sqlite3_bind_null(kk_box_t cref, int32_t param, kk_context_t *_ctx) { sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); - return sqlite3_bind_null(stmt, col); + return sqlite3_bind_null(stmt, param); } -int32_t kk_sqlite3_bind_text(kk_box_t cref, int32_t col, kk_string_t value, kk_context_t *_ctx) { +int32_t kk_sqlite3_bind_text(kk_box_t cref, int32_t param, kk_string_t value, kk_context_t *_ctx) { sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); kk_ssize_t len = 0; const char *text = kk_string_cbuf_borrow(value, &len, _ctx); - return sqlite3_bind_text64(stmt, col, text, (sqlite3_uint64)len, SQLITE_TRANSIENT, SQLITE_UTF8); + return sqlite3_bind_text64(stmt, param, text, (sqlite3_uint64)len, SQLITE_TRANSIENT, SQLITE_UTF8); } -int32_t kk_sqlite3_bind_zeroblob(kk_box_t cref, int32_t col, int64_t length, kk_context_t *_ctx) { +int32_t kk_sqlite3_bind_zeroblob(kk_box_t cref, int32_t param, int64_t length, kk_context_t *_ctx) { sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); - return sqlite3_bind_zeroblob64(stmt, col, (sqlite3_uint64)length); + return sqlite3_bind_zeroblob64(stmt, param, (sqlite3_uint64)length); } int32_t kk_sqlite3_clear_bindings(kk_box_t cref, kk_context_t *_ctx) { sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); return sqlite3_clear_bindings(stmt); } + +int32_t kk_sqlite3_parameter_count(kk_box_t cref, kk_context_t *_ctx) { + sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); + return (int32_t)sqlite3_bind_parameter_count(stmt); +} + +kk_string_t kk_sqlite3_parameter_name(kk_box_t cref, int32_t param, kk_context_t *_ctx) { + sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); + const char *name = sqlite3_bind_parameter_name(stmt, (int)param); + if (name == NULL) { + return kk_string_empty(); + } + return kk_string_alloc_from_utf8(name, _ctx); +} + +int32_t kk_sqlite3_parameter_index(kk_box_t cref, kk_string_t name, kk_context_t *_ctx) { + sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); + const char *s = kk_string_cbuf_borrow(name, NULL, _ctx); + return (int32_t)sqlite3_bind_parameter_index(stmt, s); +} diff --git a/sqlite/sqlite3.kk b/sqlite/sqlite3.kk index 66023ba..d717cf4 100644 --- a/sqlite/sqlite3.kk +++ b/sqlite/sqlite3.kk @@ -197,3 +197,17 @@ pub fun statement/bind-zeroblob(stmt: statement, param: int, length: int64): sql // Set all bound parameters to NULL. pub fun statement/clear-bindings(stmt: statement): sqlite-result capi/clear-bindings(stmt.cref).trusted-result + +// Get the number of bindable parameters in the statement. +pub fun statement/param-count(stmt: statement): int + capi/parameter-count(stmt.cref).int + +// Get the name of a parameter. +// The first parameter number is 1. +pub fun statement/param-name(stmt: statement, param: int): string + capi/parameter-name(stmt.cref, param.int32) + +// Get the index of the parameter with the given name. +// The result is suitable to pass to bind functions. +pub fun statement/param-index(stmt: statement, name: string): int + capi/parameter-index(stmt.cref, name).int diff --git a/test/test.kk b/test/test.kk index cc8da20..b158cfb 100644 --- a/test/test.kk +++ b/test/test.kk @@ -10,17 +10,17 @@ tail fun do-while(f: () -> maybe): a Nothing -> do-while(f) Just(r) -> r -fun stmt(db: sqlite3, sql: string, bind: maybe = Nothing) +fun stmt(db: sqlite3, sql: string, bind: list<(string, string)> = Nil) println(sql) match db.prepare(sql.slice) Left(r-prep) -> println(" prepare result: " ++ r-prep.show) Right((stmt, rest)) -> println(" prepare remainder: " ++ rest.show) - match bind - Nothing -> () - Just(text) -> - val r = stmt.bind-text(1, text) - println(" bind result: " ++ r.show) + println(" param count: " ++ stmt.param-count.show) + bind.foreach() fn((name, text)) + val idx = stmt.param-index(name) + val r = stmt.bind-text(idx, text) + println(" bind " ++ name ++ " result: " ++ r.show) val r-step = do-while val r = stmt.step match r @@ -29,7 +29,7 @@ fun stmt(db: sqlite3, sql: string, bind: maybe = Nothing) println(" col " ++ i.show ++ ": " ++ stmt.text(i)) Nothing r -> Just(r) - println(" final step result: " ++ r-step.show) + println(" final step result: " ++ r-step.show) val r-fin = stmt.finalize println(" finalize result: " ++ r-fin.show) @@ -39,7 +39,7 @@ pub fun main() println("open result: " ++ r-open.show) stmt(db, "DROP TABLE IF EXISTS koka; -- reset previous test runs") stmt(db, "CREATE TABLE koka(v TEXT NOT NULL)") - stmt(db, "INSERT INTO koka VALUES (?);", Just("value inserted from koka")) + stmt(db, "INSERT INTO koka VALUES (?1);", [("?1", "value inserted from koka")]) stmt(db, "SELECT * FROM koka") val r-close = db.close println("close result: " ++ r-close.show)