Compare commits

..

4 Commits

Author SHA1 Message Date
a7e2b6b7a7 param count, name, index 2026-03-03 23:58:19 -05:00
2df9f94a92 docs on bind functions 2026-03-03 23:33:22 -05:00
2fa1833736 clear bindings 2026-03-03 23:30:56 -05:00
19e6aa82a6 make C constants values 2026-03-03 23:24:51 -05:00
4 changed files with 115 additions and 43 deletions

View File

@@ -53,11 +53,16 @@ pub extern column-count(^stmt: any): int
pub extern column-type(^stmt: any, col: int32): int32 pub extern column-type(^stmt: any, col: int32): int32
c "kk_sqlite3_column_type" c "kk_sqlite3_column_type"
pub extern int32/integer(): int32 { c inline "SQLITE_INTEGER" } extern make-sqlite-integer(): int32 { c inline "SQLITE_INTEGER" }
pub extern int32/float(): int32 { c inline "SQLITE_FLOAT" } pub val int32/integer = make-sqlite-integer()
pub extern int32/blob(): int32 { c inline "SQLITE_BLOB" } extern make-sqlite-float(): int32 { c inline "SQLITE_FLOAT" }
pub extern int32/null(): int32 { c inline "SQLITE_NULL" } pub val int32/float = make-sqlite-float()
pub extern int32/text(): int32 { c inline "SQLITE_TEXT" } extern make-sqlite-blob(): int32 { c inline "SQLITE_BLOB" }
pub val int32/blob = make-sqlite-blob()
extern make-sqlite-null(): int32 { c inline "SQLITE_NULL" }
pub val int32/null = make-sqlite-null()
extern make-sqlite-text(): int32 { c inline "SQLITE_TEXT" }
pub val int32/text = make-sqlite-text()
//TODO(zephyr): waiting on a proper bytes type in koka //TODO(zephyr): waiting on a proper bytes type in koka
// pub extern column-blob(^stmt: any, col: int32): maybe<bytes> // pub extern column-blob(^stmt: any, col: int32): maybe<bytes>
@@ -75,17 +80,29 @@ pub extern column-text(^stmt: any, col: int32): string
// pub extern bind-blob(^stmt: any, col: int32, ^value: bytes): int32 // pub extern bind-blob(^stmt: any, col: int32, ^value: bytes): int32
// c "kk_sqlite3_bind_blob" // 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" 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" 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" 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" 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" 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"

View File

@@ -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) { kk_string_t kk_sqlite3_errstr(int32_t x, kk_context_t *_ctx) {
const char *s = sqlite3_errstr((int)x); 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) { if (s == NULL) {
return kk_string_empty(); return kk_string_empty();
} }
@@ -116,29 +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); 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); 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); 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); 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); sqlite3_stmt *stmt = borrow_stmt(cref, _ctx);
kk_ssize_t len = 0; kk_ssize_t len = 0;
const char *text = kk_string_cbuf_borrow(value, &len, _ctx); 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); 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);
} }

View File

