diff options
| author | Alexander Rolley <arolley35@gmail.com> | 2026-05-28 14:37:25 +0200 |
|---|---|---|
| committer | Alexander Rolley <arolley35@gmail.com> | 2026-05-28 14:37:25 +0200 |
| commit | e18128f73c2b78cb9505754facff3835b2f70ba4 (patch) | |
| tree | 6835069da703c410bf77f88b9e66598e3e7e7b5e | |
| parent | e9404aa9b8b4ddad7d06c08f038324b21c05ab2f (diff) | |
Sketch new DB API.
| -rw-r--r-- | src/db/sqlite.zig | 387 |
1 files changed, 306 insertions, 81 deletions
diff --git a/src/db/sqlite.zig b/src/db/sqlite.zig index f2fae69..4c59b62 100644 --- a/src/db/sqlite.zig +++ b/src/db/sqlite.zig @@ -1,83 +1,254 @@ 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 { - handle: *sqlite.sqlite3, + schema: Schema, - pub const Error = set: { - var set: type = error{}; + pub fn foo(self: Database) void { + _ = self; + std.debug.print("asdf\n", .{}); + } +}; - 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)); - } +pub const Schema = struct { + tables: type, - break :set set; - }; + // tables = union(enum) { + // contacts: union(enum) { + // first_name: []const u8 = "Hans", + // last_name: []const u8 = "Mustermann", + // maybe_null: ?usize, + // }, + // other: union(enum) { + // ... + // }, + // } - pub const Kind = union(enum) { - temp_in_memory, - temp_on_disk, - path: []const u8, - }; + pub fn from(comptime T: type) Schema { + const fields = switch (@typeInfo(T)) { + .@"struct" => |s| s.fields, + else => @compileError("Schema must be of type `struct`"), + }; - pub const Query = struct { - handle: *sqlite.sqlite3_stmt, + // TODO: check well-formedness of schema - // 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())); + const Int = @Int(.unsigned, @bitSizeOf(@TypeOf(fields.len)) - @clz(fields.len)); - self.* = undefined; + 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 } - }; - // 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 Tag = @Enum(Int, .exhaustive, &table_names, &table_values); + const Tables = @Union(.auto, Tag, &table_names, &table_types, &@splat(.{})); - const result: Result = .from(sqlite.sqlite3_open(name.ptr, &handle)); - return if (result.isOk()) .{ .handle = handle.? } else result.toError(handle); - } + // 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 @; + // }; - /// Closes the database and releases all of its resources. - /// - /// # Panics - /// - /// Panics if there exist unfinalised prepared statements, BLOB handlers, - /// and/or unfinished backup objects. - /// - /// See https://sqlite.org/c3ref/close.html - pub fn deinit(self: *Database) void { - const result: Result = .from(sqlite.sqlite3_close(self.handle)); +// @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 - if (!result.isOk()) @panic("There are still unfinalised and/or unfinished objects"); - self.* = undefined; + return .{ .tables = T }; } +}; - // 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 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. /// @@ -230,19 +401,19 @@ const Result = enum(i32) { /// 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)), - } - } + // 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 { @@ -251,18 +422,72 @@ const Result = enum(i32) { }; test { - var db: Database = try .init(.temp_in_memory); - // defer db.deinit(); + // 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 }; - 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("{}\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(); - std.debug.print("{any}\n", .{ query }); + // 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 }); } |