sqlite.v

6.4 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_row = 10063const sqlite_done = 10164const sqlite_open_readwrite = 0x0000000265const sqlite_open_create = 0x0000000466const sqlite_null = 56768// SQLITE_TRANSIENT: tells SQLite to copy the buffer instead of borrowing it.69const transient = unsafe { voidptr(isize(-1)) }7071fn cstring(s string) &char {72	buf := unsafe { malloc_noscan(s.len + 1) }73	unsafe {74		vmemcpy(buf, s.str, s.len)75		buf[s.len] = 076	}77	return &char(buf)78}7980pub fn libversion() string {81	return unsafe { cstring_to_vstring(C.sqlite3_libversion()) }82}8384@[heap]85pub struct Db {86mut:87	conn &C.sqlite3 = unsafe { nil }88}8990// open a database file, creating it when missing. `busy_timeout_ms` is how91// long a second writer waits before giving up.92pub fn open(path string, busy_timeout_ms int) !Db {93	path_c := cstring(path)94	defer {95		unsafe { free(path_c) }96	}97	mut conn := &C.sqlite3(unsafe { nil })98	flags := sqlite_open_readwrite | sqlite_open_create99	code := C.sqlite3_open_v2(path_c, &conn, flags, unsafe { nil })100	if code != sqlite_ok {101		msg := unsafe { cstring_to_vstring(C.sqlite3_errmsg(conn)) }102		C.sqlite3_close_v2(conn)103		return error('cannot open ${path}: ${msg}')104	}105	C.sqlite3_busy_timeout(conn, busy_timeout_ms)106	return Db{107		conn: conn108	}109}110111pub fn (db &Db) close() {112	if db.conn != unsafe { nil } {113		C.sqlite3_close_v2(db.conn)114	}115}116117pub fn (db &Db) last_error() string {118	return unsafe { cstring_to_vstring(C.sqlite3_errmsg(db.conn)) }119}120121pub fn (db &Db) changes() int {122	return C.sqlite3_changes(db.conn)123}124125// exec runs one or more statements that return no rows.126pub fn (db &Db) exec(query string) ! {127	query_c := cstring(query)128	defer {129		unsafe { free(query_c) }130	}131	mut errmsg := &char(unsafe { nil })132	code := C.sqlite3_exec(db.conn, query_c, unsafe { nil }, unsafe { nil }, &errmsg)133	if code != sqlite_ok {134		msg := if errmsg != unsafe { nil } {135			unsafe { cstring_to_vstring(errmsg) }136		} else {137			db.last_error()138		}139		if errmsg != unsafe { nil } {140			C.sqlite3_free(errmsg)141		}142		return error(msg)143	}144}145146// Stmt is a prepared statement. Call finalize() when done; `defer stmt.finalize()`147// is the normal shape.148@[heap]149pub struct Stmt {150mut:151	stmt &C.sqlite3_stmt = unsafe { nil }152	db   &Db             = unsafe { nil }153}154155pub fn (db &Db) prepare(query string) !Stmt {156	query_c := cstring(query)157	defer {158		unsafe { free(query_c) }159	}160	mut stmt := &C.sqlite3_stmt(unsafe { nil })161	code := C.sqlite3_prepare_v2(db.conn, query_c, query.len, &stmt, unsafe { nil })162	if code != sqlite_ok {163		return error('${db.last_error()} in: ${query}')164	}165	return Stmt{166		stmt: stmt167		db:   db168	}169}170171pub fn (stmt &Stmt) bind_text(idx int, val string) ! {172	if C.sqlite3_bind_text(stmt.stmt, idx, val.str, val.len, transient) != sqlite_ok {173		return error(stmt.db.last_error())174	}175}176177pub fn (stmt &Stmt) bind_blob(idx int, val []u8) ! {178	mut ptr := unsafe { nil }179	if val.len > 0 {180		ptr = val.data181	}182	if C.sqlite3_bind_blob(stmt.stmt, idx, ptr, val.len, transient) != sqlite_ok {183		return error(stmt.db.last_error())184	}185}186187pub fn (stmt &Stmt) bind_int(idx int, val i64) ! {188	if C.sqlite3_bind_int64(stmt.stmt, idx, val) != sqlite_ok {189		return error(stmt.db.last_error())190	}191}192193pub fn (stmt &Stmt) bind_null(idx int) ! {194	if C.sqlite3_bind_null(stmt.stmt, idx) != sqlite_ok {195		return error(stmt.db.last_error())196	}197}198199// step advances the statement. Returns true while rows are available.200pub fn (stmt &Stmt) step() !bool {201	code := C.sqlite3_step(stmt.stmt)202	match code {203		sqlite_row { return true }204		sqlite_done { return false }205		else { return error(stmt.db.last_error()) }206	}207}208209pub fn (stmt &Stmt) reset() {210	C.sqlite3_reset(stmt.stmt)211}212213pub fn (stmt &Stmt) finalize() {214	if stmt.stmt != unsafe { nil } {215		C.sqlite3_finalize(stmt.stmt)216	}217}218219fn (stmt &Stmt) is_null(idx int) bool {220	return C.sqlite3_column_type(stmt.stmt, idx) == sqlite_null221}222223pub fn (stmt &Stmt) text(idx int) string {224	ptr := C.sqlite3_column_text(stmt.stmt, idx)225	if ptr == unsafe { nil } {226		return ''227	}228	n := C.sqlite3_column_bytes(stmt.stmt, idx)229	return unsafe { ptr.vstring_with_len(n) }230}231232pub fn (stmt &Stmt) blob(idx int) []u8 {233	ptr := C.sqlite3_column_blob(stmt.stmt, idx)234	n := C.sqlite3_column_bytes(stmt.stmt, idx)235	if ptr == unsafe { nil } || n == 0 {236		return []237	}238	mut out := []u8{len: n}239	unsafe { vmemcpy(out.data, ptr, n) }240	return out241}242243pub fn (stmt &Stmt) int(idx int) i64 {244	return C.sqlite3_column_int64(stmt.stmt, idx)245}246247pub fn (stmt &Stmt) text_or_none(idx int) ?string {248	if stmt.is_null(idx) {249		return none250	}251	return stmt.text(idx)252}253254pub fn (stmt &Stmt) int_or_none(idx int) ?i64 {255	if stmt.is_null(idx) {256		return none257	}258	return stmt.int(idx)259}