152 lines
5.8 KiB
Plaintext
152 lines
5.8 KiB
Plaintext
// 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;
|
|
}
|