const std = @import("std"); const sqlite = @import("sqlite"); const assert = std.debug.assert; const math = std.math; // pub fn Database(comptime S: Schema) type { // _ = S; // return struct { // handle: *sqlite.sqlite3, // // pub const Check = enum { // ok, // unfinalised, // }; // // pub const Error = set: { // var set: type = error{}; // // for (std.meta.fieldNames(Result.Primary)) |name| { // set = set || @TypeOf(@field(anyerror, name)); // } // for (std.meta.fieldNames(Result.Extended)) |name| { // set = set || @TypeOf(@field(anyerror, name)); // } // // break :set set; // }; // // pub const Kind = union(enum) { // temp_in_memory, // temp_on_disk, // path: []const u8, // }; // // pub const Query = struct { // handle: *sqlite.sqlite3_stmt, // // // Source: https://sqlite.org/c3ref/finalize.html // pub fn deinit(self: *Query) void { // const result: Result = .from(sqlite.sqlite3_finalize(self.handle)); // // // TODO: maybe don't panic? // if (!result.isOk()) @panic("Last error was " ++ @errorName(result.toError())); // // self.* = undefined; // } // }; // // // Source: https://sqlite.org/c3ref/open.html // pub fn init(kind: Kind) Error!Database { // const name = switch (kind) { // .temp_in_memory => ":memory:", // .temp_on_disk => "", // .path => |path| path, // }; // var handle: ?*sqlite.sqlite3 = null; // // const result: Result = .from(sqlite.sqlite3_open(name.ptr, &handle)); // // return if (result.isOk()) .{ .handle = handle.? } else result.toError(handle); // } // // /// Closes the database and releases all of its resources, returning `.ok`. // /// // /// If there exist unfinalised prepared statements, BLOB handlers, and/or // /// unfinished backup objects, `.unfinalised` is returned; // /// // /// See https://sqlite.org/c3ref/close.html // pub fn deinit(self: *Database) Check { // const result: Result = .from(sqlite.sqlite3_close(self.handle)); // // if (!result.isOk()) .unfinalised; // // self.* = undefined; // // return .ok; // } // // // Source: https://sqlite.org/c3ref/prepare.html // pub fn query(self: *Database, statement: []const u8) Error!Query { // var handle: ?*sqlite.sqlite3_stmt = null; // var unused: [*c]const u8 = null; // // const result: Result = .from(sqlite.sqlite3_prepare_v2(self.handle, statement.ptr, @intCast(statement.len), &handle, &unused)); // // return if (result.isOk()) .{ .handle = handle.? } else result.toError(self.handle); // } // }; // } pub const Database = struct { schema: Schema, pub fn foo(self: Database) void { _ = self; std.debug.print("asdf\n", .{}); } }; pub const Schema = struct { tables: type, // tables = union(enum) { // contacts: union(enum) { // first_name: []const u8 = "Hans", // last_name: []const u8 = "Mustermann", // maybe_null: ?usize, // }, // other: union(enum) { // ... // }, // } pub fn from(comptime T: type) Schema { const fields = switch (@typeInfo(T)) { .@"struct" => |s| s.fields, else => @compileError("Schema must be of type `struct`"), }; // TODO: check well-formedness of schema const Int = @Int(.unsigned, @bitSizeOf(@TypeOf(fields.len)) - @clz(fields.len)); var table_names: [fields.len][]const u8 = undefined; var table_values: [fields.len]Int = undefined; var table_types: [fields.len]type = undefined; inline for (&table_names, &table_value, &table_types, fields, 0..) |table_name, table_value, table_type, field, index| { TODO } const Tag = @Enum(Int, .exhaustive, &table_names, &table_values); const Tables = @Union(.auto, Tag, &table_names, &table_types, &@splat(.{})); // const tables = tbl: { // const Int = @Int(.unsigned, math.log2_int_ceil(usize, schema.len)); // // var names: [schema.len][]const u8 = undefined; // var types: [schema.len]type = undefined; // var values: [schema.len]Int = undefined; // // inline for (&names, &types, &values, schema, 0..) |*name, *ty, *value, table, index| { // name.* = table.name; // ty.* = table.type; // value.* = index; // } // // const Tag = @Enum(Int, .exhaustive, &names, &values); // const // // break :tbl @; // }; // @Int(comptime signedness: std.builtin.Signedness, comptime bits: u16) type // @Union( // comptime layout: std.builtin.Type.ContainerLayout, // /// Either the integer tag type, or the integer backing type, depending on `layout`. // comptime ArgType: ?type, // comptime field_names: []const []const u8, // comptime field_types: *const [field_names.len]type, // comptime field_attrs: *const [field_names.len]std.builtin.Type.UnionField.Attributes, // ) type // @Enum( // comptime TagInt: type, // comptime mode: std.builtin.Type.Enum.Mode, // comptime field_names: []const []const u8, // comptime field_values: *const [field_names.len]TagInt, // ) type return .{ .tables = T }; } }; // pub const Schema = union(enum) { // foo, // // pub fn from(comptime T: type) Schema { // // TODO: build enum from struct fields // // TODO: table column name is @EnumLiteral() // switch (@typeInfo(T)) { // else => |x| @compileLog(x), // } // // return .foo; // } // }; // pub const Schema = struct { // tables: []const Table, // // const Table = struct { // name: @EnumLiteral(), // }; // // pub fn from(comptime S: type) Schema { // // TODO: build enum from struct fields // // TODO: table column name is @EnumLiteral() // switch (@typeInfo(S)) { // else => |x| @compileLog(x), // } // return .{ .tables = &.{} }; // } // }; // const MyTable = struct { // const Column1 = struct { // pub fn eql(element: []const u8) Condition { // return .{ // .table = MyTable.name, // } // } // } // } // const Schema = struct { // tables: []Table, // // pub fn from(comptime schema: anytype) {} // } // // pub const Table = struct { // name: @EnumLiteral(), // columns: []Column, // // pub const Column = struct { // name: @EnumLiteral(), // type: enum { // integer, // text, // }, // flags: packed struct { // .is_primary // } // }; // }; /// SQLite result codes. /// /// Primary result codes define a broad category of results, whereas extended /// result codes provide more detail. The primary result code is always part of /// an extended result code. /// /// See https://sqlite.org/rescode.html const Result = enum(i32) { _, // zig fmt: off /// Primary result codes. const Primary = enum(u8) { ok = 0, @"error" = 1, internal = 2, perm = 3, abort = 4, busy = 5, locked = 6, nomem = 7, readonly = 8, interrupt = 9, ioerr = 10, corrupt = 11, notfound = 12, full = 13, cantopen = 14, protocol = 15, empty = 16, schema = 17, toobig = 18, constraint = 19, mismatch = 20, misuse = 21, nolfs = 22, auth = 23, format = 24, range = 25, notadb = 26, notice = 27, warning = 28, row = 100, done = 101, /// Returns the primary result code as type `T`. fn as(self: Primary, comptime T: type) T { return @as(T, @intFromEnum(self)); } }; /// Extended result codes. const Extended = enum(i32) { error_missing_collseq = (Primary.@"error".as(i32) | ( 1 << 8)), error_retry = (Primary.@"error".as(i32) | ( 2 << 8)), error_snapshot = (Primary.@"error".as(i32) | ( 3 << 8)), error_reservesize = (Primary.@"error".as(i32) | ( 4 << 8)), error_key = (Primary.@"error".as(i32) | ( 5 << 8)), error_unable = (Primary.@"error".as(i32) | ( 6 << 8)), ioerr_read = (Primary.ioerr.as(i32) | ( 1 << 8)), ioerr_short_read = (Primary.ioerr.as(i32) | ( 2 << 8)), ioerr_write = (Primary.ioerr.as(i32) | ( 3 << 8)), ioerr_fsync = (Primary.ioerr.as(i32) | ( 4 << 8)), ioerr_dir_fsync = (Primary.ioerr.as(i32) | ( 5 << 8)), ioerr_truncate = (Primary.ioerr.as(i32) | ( 6 << 8)), ioerr_fstat = (Primary.ioerr.as(i32) | ( 7 << 8)), ioerr_unlock = (Primary.ioerr.as(i32) | ( 8 << 8)), ioerr_rdlock = (Primary.ioerr.as(i32) | ( 9 << 8)), ioerr_delete = (Primary.ioerr.as(i32) | (10 << 8)), ioerr_blocked = (Primary.ioerr.as(i32) | (11 << 8)), ioerr_nomem = (Primary.ioerr.as(i32) | (12 << 8)), ioerr_access = (Primary.ioerr.as(i32) | (13 << 8)), ioerr_checkreservedlock = (Primary.ioerr.as(i32) | (14 << 8)), ioerr_lock = (Primary.ioerr.as(i32) | (15 << 8)), ioerr_close = (Primary.ioerr.as(i32) | (16 << 8)), ioerr_dir_close = (Primary.ioerr.as(i32) | (17 << 8)), ioerr_shmopen = (Primary.ioerr.as(i32) | (18 << 8)), ioerr_shmsize = (Primary.ioerr.as(i32) | (19 << 8)), ioerr_shmlock = (Primary.ioerr.as(i32) | (20 << 8)), ioerr_shmmap = (Primary.ioerr.as(i32) | (21 << 8)), ioerr_seek = (Primary.ioerr.as(i32) | (22 << 8)), ioerr_delete_noent = (Primary.ioerr.as(i32) | (23 << 8)), ioerr_mmap = (Primary.ioerr.as(i32) | (24 << 8)), ioerr_gettemppath = (Primary.ioerr.as(i32) | (25 << 8)), ioerr_convpath = (Primary.ioerr.as(i32) | (26 << 8)), ioerr_vnode = (Primary.ioerr.as(i32) | (27 << 8)), ioerr_auth = (Primary.ioerr.as(i32) | (28 << 8)), ioerr_begin_atomic = (Primary.ioerr.as(i32) | (29 << 8)), ioerr_commit_atomic = (Primary.ioerr.as(i32) | (30 << 8)), ioerr_rollback_atomic = (Primary.ioerr.as(i32) | (31 << 8)), ioerr_data = (Primary.ioerr.as(i32) | (32 << 8)), ioerr_corruptfs = (Primary.ioerr.as(i32) | (33 << 8)), ioerr_in_page = (Primary.ioerr.as(i32) | (34 << 8)), ioerr_badkey = (Primary.ioerr.as(i32) | (35 << 8)), ioerr_codec = (Primary.ioerr.as(i32) | (36 << 8)), locked_sharedcache = (Primary.locked.as(i32) | ( 1 << 8)), locked_vtab = (Primary.locked.as(i32) | ( 2 << 8)), busy_recovery = (Primary.busy.as(i32) | ( 1 << 8)), busy_snapshot = (Primary.busy.as(i32) | ( 2 << 8)), busy_timeout = (Primary.busy.as(i32) | ( 3 << 8)), cantopen_notempdir = (Primary.cantopen.as(i32) | ( 1 << 8)), cantopen_isdir = (Primary.cantopen.as(i32) | ( 2 << 8)), cantopen_fullpath = (Primary.cantopen.as(i32) | ( 3 << 8)), cantopen_convpath = (Primary.cantopen.as(i32) | ( 4 << 8)), cantopen_symlink = (Primary.cantopen.as(i32) | ( 6 << 8)), corrupt_vtab = (Primary.corrupt.as(i32) | ( 1 << 8)), corrupt_sequence = (Primary.corrupt.as(i32) | ( 2 << 8)), corrupt_index = (Primary.corrupt.as(i32) | ( 3 << 8)), readonly_recovery = (Primary.readonly.as(i32) | ( 1 << 8)), readonly_cantlock = (Primary.readonly.as(i32) | ( 2 << 8)), readonly_rollback = (Primary.readonly.as(i32) | ( 3 << 8)), readonly_dbmoved = (Primary.readonly.as(i32) | ( 4 << 8)), readonly_cantinit = (Primary.readonly.as(i32) | ( 5 << 8)), readonly_directory = (Primary.readonly.as(i32) | ( 6 << 8)), abort_rollback = (Primary.abort.as(i32) | ( 2 << 8)), constraint_check = (Primary.constraint.as(i32) | ( 1 << 8)), constraint_commithook = (Primary.constraint.as(i32) | ( 2 << 8)), constraint_foreignkey = (Primary.constraint.as(i32) | ( 3 << 8)), constraint_function = (Primary.constraint.as(i32) | ( 4 << 8)), constraint_notnull = (Primary.constraint.as(i32) | ( 5 << 8)), constraint_primarykey = (Primary.constraint.as(i32) | ( 6 << 8)), constraint_trigger = (Primary.constraint.as(i32) | ( 7 << 8)), constraint_unique = (Primary.constraint.as(i32) | ( 8 << 8)), constraint_vtab = (Primary.constraint.as(i32) | ( 9 << 8)), constraint_rowid = (Primary.constraint.as(i32) | (10 << 8)), constraint_pinned = (Primary.constraint.as(i32) | (11 << 8)), constraint_datatype = (Primary.constraint.as(i32) | (12 << 8)), notice_recover_wal = (Primary.notice.as(i32) | ( 1 << 8)), notice_recover_rollback = (Primary.notice.as(i32) | ( 2 << 8)), notice_rbu = (Primary.notice.as(i32) | ( 3 << 8)), warning_autoindex = (Primary.warning.as(i32) | ( 1 << 8)), auth_user = (Primary.auth.as(i32) | ( 1 << 8)), ok_load_permanently = (Primary.ok.as(i32) | ( 1 << 8)), }; // zig fmt: on /// Returns the result code corresponding to the given integer value. fn from(integer: anytype) Result { assert(std.enums.fromInt(Primary, integer) != null or std.enums.fromInt(Extended, integer) != null); return @enumFromInt(integer); } /// Returns `true` if the primary result code is `.ok`. fn isOk(self: Result) bool { return if (self.asPrimary() == .ok) true else false; } /// Returns the error name corresponding to the result code. The `handle` is /// needed to fetch the extended result code, if it exists, only if `self` /// is not already an extended result code. // fn toError(self: Result, handle: ?*sqlite.sqlite3) Database.Error { // if (std.enums.fromInt(Extended, @intFromEnum(self))) |extended| switch (extended) { // inline else => |code| return @field(Database.Error, @tagName(code)), // }; // // const value: Result = .from(sqlite.sqlite3_extended_errcode(handle)); // // if (std.enums.fromInt(Extended, @intFromEnum(value))) |extended| switch (extended) { // inline else => |code| return @field(Database.Error, @tagName(code)), // } else switch (self.asPrimary()) { // inline else => |code| return @field(Database.Error, @tagName(code)), // } // } /// Returns the primary result code. fn asPrimary(self: Result) Primary { return @enumFromInt(0x000000FF & @intFromEnum(self)); } }; test { // const schema = &.{ // .{ // .name = .contacts, // .columns = &.{ // .{ .name = .contact_id, .type = .integer, .is_primary = true }, // .{ .name = .first_name, .type = .text, .is_not_null = true }, // .{ .name = .last_name, .type = .text, .is_not_null = true }, // .{ .name = .email, .text = .text , .is_not_null = true, .is_unique = true }, // .{ .name = .phone, .text = .text , .is_not_null = true, .is_unique = true }, // } // }, // }; // // const schema = .{ // .contacts = .{ // .contact_id = .{ .integer, .is_primary }, // .first_name = .{ .text, .not_null }, // .last_name = .{ .text, .not_null }, // .email = .{ .text, .not_null, .unique }, // .phone = .{ .text, .not_null, .unique }, // }, // }; // NOTE: we don't use `union(enum)` here as if a column has a default value, // an integer tag type is needed explicitely, which, from a usability // standpoint, is not great; we can calulate this value in `Schema.from`. const schema: Schema = .from(struct { contacts: struct { // contact_id: Primary(usize), first_name: []const u8 = "Hans", last_name: []const u8 = "Mustermann", maybe_null: ?usize, }, }); const db: Database = .{ .schema = schema }; std.debug.print("{}\n", .{ db }); db.foo(); // const Contacts = struct { // first_name: []const u8, // last_name : []const u8, // // pub fn foo() void { // std.debug.print("asdf\n", .{}); // } // }; // const Other = struct {}; // const schema: Schema = .from(&.{ Contacts, Other }); // const db: Database(schema) = try .init(.temp_in_memory); // _ = db; // defer db.deinit(); // const query = db.query( // \\ CREATE TABLE contacts ( // \\ contact_id INTEGER PRIMARY KEY, // \\ first_name TEXT NOT NULL, // \\ last_name TEXT NOT NULL, // \\ email TEXT NOT NULL UNIQUE, // \\ phone TEXT NOT NULL UNIQUE // \\ ); // ); // // std.debug.print("{any}\n", .{ query }); }