Initial commit
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
// Minimal SQLite binding, loaded at runtime (sqlite3.dll on Windows, libsqlite3.so.0 on Linux).
|
||||
// Loading dynamically means the server binary has no link-time dependency on SQLite, so it can be
|
||||
// cross-compiled and then just needs the distro package (libsqlite3-0) on the droplet.
|
||||
|
||||
SQLITE_OK :: 0;
|
||||
SQLITE_ROW :: 100;
|
||||
SQLITE_DONE :: 101;
|
||||
SQLITE_OPEN_READWRITE :: 0x00000002;
|
||||
SQLITE_OPEN_CREATE :: 0x00000004;
|
||||
SQLITE_OPEN_NOMUTEX :: 0x00008000;
|
||||
SQLITE_TRANSIENT :: cast(*void) -1;
|
||||
|
||||
sqlite3 :: void;
|
||||
sqlite3_stmt :: void;
|
||||
|
||||
Sqlite_Api :: struct {
|
||||
sqlite3_open_v2: (filename: *u8, db: **sqlite3, flags: s32, vfs: *u8) -> s32 #c_call;
|
||||
sqlite3_close_v2: (db: *sqlite3) -> s32 #c_call;
|
||||
sqlite3_exec: (db: *sqlite3, sql: *u8, callback: *void, arg: *void, errmsg: **u8) -> s32 #c_call;
|
||||
sqlite3_prepare_v2: (db: *sqlite3, sql: *u8, bytes: s32, stmt: **sqlite3_stmt, tail: **u8) -> s32 #c_call;
|
||||
sqlite3_step: (stmt: *sqlite3_stmt) -> s32 #c_call;
|
||||
sqlite3_reset: (stmt: *sqlite3_stmt) -> s32 #c_call;
|
||||
sqlite3_finalize: (stmt: *sqlite3_stmt) -> s32 #c_call;
|
||||
sqlite3_bind_int64: (stmt: *sqlite3_stmt, index: s32, value: s64) -> s32 #c_call;
|
||||
sqlite3_bind_text: (stmt: *sqlite3_stmt, index: s32, text: *u8, bytes: s32, destructor: *void) -> s32 #c_call;
|
||||
sqlite3_bind_blob: (stmt: *sqlite3_stmt, index: s32, data: *void, bytes: s32, destructor: *void) -> s32 #c_call;
|
||||
sqlite3_column_int64: (stmt: *sqlite3_stmt, column: s32) -> s64 #c_call;
|
||||
sqlite3_column_text: (stmt: *sqlite3_stmt, column: s32) -> *u8 #c_call;
|
||||
sqlite3_column_blob: (stmt: *sqlite3_stmt, column: s32) -> *void #c_call;
|
||||
sqlite3_column_bytes: (stmt: *sqlite3_stmt, column: s32) -> s32 #c_call;
|
||||
sqlite3_errmsg: (db: *sqlite3) -> *u8 #c_call;
|
||||
sqlite3_changes: (db: *sqlite3) -> s32 #c_call;
|
||||
sqlite3_last_insert_rowid: (db: *sqlite3) -> s64 #c_call;
|
||||
sqlite3_busy_timeout: (db: *sqlite3, ms: s32) -> s32 #c_call;
|
||||
sqlite3_libversion: () -> *u8 #c_call;
|
||||
}
|
||||
|
||||
sql: Sqlite_Api;
|
||||
|
||||
load_sqlite :: () -> bool {
|
||||
#if OS == .WINDOWS {
|
||||
tf_kernel32 :: #system_library "kernel32";
|
||||
LoadLibraryA :: (name: *u8) -> *void #foreign tf_kernel32;
|
||||
GetProcAddress :: (module: *void, name: *u8) -> *void #foreign tf_kernel32;
|
||||
lib := LoadLibraryA("sqlite3.dll");
|
||||
lookup :: GetProcAddress;
|
||||
} else {
|
||||
lib := dlopen("libsqlite3.so.0", RTLD_NOW);
|
||||
if !lib lib = dlopen("libsqlite3.so", RTLD_NOW);
|
||||
lookup :: dlsym;
|
||||
}
|
||||
if !lib {
|
||||
log_error("Could not load SQLite. On Ubuntu/Debian: sudo apt install libsqlite3-0. On Windows: put sqlite3.dll next to the server.");
|
||||
return false;
|
||||
}
|
||||
|
||||
info := type_info(Sqlite_Api);
|
||||
for info.members {
|
||||
address := lookup(lib, temp_c_string(it.name));
|
||||
if !address {
|
||||
log_error("SQLite is missing %; it may be too old.", it.name);
|
||||
return false;
|
||||
}
|
||||
(cast(**void) (cast(*u8) *sql + it.offset_in_bytes)).* = address;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Small conveniences.
|
||||
//
|
||||
|
||||
Db :: struct {
|
||||
handle: *sqlite3;
|
||||
}
|
||||
|
||||
db_open :: (path: string) -> Db, bool {
|
||||
db: Db;
|
||||
rc := sql.sqlite3_open_v2(temp_c_string(path), *db.handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, null);
|
||||
if rc != SQLITE_OK {
|
||||
log_error("Could not open database %: %", path, db_error(db));
|
||||
return db, false;
|
||||
}
|
||||
sql.sqlite3_busy_timeout(db.handle, 5000);
|
||||
db_exec(db, "PRAGMA journal_mode = WAL;");
|
||||
db_exec(db, "PRAGMA synchronous = NORMAL;");
|
||||
db_exec(db, "PRAGMA foreign_keys = ON;");
|
||||
return db, true;
|
||||
}
|
||||
|
||||
db_error :: (db: Db) -> string {
|
||||
return to_string(sql.sqlite3_errmsg(db.handle));
|
||||
}
|
||||
|
||||
db_exec :: (db: Db, statement: string) -> bool {
|
||||
err: *u8;
|
||||
rc := sql.sqlite3_exec(db.handle, temp_c_string(statement), null, null, *err);
|
||||
if rc != SQLITE_OK {
|
||||
log_error("SQL error in \"%\": %", statement, to_string(err));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Stmt :: struct {
|
||||
handle: *sqlite3_stmt;
|
||||
db: Db;
|
||||
}
|
||||
|
||||
prepare :: (db: Db, statement: string) -> Stmt {
|
||||
s: Stmt;
|
||||
s.db = db;
|
||||
rc := sql.sqlite3_prepare_v2(db.handle, statement.data, cast(s32) statement.count, *s.handle, null);
|
||||
if rc != SQLITE_OK log_error("SQL prepare failed for \"%\": %", statement, db_error(db));
|
||||
return s;
|
||||
}
|
||||
|
||||
bind :: (s: Stmt, index: s32, value: s64) { sql.sqlite3_bind_int64(s.handle, index, value); }
|
||||
bind :: (s: Stmt, index: s32, value: string) { sql.sqlite3_bind_text(s.handle, index, value.data, cast(s32) value.count, SQLITE_TRANSIENT); }
|
||||
bind_blob :: (s: Stmt, index: s32, value: string) { sql.sqlite3_bind_blob(s.handle, index, value.data, cast(s32) value.count, SQLITE_TRANSIENT); }
|
||||
|
||||
step :: (s: Stmt) -> bool {
|
||||
rc := sql.sqlite3_step(s.handle);
|
||||
if rc == SQLITE_ROW return true;
|
||||
if rc != SQLITE_DONE log_error("SQL step failed: %", db_error(s.db));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Runs a statement that returns no rows; returns success.
|
||||
run :: (s: Stmt) -> bool {
|
||||
rc := sql.sqlite3_step(s.handle);
|
||||
if rc != SQLITE_DONE && rc != SQLITE_ROW {
|
||||
log_error("SQL failed: %", db_error(s.db));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
reset :: (s: Stmt) { sql.sqlite3_reset(s.handle); }
|
||||
finalize :: (s: Stmt) { sql.sqlite3_finalize(s.handle); }
|
||||
|
||||
column_int :: (s: Stmt, column: s32) -> s64 { return sql.sqlite3_column_int64(s.handle, column); }
|
||||
|
||||
// Column text/blob, copied into temporary storage.
|
||||
column_text :: (s: Stmt, column: s32) -> string {
|
||||
data := sql.sqlite3_column_blob(s.handle, column);
|
||||
count := sql.sqlite3_column_bytes(s.handle, column);
|
||||
result := alloc_string(count,, temp);
|
||||
if count memcpy(result.data, data, count);
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user