From 30da10d3afbd8b5e1e057eb680d68e6741ca5cfd Mon Sep 17 00:00:00 2001 From: Spencer Brower Date: Sat, 20 Jun 2026 19:01:10 -0400 Subject: [PATCH] refactor(sqlite): Used distinct types for Db and Stmt pointers. --- db.odin | 77 ++++++++++++++++++++-------------------- db_integration_test.odin | 4 +-- db_test.odin | 2 +- sqlite/sqlite.odin | 30 +++++++++------- 4 files changed, 58 insertions(+), 55 deletions(-) diff --git a/db.odin b/db.odin index 6cb99a5..145d344 100644 --- a/db.odin +++ b/db.odin @@ -31,8 +31,7 @@ SyncError :: enum { } Db :: struct { - // Pointer to the sqlite db - db: ^rawptr, + conn: ^sqlite.Db, cfg: Config, changed: bool, arena: mem.Dynamic_Arena, @@ -65,7 +64,7 @@ db_open :: proc(cfg_path: string) -> (database: Db, ok: bool) { data_path := data_path(database.cfg.config_path, context.temp_allocator) if os.exists(data_path) { if ok = db_restore_from_encrypted(&database, data_path); !ok { - sqlite.close(database.db) + sqlite.close(database.conn) return database, false } } else { @@ -79,21 +78,21 @@ db_open :: proc(cfg_path: string) -> (database: Db, ok: bool) { // Creates a database an allocator and fresh, empty table, with zero encryption. // In production, you most likely want to use `db_open`. db_init :: proc() -> (database: Db, ok: bool) { - db: ^rawptr - rc := sqlite.open(":memory:", &db) + conn: ^sqlite.Db + rc := sqlite.open(":memory:", &conn) if rc != sqlite.OK { - fmt.printf("Error opening in-memory database: %s\n", sqlite.db_errmsg(db)) + fmt.printf("Error opening in-memory database: %s\n", sqlite.db_errmsg(conn)) return } create_sql: cstring = "CREATE TABLE IF NOT EXISTS envr_env_files (path TEXT PRIMARY KEY NOT NULL, remotes TEXT, sha256 TEXT NOT NULL, contents TEXT NOT NULL)" - rc = sqlite.db_exec(db, create_sql, nil, nil, nil) + rc = sqlite.db_exec(conn, create_sql, nil, nil, nil) if rc != sqlite.OK { - fmt.printf("Error creating table: %s\n", sqlite.db_errmsg(db)) - sqlite.close(db) + fmt.printf("Error creating table: %s\n", sqlite.db_errmsg(conn)) + sqlite.close(conn) return } - database.db = db + database.conn = conn mem.dynamic_arena_init(&database.arena) @@ -128,7 +127,7 @@ db_restore_from_encrypted :: proc(db: ^Db, data_path: string) -> bool { copy(buf[:len(plaintext)], plaintext) rc := sqlite.deserialize( - db.db, + db.conn, "main", buf, n, @@ -137,7 +136,7 @@ db_restore_from_encrypted :: proc(db: ^Db, data_path: string) -> bool { ) if rc != sqlite.OK { sqlite.free(buf) - fmt.printf("Error deserializing database: %s\n", sqlite.db_errmsg(db.db)) + fmt.printf("Error deserializing database: %s\n", sqlite.db_errmsg(db.conn)) return false } @@ -148,7 +147,7 @@ db_close :: proc(d: ^Db) { allocator := db_allocator(d) defer { - sqlite.close(d.db) + sqlite.close(d.conn) delete_config(&d.cfg, allocator) @@ -156,14 +155,14 @@ db_close :: proc(d: ^Db) { } if d.changed { - rc := sqlite.db_exec(d.db, "VACUUM", nil, nil, nil) + rc := sqlite.db_exec(d.conn, "VACUUM", nil, nil, nil) if rc != sqlite.OK { - fmt.printf("Error vacuuming database: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error vacuuming database: %s\n", sqlite.db_errmsg(d.conn)) return } sz: i64 - data := sqlite.serialize(d.db, "main", &sz, 0) + data := sqlite.serialize(d.conn, "main", &sz, 0) if data == nil { fmt.println("Error: failed to serialize database") return @@ -195,16 +194,16 @@ db_close :: proc(d: ^Db) { // Results will be freed when `db_close` is called. db_list :: proc(d: ^Db) -> ([]EnvFile, bool) { - stmt: ^rawptr + stmt: ^sqlite.Stmt rc := sqlite.prepare_v2( - d.db, + d.conn, "SELECT path, remotes, sha256, contents FROM envr_env_files", -1, &stmt, nil, ) if rc != sqlite.OK { - fmt.printf("Error preparing query: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error preparing query: %s\n", sqlite.db_errmsg(d.conn)) return []EnvFile{}, false } defer sqlite.finalize(stmt) @@ -218,7 +217,7 @@ db_list :: proc(d: ^Db) -> ([]EnvFile, bool) { break } if rc != sqlite.ROW { - fmt.printf("Error stepping query: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error stepping query: %s\n", sqlite.db_errmsg(d.conn)) #no_bounds_check return results[:], false } @@ -255,10 +254,10 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool { sql: cstring = "INSERT OR REPLACE INTO " + "envr_env_files (path, remotes, sha256, contents) VALUES (?, ?, ?, ?)" - stmt: ^rawptr - rc := sqlite.prepare_v2(d.db, sql, -1, &stmt, nil) + stmt: ^sqlite.Stmt + rc := sqlite.prepare_v2(d.conn, sql, -1, &stmt, nil) if rc != sqlite.OK { - fmt.printf("Error preparing insert: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error preparing insert: %s\n", sqlite.db_errmsg(d.conn)) return false } defer sqlite.finalize(stmt) @@ -268,7 +267,7 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool { defer delete(cpath) rc = sqlite.bind_text(stmt, 1, cpath, -1, nil) if rc != sqlite.OK { - fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.conn)) return false } @@ -276,7 +275,7 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool { defer delete(cremotes) rc = sqlite.bind_text(stmt, 2, cremotes, -1, nil) if rc != sqlite.OK { - fmt.printf("Error binding remotes: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error binding remotes: %s\n", sqlite.db_errmsg(d.conn)) return false } @@ -284,7 +283,7 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool { defer delete(csha) rc = sqlite.bind_text(stmt, 3, csha, -1, nil) if rc != sqlite.OK { - fmt.printf("Error binding sha256: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error binding sha256: %s\n", sqlite.db_errmsg(d.conn)) return false } @@ -292,13 +291,13 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool { defer delete(ccontents) rc = sqlite.bind_text(stmt, 4, ccontents, -1, nil) if rc != sqlite.OK { - fmt.printf("Error binding contents: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error binding contents: %s\n", sqlite.db_errmsg(d.conn)) return false } rc = sqlite.step(stmt) if rc != sqlite.DONE { - fmt.printf("Error inserting: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error inserting: %s\n", sqlite.db_errmsg(d.conn)) return false } @@ -309,10 +308,10 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool { // Result will be freed when `db_close` is called. db_fetch :: proc(d: ^Db, path: string) -> (EnvFile, bool) { sql: cstring = "SELECT path, remotes, sha256, contents FROM envr_env_files WHERE path = ?" - stmt: ^rawptr - rc := sqlite.prepare_v2(d.db, sql, -1, &stmt, nil) + stmt: ^sqlite.Stmt + rc := sqlite.prepare_v2(d.conn, sql, -1, &stmt, nil) if rc != sqlite.OK { - fmt.printf("Error preparing fetch: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error preparing fetch: %s\n", sqlite.db_errmsg(d.conn)) return EnvFile{}, false } defer sqlite.finalize(stmt) @@ -323,7 +322,7 @@ db_fetch :: proc(d: ^Db, path: string) -> (EnvFile, bool) { defer delete(cpath, allocator) rc = sqlite.bind_text(stmt, 1, cpath, -1, nil) if rc != sqlite.OK { - fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.conn)) return EnvFile{}, false } rc = sqlite.step(stmt) @@ -332,7 +331,7 @@ db_fetch :: proc(d: ^Db, path: string) -> (EnvFile, bool) { return EnvFile{}, false } if rc != sqlite.ROW { - fmt.printf("Error fetching: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error fetching: %s\n", sqlite.db_errmsg(d.conn)) return EnvFile{}, false } @@ -356,10 +355,10 @@ db_fetch :: proc(d: ^Db, path: string) -> (EnvFile, bool) { db_delete :: proc(d: ^Db, path: string) -> bool { sql: cstring = "DELETE FROM envr_env_files WHERE path = ?" - stmt: ^rawptr - rc := sqlite.prepare_v2(d.db, sql, -1, &stmt, nil) + stmt: ^sqlite.Stmt + rc := sqlite.prepare_v2(d.conn, sql, -1, &stmt, nil) if rc != sqlite.OK { - fmt.printf("Error preparing delete: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error preparing delete: %s\n", sqlite.db_errmsg(d.conn)) return false } defer sqlite.finalize(stmt) @@ -368,16 +367,16 @@ db_delete :: proc(d: ^Db, path: string) -> bool { defer delete(cpath) rc = sqlite.bind_text(stmt, 1, cpath, -1, nil) if rc != sqlite.OK { - fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.conn)) return false } rc = sqlite.step(stmt) if rc != sqlite.DONE { - fmt.printf("Error deleting: %s\n", sqlite.db_errmsg(d.db)) + fmt.printf("Error deleting: %s\n", sqlite.db_errmsg(d.conn)) return false } - if sqlite.changes(d.db) == 0 { + if sqlite.changes(d.conn) == 0 { fmt.printf("No file found with path: %s\n", path) return false } diff --git a/db_integration_test.odin b/db_integration_test.odin index 740cd35..3a8b44b 100644 --- a/db_integration_test.odin +++ b/db_integration_test.odin @@ -165,7 +165,7 @@ test_decrypt_then_deserialize_sqlite :: proc(t: ^testing.T) { } defer delete(plaintext) - mem_db: ^rawptr + mem_db: ^sqlite.Db rc := sqlite.open(":memory:", &mem_db) testing.expectf(t, rc == sqlite.OK, "failed to open in-memory db") if rc != sqlite.OK { @@ -194,7 +194,7 @@ test_decrypt_then_deserialize_sqlite :: proc(t: ^testing.T) { } sql: cstring = "SELECT path FROM envr_env_files" - stmt: ^rawptr + stmt: ^sqlite.Stmt rc = sqlite.prepare_v2(mem_db, sql, -1, &stmt, nil) testing.expect(t, rc == sqlite.OK, "prepare failed") if rc != sqlite.OK { diff --git a/db_test.odin b/db_test.odin index 53a536a..6f38a79 100644 --- a/db_test.odin +++ b/db_test.odin @@ -196,7 +196,7 @@ test_db_serialize :: proc(t: ^testing.T) { db_insert(&d, f) sz: i64 - data := sqlite.serialize(d.db, "main", &sz, 0) + data := sqlite.serialize(d.conn, "main", &sz, 0) testing.expect(t, data != nil, "serialize should return non-nil") if data == nil do return defer sqlite.free(data) diff --git a/sqlite/sqlite.odin b/sqlite/sqlite.odin index c5bff83..cc4e9b7 100644 --- a/sqlite/sqlite.odin +++ b/sqlite/sqlite.odin @@ -4,6 +4,10 @@ import "core:c" foreign import lib "system:sqlite3" +Db :: distinct rawptr +Stmt :: distinct rawptr + +// TODO: Use an enum? OK :: 0 ROW :: 100 DONE :: 101 @@ -13,31 +17,31 @@ DESERIALIZE_RESIZEABLE :: 2 foreign lib { @(link_name = "sqlite3_open") - open :: proc(filename: cstring, ppDb: ^^rawptr) -> c.int --- + open :: proc(filename: cstring, ppDb: ^^Db) -> c.int --- @(link_name = "sqlite3_close") - close :: proc(db: ^rawptr) -> c.int --- + close :: proc(db: ^Db) -> c.int --- @(link_name = "sqlite3_errmsg") - db_errmsg :: proc(db: ^rawptr) -> cstring --- + db_errmsg :: proc(db: ^Db) -> cstring --- @(link_name = "sqlite3_exec") - db_exec :: proc(db: ^rawptr, sql: cstring, callback: rawptr, callback_arg: rawptr, errmsg: ^cstring) -> c.int --- + db_exec :: proc(db: ^Db, sql: cstring, callback: rawptr, callback_arg: rawptr, errmsg: ^cstring) -> c.int --- @(link_name = "sqlite3_prepare_v2") - prepare_v2 :: proc(db: ^rawptr, sql: cstring, nByte: c.int, ppStmt: ^^rawptr, pzTail: ^cstring) -> c.int --- + prepare_v2 :: proc(db: ^Db, sql: cstring, nByte: c.int, ppStmt: ^^Stmt, pzTail: ^cstring) -> c.int --- @(link_name = "sqlite3_step") - step :: proc(stmt: ^rawptr) -> c.int --- + step :: proc(stmt: ^Stmt) -> c.int --- @(link_name = "sqlite3_finalize") - finalize :: proc(stmt: ^rawptr) -> c.int --- + finalize :: proc(stmt: ^Stmt) -> c.int --- @(link_name = "sqlite3_column_text") - column_text :: proc(stmt: ^rawptr, iCol: c.int) -> cstring --- + column_text :: proc(stmt: ^Stmt, iCol: c.int) -> cstring --- @(link_name = "sqlite3_column_bytes") - column_bytes :: proc(stmt: ^rawptr, iCol: c.int) -> c.int --- + column_bytes :: proc(stmt: ^Stmt, iCol: c.int) -> c.int --- @(link_name = "sqlite3_bind_text") - bind_text :: proc(stmt: ^rawptr, idx: c.int, val: cstring, n: c.int, destructor: rawptr) -> c.int --- + bind_text :: proc(stmt: ^Stmt, idx: c.int, val: cstring, n: c.int, destructor: rawptr) -> c.int --- @(link_name = "sqlite3_changes") - changes :: proc(db: ^rawptr) -> c.int --- + changes :: proc(db: ^Db) -> c.int --- @(link_name = "sqlite3_serialize") - serialize :: proc(db: ^rawptr, zSchema: cstring, piSize: ^i64, mFlags: u32) -> [^]u8 --- + serialize :: proc(db: ^Db, zSchema: cstring, piSize: ^i64, mFlags: u32) -> [^]u8 --- @(link_name = "sqlite3_deserialize") - deserialize :: proc(db: ^rawptr, zSchema: cstring, pData: [^]u8, szDb: i64, szBuf: i64, mFlags: u32) -> c.int --- + deserialize :: proc(db: ^Db, zSchema: cstring, pData: [^]u8, szDb: i64, szBuf: i64, mFlags: u32) -> c.int --- @(link_name = "sqlite3_malloc64") malloc64 :: proc(n: i64) -> [^]u8 --- @(link_name = "sqlite3_free")