@@ -1,7 +1,7 @@
// Low level wrapper around a modern subset of the SQLite3 API. // Low level wrapper around a modern subset of the SQLite3 API.
// //
// Using this module involves manual resource management. // Using this module involves manual resource management.
// // Statements must be finalized and databases must be closed.
module sqlite/sqlite3 module sqlite/sqlite3
import sqlite/capi import sqlite/capi
@@ -143,10 +143,10 @@ pub type sqlite3-type
// The leftmost column is numbered 0. // The leftmost column is numbered 0.
pub fun statment/column-type(stmt: statement, col: int): sqlite3-type pub fun statment/column-type(stmt: statement, col: int): sqlite3-type
val t = capi/column-type(stmt.cref, col.int32) val t = capi/column-type(stmt.cref, col.int32)
if t == int32/integer() then Integer if t == capi/int32/integer then Integer
else if t == int32/float() then Float else if t == capi/int32/float then Float
else if t == int32/blob() then Blob else if t == capi/int32/blob then Blob
else if t == int32/text() then Text else if t == capi/int32/text then Text
// If the DB gives us something unimaginable, pretend it's nothing. // If the DB gives us something unimaginable, pretend it's nothing.
else Null else Null
@@ -169,17 +169,45 @@ pub fun statement/int(stmt: statement, col: int): int
pub fun statement/text(stmt: statement, col: int): string pub fun statement/text(stmt: statement, col: int): string
capi/column-text(stmt.cref, col.int32) capi/column-text(stmt.cref, col.int32)
pub fun statement/bind-float64(stmt: statement, col: int, value: float64): sqlite-result // Bind a float64 value to a statement parameter.
capi/bind-double(stmt.cref, col.int32, value).trusted-result // The first pararmeter number is 1.
pub fun statement/bind-float64(stmt: statement, param: int, value: float64): sqlite-result
capi/bind-double(stmt.cref, param.int32, value).trusted-result
pub fun statement/bind-int64(stmt: statement, col: int, value: int64): sqlite-result // Bind an int64 value to a statement parameter.
capi/bind-int64(stmt.cref, col.int32, value).trusted-result // The first parameter number is 1.
pub fun statement/bind-int64(stmt: statement, param: int, value: int64): sqlite-result
capi/bind-int64(stmt.cref, param.int32, value).trusted-result
pub fun statement/bind-null(stmt: statement, col: int): sqlite-result // Bind NULL to a statement parameter.
capi/bind-null(stmt.cref, col.int32).trusted-result // The first parameter number is 1.
pub fun statement/bind-null(stmt: statement, param: int): sqlite-result
capi/bind-null(stmt.cref, param.int32).trusted-result
pub fun statement/bind-text(stmt: statement, col: int, value: string): sqlite-result // Bind a string to a statement parameter.
capi/bind-text(stmt.cref, col.int32, value).trusted-result // The first parameter number is 1.
pub fun statement/bind-text(stmt: statement, param: int, value: string): sqlite-result
capi/bind-text(stmt.cref, param.int32, value).trusted-result
pub fun statement/bind-zeroblob(stmt: statement, col: int, length: int64): sqlite-result // Bind a BLOB value initialized to `length` zeros to a statement parameter.
capi/bind-zeroblob(stmt.cref, col.int32, length).trusted-result // The first parameter number is 1.
pub fun statement/bind-zeroblob(stmt: statement, param: int, length: int64): sqlite-result
capi/bind-zeroblob(stmt.cref, param.int32, length).trusted-result
// 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

View File

@@ -10,17 +10,17 @@ tail fun do-while(f: () -> <div|e> maybe<a>): <div|e> a
Nothing -> do-while(f) Nothing -> do-while(f)
Just(r) -> r Just(r) -> r
fun stmt(db: sqlite3, sql: string, bind: maybe<string> = Nothing) fun stmt(db: sqlite3, sql: string, bind: list<(string, string)> = Nil)
println(sql) println(sql)
match db.prepare(sql.slice) match db.prepare(sql.slice)
Left(r-prep) -> println(" prepare result: " ++ r-prep.show) Left(r-prep) -> println(" prepare result: " ++ r-prep.show)
Right((stmt, rest)) -> Right((stmt, rest)) ->
println(" prepare remainder: " ++ rest.show) println(" prepare remainder: " ++ rest.show)
match bind println(" param count: " ++ stmt.param-count.show)
Nothing -> () bind.foreach() fn((name, text))
Just(text) -> val idx = stmt.param-index(name)
val r = stmt.bind-text(1, text) val r = stmt.bind-text(idx, text)
println(" bind result: " ++ r.show) println(" bind " ++ name ++ " result: " ++ r.show)
val r-step = do-while val r-step = do-while
val r = stmt.step val r = stmt.step
match r match r
@@ -39,7 +39,7 @@ pub fun main()
println("open result: " ++ r-open.show) println("open result: " ++ r-open.show)
stmt(db, "DROP TABLE IF EXISTS koka; -- reset previous test runs") stmt(db, "DROP TABLE IF EXISTS koka; -- reset previous test runs")
stmt(db, "CREATE TABLE koka(v TEXT NOT NULL)") 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") stmt(db, "SELECT * FROM koka")
val r-close = db.close val r-close = db.close
println("close result: " ++ r-close.show) println("close result: " ++ r-close.show)