From 320ff32b028add3dfc827229dc2c16727b0e74ef Mon Sep 17 00:00:00 2001 From: Alexander Rolley Date: Sun, 14 Jun 2026 18:38:30 +0200 Subject: Update. --- src/db/sqlite_new.zig | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 src/db/sqlite_new.zig (limited to 'src/db/sqlite_new.zig') diff --git a/src/db/sqlite_new.zig b/src/db/sqlite_new.zig new file mode 100644 index 0000000..46706b8 --- /dev/null +++ b/src/db/sqlite_new.zig @@ -0,0 +1,263 @@ +const std = @import("std"); +const sqlite = @import("sqlite"); + +pub fn Database(comptime T: type) type { + return struct { + const Schema = DatabaseSchema(T); + const Self = @This(); + + pub const Kind = union(enum) { + temp_in_memory, + temp_on_disk, + path: []const u8, + }; + + pub fn init(kind: Kind) error{}!Self { + _ = kind; + std.debug.print("{}\n", .{Self.Schema}); + return .{}; + } + + pub fn deinit(self: *Self) void { + self.* = undefined; + } + + // TODO: make functions out of `table` and `columns`: Table(Schema), []const Column(Schema, table) for readability? + pub fn select(self: *Self, comptime table: std.meta.Tag(Schema), comptime columns: []const std.meta.Tag(@FieldType(Schema, @tagName(table)))) Select(Schema, table, columns) { + _ = self; + std.debug.print("{any}\n", .{ columns }); + return .{ + // .handle = self.handle, + }; + } + }; +} + +fn Select(comptime Schema: type, comptime table: std.meta.Tag(Schema), comptime columns: []const std.meta.Tag(@FieldType(Schema, @tagName(table)))) type { + return struct { + table: std.meta.Tag(Schema) = table, + + // TODO: db handle, etc. + + // FIXME: pass in columns instead + pub fn where(self: @This(), condition: Condition(Schema, table)) void { + _ = self; + _ = condition; + std.debug.print("SELECT {any} FROM {}\n", .{ columns, table }); + } + }; + // const columns = switch(@typeInfo(@TypeOf(T))) { + // + // }; + // + // std.debug.assert(columns.len > 0); + // + // const Int = @Int(.unsigned, std.math.log2_int_ceil(usize, columns.len)); + // + // var names: [columns.len][]const u8 = undefined; + // var values: [columns.len]Int = undefined; + // + // inline for (&names, &values, columns, 0..) |*name, *value, column, index| { + // name.* = @tagName(column); + // value.* = index; + // } + // + // return @Enum(Int, .exhaustive, &names, &values); +} + +fn DatabaseSchema(comptime T: type) type { + const tables = switch (@typeInfo(T)) { + .@"struct" => |s| s.fields, + else => @compileError("Schema must be of type `struct`"), + }; + + // NOTE: Unlike `std.math.log2_int_ceil(usize, tables.len)`, this doesn't panic + // if `tables.len` is zero. + const IntSchema = @Int(.unsigned, @bitSizeOf(@TypeOf(tables.len)) - @clz(tables.len)); + + var table_names: [tables.len][]const u8 = undefined; + var table_types: [tables.len]type = undefined; + var table_values: [tables.len]IntSchema = undefined; + + inline for (&table_names, &table_types, &table_values, tables, 0..) |*table_name, *table_type, *table_value, table, table_index| { + const columns = switch (@typeInfo(table.type)) { + .@"struct" => |s| s.fields, + else => @compileError("Table `" ++ table.name ++ "` must be of type `struct`"), + }; + + // NOTE: Needing at least one column is defined by the `create-table-stmt` + // syntax; see https://sqlite.org/syntax/create-table-stmt.html. + if (columns.len == 0) @compileError("Table `" ++ table.name ++ "` must have at least one column"); + + const IntColumns = @Int(.unsigned, std.math.log2_int_ceil(usize, columns.len)); + + var column_names: [columns.len][]const u8 = undefined; + var column_types: [columns.len]type = undefined; + var column_values: [columns.len]IntColumns = undefined; + + inline for (&column_names, &column_types, &column_values, columns, 0..) |*column_name, *column_type, *column_value, column, column_index| { + column_name.* = column.name; + column_type.* = column.type; + column_value.* = column_index; + + // TODO: check well-formedness of tables/translate types into SQL types, + // i.e. `[]const u8` to VarChar(slice.len) etc. + } + + const Tag = @Enum(IntColumns, .exhaustive, &column_names, &column_values); + const Columns = @Union(.auto, Tag, &column_names, &column_types, &@splat(.{})); + + table_name.* = table.name; + table_type.* = Columns; + table_value.* = table_index; + } + + const Tag = @Enum(IntSchema, .exhaustive, &table_names, &table_values); + const Schema = @Union(.auto, Tag, &table_names, &table_types, &@splat(.{})); + + return Schema; +} + +// pub const Integer = struct { +// pub const unconstrained: Integer = .{}; +// +// pub fn constrain(constraint: Constraint) Integer { +// _ = constraint; +// return .{}; +// } +// }; +// pub const Constraint = enum { +// not_null, +// primary_key, +// }; + +// fn Constraints(comptime T: type) type { +// return struct { +// not_null: bool = false, +// primary_key: bool = false, +// default_value: ?T = null, // FIXME: more complex than that (see literal-value in create-table-stmt) +// }; +// } + +fn Integer(comptime T: type) type { + // TODO: check T is an integer (zig type) + return struct { + const Type = T; + const string = "INTEGER"; + }; +} + +fn Unique(comptime T: type) type { + return struct { + const Type = T.Type; + // TODO: `contains(expected, str)` function + const string = if (std.mem.count(u8, T.string, "UNIQUE") > 0) + @compileError(std.fmt.comptimePrint("Type `{s}` is already unique", .{ @typeName(T) })) + else + std.fmt.comptimePrint("UNIQUE {s}", .{ T.string }); + }; +} + +fn Default(comptime T: type, comptime value: T.Type) type { + return struct { + const Type = T.type; + const string = if(std.mem.count(u8, T.string, "DEFAULT") > 0) + @compileError(std.fmt.comptimePrint("Type `{s}` already has a default value", .{ @typeName(T) })) + else + std.fmt.comptimePrint("{s} DEFAULT {}", .{ T.string, value }); + }; +} + +// ------------------------------------------------- + +// Constraints +// +// primary key: feature of a table (if multiple columns) or of a column (if signle) => use decl +// -> also applies to foreign keys +// + +const Contacts = struct { + id: usize, + first_name: []const u8, +}; + +fn Test(comptime Schema: type, comptime table: std.meta.Tag(Schema), comptime columns: []const std.meta.Tag(@FieldType(Schema, @tagName(table)))) type { + const ColumnInt = @Int(.unsigned, std.math.log2_int_ceil(usize, columns.len)); + var column_names: [columns.len][]const u8 = undefined; + var column_values: [columns.len]ColumnInt = undefined; + var column_types: [columns.len]type = undefined; + inline for (&column_names, &column_values, &column_types, columns, 0..) |*column_name, *column_value, *column_type, column, index| { + column_name.* = @tagName(column); + column_value.* = index; + column_type.* = @FieldType(@FieldType(Schema, @tagName(table)), @tagName(column)); + } + const ColumnEnum = @Enum(ColumnInt, .exhaustive, &column_names, &column_values); + const ColumnUnion = @Union(.auto, ColumnEnum, &column_names, &column_types, &@splat(.{})); + + return struct { + const Self = @This(); + const Node = union(enum) { + eql: type, + }; + + fn Eql(comptime column: std.meta.Tag(ColumnUnion), value: @FieldType(ColumnUnion, @tagName(column))) type { + return struct { + const fmt: []const u8 = std.fmt.comptimePrint("{s} = ", .{ @tagName(column) }); + const args: @Tuple(&.{ @TypeOf(value) }) = .{ value }; + }; + } + // fn And() type { + // todo: concat fmt slices and args tuples + // } + + string: Node, + + fn eql(comptime column: std.meta.Tag(ColumnUnion), value: @FieldType(ColumnUnion, @tagName(column))) Self { + return .{ .string = .{ .eql = Eql(column, value) } }; + } + + // TODO: call this and2 ? + fn op_and(comptime lhs: Self, comptime rhs: Self) Self { + return .{ .string = std.fmt.comptimePrint("{s} AND {s}", .{ lhs.repr, rhs.repr }) }; + } + }; +} + +fn Condition(comptime Schema: type, comptime table: std.meta.Tag(Schema)) type { + return struct { + fn eql(comptime column: std.meta.Tag(@FieldType(Schema, @tagName(table))), value: @FieldType(@FieldType(Schema, @tagName(table)), @tagName(column))) @This() { + std.debug.print("column: {}\texpected value: {}\n", .{ column, value }); + return .{}; + } + }; +} + +test "schema" { + const Schema = struct { + contacts: Contacts, + // contacts: struct { + // contact_id: Integer = .constrain(.primary_key), + // age: Integer = .unconstrained, + // contact_id: Integer(usize) = .constrain(.{ .primary_key = true }), + // first_name: Default(VarChar(255), "Hans"), + // }, + }; + + // const asdf = DatabaseSchema(Schema); + // _ = asdf; + + var db: Database(Schema) = try .init(.temp_in_memory); + defer db.deinit(); + + const x = db.select(.contacts, &.{ .id, .first_name }).where(.eql(.id, 42)); + _ = x; + + const t: Test(@TypeOf(db).Schema, .contacts, &.{ .id, .first_name }) = .eql(.id, 42); + // const t: Test(@TypeOf(db).Schema, .contacts, &.{ .id, .first_name }) = .op_and(.eql(.id, 42), .eql(.first_name, "name")); + std.debug.print("test output: {s}{}\n", switch (t.string) { .eql => |eq| .{ eq.fmt, eq.args } }); + + const Int = Default(Unique(Integer(usize)), 42); + std.debug.print("default unique integer with default 42: {s}\n", .{ Int.string }); + + +} -- cgit v1.2.3-70-g09d2