sqlite.v

7.0 KB · raw

1//! SQLite bindings over the vendored amalgamation in `vendor/sqlite`.2//!3//! V's own `db.sqlite` module links whatever SQLite the host provides. Koh4//! links its own copy instead, so macOS and Linux run the same SQLite and the5//! build depends on nothing outside the repo. Only the calls Koh needs are6//! declared here — no ORM, no pooling, no reflection.78module storage910#flag -I@VMODROOT/vendor/sqlite11#flag @VMODROOT/vendor/sqlite/sqlite3.c12#include "sqlite3.h"1314// Opaque handles. V wants every C type named in a declaration to exist.15pub struct C.sqlite3 {}1617pub struct C.sqlite3_stmt {}1819fn C.sqlite3_libversion() &char2021fn C.sqlite3_open_v2(filename &char, pp_db &&C.sqlite3, flags i32, vfs &char) i322223fn C.sqlite3_close_v2(db &C.sqlite3) i322425fn C.sqlite3_errmsg(db &C.sqlite3) &char2627fn C.sqlite3_busy_timeout(db &C.sqlite3, ms i32) i322829fn C.sqlite3_exec(db &C.sqlite3, sql &char, callback voidptr, arg voidptr, errmsg &&char) i323031fn C.sqlite3_free(ptr voidptr)3233fn C.sqlite3_prepare_v2(db &C.sqlite3, sql &char, n i32, stmt &&C.sqlite3_stmt, tail &&char) i323435fn C.sqlite3_step(stmt &C.sqlite3_stmt) i323637fn C.sqlite3_reset(stmt &C.sqlite3_stmt) i323839fn C.sqlite3_finalize(stmt &C.sqlite3_stmt) i324041fn C.sqlite3_bind_blob(stmt &C.sqlite3_stmt, idx i32, val voidptr, n i32, done voidptr) i324243fn C.sqlite3_bind_text(stmt &C.sqlite3_stmt, idx i32, val &char, n i32, done voidptr) i324445fn C.sqlite3_bind_int64(stmt &C.sqlite3_stmt, idx i32, val i64) i324647fn C.sqlite3_bind_null(stmt &C.sqlite3_stmt, idx i32) i324849fn C.sqlite3_column_blob(stmt &C.sqlite3_stmt, idx i32) voidptr5051fn C.sqlite3_column_bytes(stmt &C.sqlite3_stmt, idx i32) i325253fn C.sqlite3_column_text(stmt &C.sqlite3_stmt, idx i32) &u85455fn C.sqlite3_column_int64(stmt &C.sqlite3_stmt, idx i32) i645657fn C.sqlite3_column_type(stmt &C.sqlite3_stmt, idx i32) i325859fn C.sqlite3_changes(db &C.sqlite3) i326061const sqlite_ok = 062const sqlite_busy = 563const sqlite_row = 10064const sqlite_done = 10165const sqlite_open_readwrite = 0x0000000266const sqlite_open_create = 0x0000000467const sqlite_null = 56869// SQLITE_TRANSIENT: tells SQLite to copy the buffer instead of borrowing it.70const transient = unsafe { voidptr(isize(-1)) }7172fn cstring(s string) &char {73	buf := unsafe { malloc_noscan(s.len + 1) }74	unsafe {75		vmemcpy(buf, s.str, s.len)76		buf[s.len] = 077	}78	return &char(buf)79}8081pub fn libversion() string {82	return unsafe { cstring_to_vstring(C.sqlite3_libversion()) }83}8485@[heap]86pub struct Db {87mut:88	conn &C.sqlite3 = unsafe { nil }89}9091// connect opens a database file, creating it when missing. `busy_timeout_ms`92// is how long a second writer waits before giving up.93pub fn connect(path string, busy_timeout_ms int) !Db {94	path_c := cstring(path)95	defer {96		unsafe { free(path_c) }97	}98	mut conn := &C.sqlite3(unsafe { nil })99	flags := sqlite_open_readwrite | sqlite_open_create100	code := C.sqlite3_open_v2(path_c, &conn, flags, unsafe { nil })101	if code != sqlite_ok {102		msg := unsafe { cstring_to_vstring(C.sqlite3_errmsg(conn)) }103		C.sqlite3_close_v2(conn)104		return error('cannot open ${path}: ${msg}')105	}106	C.sqlite3_busy_timeout(conn, busy_timeout_ms)107	return Db{108		conn: conn109	}110}111112pub fn (db &Db) close() {113	if db.conn != unsafe { nil } {114		C.sqlite3_close_v2(db.conn)115	}116}117118pub fn (db &Db) last_error() string {119	return unsafe { cstring_to_vstring(C.sqlite3_errmsg(db.conn)) }120}121122pub fn (db &Db) changes() int {123	return C.sqlite3_changes(db.conn)124}125126// exec runs one or more statements that return no rows.127pub fn (db &Db) exec(query string) ! {128	query_c := cstring(query)129	defer {130		unsafe { free(query_c) }131	}132	mut errmsg := &char(unsafe { nil })133	code := C.sqlite3_exec(db.conn, query_c, unsafe { nil }, unsafe { nil }, &errmsg)134	if code == sqlite_busy {135		if errmsg != unsafe { nil } {136			C.sqlite3_free(errmsg)137		}138		return error(busy)139	}140	if code != sqlite_ok {141		msg := if errmsg != unsafe { nil } {142			unsafe { cstring_to_vstring(errmsg) }143		} else {144			db.last_error()145		}146		if errmsg != unsafe { nil } {147			C.sqlite3_free(errmsg)148		}149		return error(msg)150	}151}152153// Stmt is a prepared statement. Call finalize() when done; `defer stmt.finalize()`154// is the normal shape.155@[heap]156pub struct Stmt {157mut:158	stmt &C.sqlite3_stmt = unsafe { nil }159	db   &Db             = unsafe { nil }160}161162pub fn (db &Db) prepare(query string) !Stmt {163	query_c := cstring(query)164	defer {165		unsafe { free(query_c) }166	}167	mut stmt := &C.sqlite3_stmt(unsafe { nil })168	code := C.sqlite3_prepare_v2(db.conn, query_c, query.len, &stmt, unsafe { nil })169	if code != sqlite_ok {170		return error('${db.last_error()} in: ${query}')171	}172	return Stmt{173		stmt: stmt174		db:   db175	}176}177178pub fn (stmt &Stmt) bind_text(idx int, val string) ! {179	if C.sqlite3_bind_text(stmt.stmt, idx, val.str, val.len, transient) != sqlite_ok {180		return error(stmt.db.last_error())181	}182}183184pub fn (stmt &Stmt) bind_blob(idx int, val []u8) ! {185	mut ptr := unsafe { nil }186	if val.len > 0 {187		ptr = val.data188	}189	if C.sqlite3_bind_blob(stmt.stmt, idx, ptr, val.len, transient) != sqlite_ok {190		return error(stmt.db.last_error())191	}192}193194pub fn (stmt &Stmt) bind_int(idx int, val i64) ! {195	if C.sqlite3_bind_int64(stmt.stmt, idx, val) != sqlite_ok {196		return error(stmt.db.last_error())197	}198}199200pub fn (stmt &Stmt) bind_null(idx int) ! {201	if C.sqlite3_bind_null(stmt.stmt, idx) != sqlite_ok {202		return error(stmt.db.last_error())203	}204}205206// step advances the statement. Returns true while rows are available. Rows of207// a result are read with text/blob/int between steps; a statement that has208// returned false must be reset() before it can be bound and run again.209pub fn (stmt &Stmt) step() !bool {210	code := C.sqlite3_step(stmt.stmt)211	match code {212		sqlite_row { return true }213		sqlite_done { return false }214		sqlite_busy { return error(busy) }215		else { return error(stmt.db.last_error()) }216	}217}218219pub fn (stmt &Stmt) reset() {220	C.sqlite3_reset(stmt.stmt)221}222223pub fn (stmt &Stmt) finalize() {224	if stmt.stmt != unsafe { nil } {225		C.sqlite3_finalize(stmt.stmt)226	}227}228229fn (stmt &Stmt) is_null(idx int) bool {230	return C.sqlite3_column_type(stmt.stmt, idx) == sqlite_null231}232233// text copies the column out of SQLite's row buffer. It must copy: SQLite owns234// that memory only until the statement is stepped again or finalized, and a V235// string built by aliasing it would point at freed bytes.236pub fn (stmt &Stmt) text(idx int) string {237	ptr := C.sqlite3_column_text(stmt.stmt, idx)238	if ptr == unsafe { nil } {239		return ''240	}241	n := C.sqlite3_column_bytes(stmt.stmt, idx)242	if n == 0 {243		return ''244	}245	mut out := []u8{len: n}246	unsafe { vmemcpy(out.data, ptr, n) }247	return out.bytestr()248}249250pub fn (stmt &Stmt) blob(idx int) []u8 {251	ptr := C.sqlite3_column_blob(stmt.stmt, idx)252	n := C.sqlite3_column_bytes(stmt.stmt, idx)253	if ptr == unsafe { nil } || n == 0 {254		return []255	}256	mut out := []u8{len: n}257	unsafe { vmemcpy(out.data, ptr, n) }258	return out259}260261pub fn (stmt &Stmt) int(idx int) i64 {262	return C.sqlite3_column_int64(stmt.stmt, idx)263}264265pub fn (stmt &Stmt) text_or_none(idx int) ?string {266	if stmt.is_null(idx) {267		return none268	}269	return stmt.text(idx)270}271272pub fn (stmt &Stmt) int_or_none(idx int) ?i64 {273	if stmt.is_null(idx) {274		return none275	}276	return stmt.int(idx)277}