package sqlite3 import "core:c" import "core:fmt" import "core:mem" import "core:slice" import "core:strings" import sqlite3 "bindings" DB :: sqlite3.Connection Rows :: struct { _stmt: sqlite3.Statement, } open :: proc(filename: string) -> (DB, bool) { db: DB c_filename := strings.clone_to_cstring(filename) defer delete(c_filename) if res := sqlite3.Result(sqlite3.open(c_filename, &db)); res != .Ok { fmt.eprintf("sqlite3.open: %s\n", res) return nil, false } return db, true } close :: proc(db: DB) -> bool { if res := sqlite3.Result(sqlite3.close(db)); res != .Ok { fmt.eprintf("sqlite3.close: %s\n", res) return false } return true } query :: proc(db: DB, sql: string, args: ..any) -> (^Rows, bool) { rows := new(Rows) c_sql := strings.clone_to_cstring(sql) defer delete(c_sql) if res := sqlite3.Result(sqlite3.prepare(db, c_sql, -1, &rows._stmt, nil)); res != .Ok { fmt.eprintf("sqlite3.query: %s\n", res) return nil, false } for arg, i in args { switch v in arg { case int: sqlite3.bind_int(rows._stmt, c.int(i + 1), c.int(v)) case f32: sqlite3.bind_double(rows._stmt, c.int(i + 1), c.double(v)) case f64: sqlite3.bind_double(rows._stmt, c.int(i + 1), c.double(v)) case string: sqlite3.bind_text( rows._stmt, c.int(i + 1), strings.unsafe_string_to_cstring(v), c.int(len(v)), nil, ) case []u8: sqlite3.bind_blob(rows._stmt, c.int(i + 1), raw_data(v), c.int(len(v)), nil) case: fmt.printf("sqlite3.query: unsupported datatype %T\n", arg) } } return rows, true } exec :: proc(db: DB, sql: string, args: ..any) -> bool { rows, ok := query(db, sql, ..args) if !ok { fmt.printf("sqlite3.exec: query failed\n") return false } defer rows_close(rows) return rows_next(rows) } rows_next :: proc(rows: ^Rows) -> bool { if res := sqlite3.Result(sqlite3.step(rows._stmt)); res != .Ok && res != .Row { if res != .Done { fmt.eprintf("sqlite3.next: %s\n", res) } return false } return true } rows_scan :: proc(rows: ^Rows, columns: ..any) -> bool { for &col, i in columns { datatype := sqlite3.column_type(rows._stmt, c.int(i)) switch datatype { case .Integer: col_int, ok := col.(^int) if !ok { fmt.eprintf("sqlite3.scan: invalid data type: expected int, got %T\n", col) return false } col_int^ = int(sqlite3.column_int(rows._stmt, c.int(i))) case .Float: col_float, ok := col.(^f32) if !ok { fmt.eprintf("sqlite3.scan: invalid data type: expected f32, got %T\n", col) return false } col_float^ = f32(sqlite3.column_double(rows._stmt, c.int(i))) case .Text: col_str, ok := col.(^string) if !ok { fmt.eprintf("sqlite3.scan: invalid data type: expected string, got %T\n", col) return false } raw_str := sqlite3.column_text(rows._stmt, c.int(i)) err: mem.Allocator_Error col_str^, err = strings.clone_from_cstring(raw_str) if err != nil { fmt.eprintf("sqlite3.scan: err %#v\n", err) return false } case .Blob: col_bytes, ok := col.(^[]u8) if !ok { fmt.eprintf("sqlite3.scan: invalid data type: expected byte slice, got %T\n", col) return false } n_bytes := sqlite3.column_bytes(rows._stmt, c.int(i)) raw_bytes := sqlite3.column_blob(rows._stmt, c.int(i)) col_bytes^ = slice.bytes_from_ptr(raw_bytes, int(n_bytes)) case .Null: fmt.printf("sqlite3.scan: column datatype is Null, skipping\n") return true } } return true } rows_close :: proc(rows: ^Rows) -> bool { if res := sqlite3.Result(sqlite3.finalize(rows._stmt)); res != .Ok { fmt.eprintf("sqlite3.close: %s\n", res) return false } free(rows) return true } // TODO: a "select" call that can fill a struct with columns, should support both single row and multiple rows through procedure groups /* if true { conn: sqlite3.Connection fmt.printf("sqlite3 open: %s\n", sqlite3.open(cstring("test.db"), &conn)) stmt: sqlite3.Statement fmt.printf( "sqlite3 prepare: %s\n", sqlite3.prepare( conn, cstring("create table t1(x integer primary key asc,y);"), -1, &stmt, nil, ), ) fmt.printf("sqlite3 step: %s\n", sqlite3.step(stmt)) fmt.printf("sqlite3 finalize: %s\n", sqlite3.finalize(stmt)) fmt.printf( "sqlite3 exec: %s\n", sqlite3.exec( conn, cstring( "insert into t1 (y) values (1); insert into t1 (y) values (2); select * from t1;", ), exec_proc, conn, nil, ), ) errmsg: cstring if sqlite3.exec(conn, cstring("insert into t2 (y) values (1)"), nil, nil, &errmsg) != .Ok { fmt.printf("sqlite3 exec err: %#v\n", errmsg) sqlite3.free(rawptr(errmsg)) } fmt.printf("sqlite3 close: %s\n", sqlite3.close(conn)) os.exit(1) } */