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 }; } };