aboutsummaryrefslogtreecommitdiff
path: root/src/db/sqlite_new.zig
diff options
context:
space:
mode:
authorAlexander Rolley <arolley35@gmail.com>2026-06-14 18:38:30 +0200
committerAlexander Rolley <arolley35@gmail.com>2026-06-14 18:38:30 +0200
commit320ff32b028add3dfc827229dc2c16727b0e74ef (patch)
tree988339e21d37926cf8d85b2364c5fdb4b5b9a0b0 /src/db/sqlite_new.zig
parent1d6e34388d9d792fd184c8b9e0fb11fb9b7e14f9 (diff)
Update.
Diffstat (limited to 'src/db/sqlite_new.zig')
-rw-r--r--src/db/sqlite_new.zig263
1 files changed, 263 insertions, 0 deletions
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 });
+
+
+}