diff --git a/sqlite/capi.kk b/sqlite/capi.kk index 093652a..07cccf5 100644 --- a/sqlite/capi.kk +++ b/sqlite/capi.kk @@ -47,3 +47,27 @@ pub extern step(^stmt: any): int32 pub extern reset(^stmt: any): int32 c "kk_sqlite3_reset" + +pub extern column-count(^stmt: any): int + c "kk_sqlite3_column_count" + +pub extern column-type(^stmt: any, col: int32): int32 + c "kk_sqlite3_column_type" +pub extern int32/integer(): int32 { c inline "SQLITE_INTEGER" } +pub extern int32/float(): int32 { c inline "SQLITE_FLOAT" } +pub extern int32/blob(): int32 { c inline "SQLITE_BLOB" } +pub extern int32/null(): int32 { c inline "SQLITE_NULL" } +pub extern int32/text(): int32 { c inline "SQLITE_TEXT" } + +//TODO(zephyr): waiting on a proper bytes type in koka +// pub extern column-blob(^stmt: any, col: int32): maybe +// c "kk_sqlite3_column_blob" + +pub extern column-double(^stmt: any, col: int32): float64 + c "kk_sqlite3_column_double" + +pub extern column-integer(^stmt: any, col: int32): int + c "kk_sqlite3_column_integer" + +pub extern column-text(^stmt: any, col: int32): string + c "kk_sqlite3_column_text" diff --git a/sqlite/sqlite-inline.c b/sqlite/sqlite-inline.c index a6bda8c..a8f883f 100644 --- a/sqlite/sqlite-inline.c +++ b/sqlite/sqlite-inline.c @@ -81,3 +81,37 @@ int32_t kk_sqlite3_reset(kk_box_t cref, kk_context_t *_ctx) { sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); return (int32_t)sqlite3_reset(stmt); } + +kk_integer_t kk_sqlite3_column_count(kk_box_t cref, kk_context_t *_ctx) { + sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); + int r = sqlite3_column_count(stmt); + return kk_integer_from_int(r, _ctx); +} + +int32_t kk_sqlite3_column_type(kk_box_t cref, int32_t col, kk_context_t *_ctx) { + sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); + return (int32_t)sqlite3_column_type(stmt, (int)col); +} + +double kk_sqlite3_column_double(kk_box_t cref, int32_t col, kk_context_t *_ctx) { + sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); + return sqlite3_column_double(stmt, (int)col); +} + +kk_integer_t kk_sqlite3_column_integer(kk_box_t cref, int32_t col, kk_context_t *_ctx) { + sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); + sqlite3_int64 r = sqlite3_column_int64(stmt, (int)col); + return kk_integer_from_int64((int64_t)r, _ctx); +} + +kk_string_t kk_sqlite3_column_text(kk_box_t cref, int32_t col, kk_context_t *_ctx) { + sqlite3_stmt *stmt = borrow_stmt(cref, _ctx); + const unsigned char *text = sqlite3_column_text(stmt, (int)col); + if (text == NULL) { + // The column value is NULL, but we can't put that in a Koka string. + // Since a string was requested, return an empty string instead. + return kk_string_empty(); + } + int len = sqlite3_column_bytes(stmt, (int)col); + return kk_string_alloc_from_qutf8n((kk_ssize_t)len, (const char *)text, _ctx); +} diff --git a/sqlite/sqlite3.kk b/sqlite/sqlite3.kk index 83354b0..aa2ec60 100644 --- a/sqlite/sqlite3.kk +++ b/sqlite/sqlite3.kk @@ -95,6 +95,10 @@ pub fun close(db: sqlite3): sqlite-result abstract struct statement cref: any +// Prepare a statement. +// The Right value contains the prepared statement and the portion of `sql` +// remaining after parsing the first statement. +// Once use is finished, the statement must be `finalize`d. pub fun prepare(db: sqlite3, sql: sslice, persistent: bool = False, disable-vtab: bool = False, disable-log: bool = False): either val flags = 0.i32 .or(persistent.int32 * 0x01.i32) @@ -106,11 +110,61 @@ pub fun prepare(db: sqlite3, sql: sslice, persistent: bool = False, disable-vtab else Left(r.trusted-result(capi/error-offset(db.cref))) +// Release resources associated with a statement. +// Once finalized, a statement can no longer be used. +// Every statement must be finalized. pub fun statement/finalize(stmt: statement): sqlite-result capi/finalize(stmt.cref).trusted-result +// Perform one evaluation step of the statement. +// If the result is `Row`, values are available. +// If the result is `Done`, the statement has finished executing, and it should +// be either `reset` or `finalize`d. pub fun statement/step(stmt: statement): sqlite-result capi/step(stmt.cref).trusted-result +// Reset a statement so that it can be evaluated again. pub fun statement/reset(stmt: statement): sqlite-result capi/reset(stmt.cref).trusted-result + +// Get the number of columns available in the statement's result. +pub fun statement/column-count(stmt: statement): int + capi/column-count(stmt.cref) + +// Datatype codes for query results. +pub type sqlite3-type + Integer + Float + Text + Blob + Null + +// Get the result affinity of a column in the statement's result. +// The leftmost column is numbered 0. +pub fun statment/column-type(stmt: statement, col: int): sqlite3-type + val t = capi/column-type(stmt.cref, col.int32) + if t == int32/integer() then Integer + else if t == int32/float() then Float + else if t == int32/blob() then Blob + else if t == int32/text() then Text + // If the DB gives us something unimaginable, pretend it's nothing. + else Null + +// Get the result value of a column as a float64. +// If the result does not have float64 affinity, it is converted. +// The leftmost column is numbered 0. +pub fun statement/float64(stmt: statement, col: int): float64 + capi/column-double(stmt.cref, col.int32) + +// Get the result value of a column as an integer. +// If the result does not have integer affinity, it is converted. +// The leftmust column is numbered 0. +pub fun statement/int(stmt: statement, col: int): int + capi/column-integer(stmt.cref, col.int32) + +// Get the result value of a column as a string. +// If the actual result is NULL, the result is the empty string. +// Otherwise, if the result does not have text affinity, it is converted. +// The leftmost column is numbeered 0. +pub fun statement/text(stmt: statement, col: int): string + capi/column-text(stmt.cref, col.int32) diff --git a/test/test.kk b/test/test.kk index d5e2c85..efbeb3b 100644 --- a/test/test.kk +++ b/test/test.kk @@ -5,14 +5,26 @@ import sqlite/sqlite3 // TODO(zephyr): For now, we're just doing a very basic test to estimate whether // I'm doing Koka FFI correctly. An actual test suite would be excellent. -fun stmt(db: sqlite3, text: string) - println(text) - match db.prepare(text.slice) +tail fun do-while(f: () -> maybe): a + match f() + Nothing -> do-while(f) + Just(r) -> r + +fun stmt(db: sqlite3, sql: string) + println(sql) + match db.prepare(sql.slice) Left(r-prep) -> println(" prepare result: " ++ r-prep.show) Right((stmt, rest)) -> println(" prepare remainder: " ++ rest.show) - val r-step = stmt.step - println(" step result: " ++ r-step.show) + val r-step = do-while + val r = stmt.step + match r + Row -> + for(stmt.column-count) fn(i) + println(" col " ++ i.show ++ ": " ++ stmt.text(i)) + Nothing + r -> Just(r) + println(" final step result: " ++ r-step.show) val r-fin = stmt.finalize println(" finalize result: " ++ r-fin.show) @@ -23,5 +35,6 @@ pub fun main() 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 ('value inserted from koka');") + stmt(db, "SELECT * FROM koka") val r-close = db.close println("close result: " ++ r-close.show)