summaryrefslogtreecommitdiff
path: root/backend/api/src/db.zig
diff options
context:
space:
mode:
Diffstat (limited to 'backend/api/src/db.zig')
-rw-r--r--backend/api/src/db.zig149
1 files changed, 149 insertions, 0 deletions
diff --git a/backend/api/src/db.zig b/backend/api/src/db.zig
new file mode 100644
index 0000000..78258f0
--- /dev/null
+++ b/backend/api/src/db.zig
@@ -0,0 +1,149 @@
+const z = @import( "std" );
+const u = @import( "util.zig" );
+const sql = @import( "sqlite" );
+
+const alloc = u.alloc;
+
+const ArenaAllocator = z.heap.ArenaAllocator;
+const Mutex = z.Thread.Mutex;
+const Db = sql.Db;
+pub threadlocal var dbi: Interface = undefined;
+pub threadlocal var dbi_init: bool = false;
+
+var db_mutex = Mutex{};
+
+pub fn create( dbfile: [:0]const u8 ) !void {
+ dbiCheck() catch try init( dbfile );
+
+ const create_users_q =
+ \\CREATE TABLE IF NOT EXISTS users (
+ \\ uuid TEXT PRIMARY KEY,
+ \\ email TEXT,
+ \\ tokens TEXT,
+ \\ login_token TEXT,
+ \\ token_resetdate INTEGER,
+ \\ api_tokens TEXT,
+ \\ created_at INTEGER,
+ \\ subscription_data TEXT
+ \\)
+ ;
+
+ try dbi.put( create_users_q, .{} );
+ // ensure all fields actually exist in the database
+ dbi.put( "ALTER TABLE users ADD COLUMN email TEXT", .{} ) catch {};
+ dbi.put( "ALTER TABLE users ADD COLUMN tokens TEXT", .{} ) catch {};
+ dbi.put( "ALTER TABLE users ADD COLUMN login_token TEXT", .{} ) catch {};
+ dbi.put( "ALTER TABLE users ADD COLUMN token_resetdate INTEGER", .{} ) catch {};
+ dbi.put( "ALTER TABLE users ADD COLUMN api_tokens TEXT", .{} ) catch {};
+ dbi.put( "ALTER TABLE users ADD COLUMN created_at INTEGER", .{} ) catch {};
+ dbi.put( "ALTER TABLE users ADD COLUMN subscription_data TEXT", .{} ) catch {};
+
+ const create_userdata_q =
+ \\CREATE TABLE IF NOT EXISTS user_data (
+ \\ uuid TEXT PRIMARY KEY,
+ \\ nickname TEXT,
+ \\ prompt_data TEXT,
+ \\ site_prefs TEXT,
+ \\ chat_files TEXT
+ \\)
+ ;
+
+ try dbi.put( create_userdata_q, .{} );
+ // ensure all fields actually exist in the database
+ dbi.put( "ALTER TABLE user_data ADD COLUMN nickname INTEGER", .{} ) catch {};
+ dbi.put( "ALTER TABLE user_data ADD COLUMN prompt_data INTEGER", .{} ) catch {};
+ dbi.put( "ALTER TABLE user_data ADD COLUMN site_prefs INTEGER", .{} ) catch {};
+ dbi.put( "ALTER TABLE user_data ADD COLUMN chat_files INTEGER", .{} ) catch {};
+}
+
+pub fn init( dbfile: [:0]const u8 ) !void {
+ if( dbi_init ) return error.DbiAlreadyInitialized;
+
+ const db = try sql.Db.init( .{
+ .mode = Db.Mode{ .File = dbfile },
+ .open_flags = .{
+ .write = true,
+ .create = true
+ },
+ .threading_mode = .MultiThread
+ } );
+
+ dbi = .{ .db = db };
+ dbi_init = true;
+}
+
+fn dbiCheck() !void {
+ if( !dbi_init ) return error.DbiNotInitialized;
+}
+
+///methods of t get ignored
+pub fn Row( comptime t: type ) type {
+ return struct {
+ v: t,
+ alloc: ArenaAllocator,
+ pub fn deinit( self: @This() ) void {
+ self.alloc.deinit();
+ }
+ };
+}
+
+pub const Interface = struct {
+ const Self = Interface;
+ db: Db,
+ pub fn deinit( self: *Interface ) void {
+ self.db.deinit();
+ }
+
+ pub fn put( self: *Interface, comptime query: []const u8, value: anytype ) !void {
+ try dbiCheck();
+ db_mutex.lock();
+ defer db_mutex.unlock();
+
+ var statement = try self.db.prepare( query );
+ defer statement.deinit();
+
+ try statement.exec( .{}, value );
+ }
+
+ ///returns the first row of the query
+ pub fn one( self: *Interface, comptime t: type, comptime query: []const u8, value: anytype ) !t {
+ try dbiCheck();
+ db_mutex.lock();
+ defer db_mutex.unlock();
+
+ var statement = try self.db.prepare( query );
+ defer statement.deinit();
+
+ const ret = try statement.one( t, .{}, value );
+ return ret.?;
+ }
+
+ ///returns all rows of the query
+ ///must be freed by caller
+ pub fn all( self: *Interface, comptime t: type, comptime query: []const u8, value: anytype ) ![]t {
+ try dbiCheck();
+ db_mutex.lock();
+ defer db_mutex.unlock();
+
+ var statement = try self.db.prepare( query );
+ defer statement.deinit();
+
+ return try statement.all( t, alloc, .{}, value );
+ }
+
+ pub fn oneAlloc( self: *Interface, comptime t: type, comptime query: []const u8, value: anytype ) !Row(t) {
+ try dbiCheck();
+ db_mutex.lock();
+ defer db_mutex.unlock();
+
+ var arena = ArenaAllocator.init( alloc );
+ const a = arena.allocator();
+
+ var statement = try self.db.prepareDynamic( query );
+ defer statement.deinit();
+
+ const ret = try statement.oneAlloc( t, a, .{}, value );
+ if( ret == null ) return error.QueryError;
+ return .{ .v = ret.?, .alloc = arena };
+ }
+};