diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/db/sqlite.zig | 478 | ||||
| -rw-r--r-- | src/db/sqlite_new.zig | 263 |
2 files changed, 505 insertions, 236 deletions
diff --git a/src/db/sqlite.zig b/src/db/sqlite.zig index 4c59b62..28d6487 100644 --- a/src/db/sqlite.zig +++ b/src/db/sqlite.zig @@ -1,7 +1,176 @@ const std = @import("std"); const sqlite = @import("sqlite"); const assert = std.debug.assert; -const math = std.math; + +pub fn Database(comptime T: type) type { + const tables = switch (@typeInfo(T)) { + .@"struct" => |s| s.fields, + else => @compileError("Database schema must be of type `struct`"), + }; + + // NOTE: `std.math.log2_int_ceil(@TypeOf(tables.len), tables.len)` panics + // if `tables.len` is zero. The below properly handles the case of + // a (useless) schema that does not define any tables. + const TagIntTables = @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]TagIntTables = 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: A table must have at least one column + // (see `https://sqlite.org/syntax/create-table-stmt.html`). + if (columns.len == 0) @compileError("Table `" ++ table.name ++ "` must have at least one column"); + + const TagIntColumns = @Int(.unsigned, std.math.log2_int_ceil(@TypeOf(columns.len), columns.len)); + + var column_names : [columns.len][]const u8 = undefined; + var column_types : [columns.len]type = undefined; + var column_values: [columns.len]TagIntColumns = undefined; + + inline for (&column_names, &column_types, &column_values, columns, 0..) |*column_name, *column_type, *column_value, column, column_index| { + // TODO: check column.type (of type `Type`) + + column_name.* = column.name; + column_type.* = column.type; + column_value.* = column_index; + } + + const TagColumns = @Enum(TagIntColumns, .exhaustive, &column_names, &column_values); + const Columns = @Union(.auto, TagColumns, &column_names, &column_types, &@splat(.{})); + + table_name.* = table.name; + table_type.* = Columns; + table_value.* = table_index; + } + + const TagTables = @Enum(TagIntTables, .exhaustive, &table_names, &table_values); + const Tables = @Union(.auto, TagTables, &table_names, &table_types, &@splat(.{})); + + return struct { + inner: Inner, + + pub const Check = enum { + ok, + busy, + }; + + 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, + }; + + const Schema = Tables; + const Self = @This(); + + const Inner = struct { + handle: *sqlite.sqlite3, + }; + + pub fn init(kind: Kind) Error!Self { + 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)); + const primary = result.asPrimary(); + + return if (primary == .ok) .{ .handle = handle.? } else result.toError(Error, handle); + } + + pub fn deinit(self: *Self) Check { + const result: Result = .from(sqlite.sqlite3_close(self.handle)); + const primary = result.asPrimary(); + + if (primary == .busy) return .busy; + + self.* = undefined; + return .ok; + } + + // NOTE: SELECT without FROM is non-conforming SQL + // https://stackoverflow.com/questions/3732422/select-from-nothing + // https://modern-sql.com/use-case/select-without-from + pub fn select(self: Self, comptime table: std.meta.Tag(Schema), comptime columns: []const std.meta.Tag(@FieldType(Schema, @tagName(table)))) Select(Self, table, columns) { + _ = rows; + return .{ .db = self.handle }; + } + }; +} + +fn Select(comptime Db: type, comptime table: std.meta.Tag(Db.Schema), comptime columns: []const std.meta.Tag(@FieldType(Db.Schema, @tagName(table)))) type { + return struct { + db: *sqlite.sqlite3, + comptime table: std.meta.Tag(Db.Schema) = table, + comptime columns: []const std.meta.Tag(@FieldType(Db.Schema, @tagName(table))) = columns, + + const Self = @This(); + + // pub fn where(self: Self, ) + }; +} + + + +// pub const Type = struct { +// string: String.Static, +// type: type, +// +// pub fn integer() Type { +// // end of Type recursion +// } +// +// pub fn unique(Type) Type { +// +// } +// +// ... +// }; + +// NEVER PUB!!! +const String = struct { + const Static = struct { + value: []const u8, + }; + + const Dynamic = struct { + fmt: []const u8, + args: type, + }; + + fn contains(haystack: []const u8, needle: []const u8) bool { + if (haystack.len < needle.len) return false; + + var start: usize = 0; + while (start + needle.len <= haystack.len) : (start += 1) { + if (std.mem.eql(u8, haystack[start..(start + needle.len)], needle)) return true; + } + + return false; + } +}; // pub fn Database(comptime S: Schema) type { // _ = S; @@ -88,168 +257,16 @@ const math = std.math; // }; // } - -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, +// pub const Database = struct { +// schema: Schema, // -// 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 = &.{} }; +// pub fn foo(self: Database) void { +// _ = self; +// std.debug.print("asdf\n", .{}); // } // }; - -// 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 @@ -393,27 +410,22 @@ const Result = enum(i32) { 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)), - // } - // } + fn toError(self: Result, comptime Error: type, handle: ?*sqlite.sqlite3) Error { + if (std.enums.fromInt(Extended, @intFromEnum(self))) |extended| switch (extended) { + inline else => |code| return @field(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(Error, @tagName(code)), + } else switch (self.asPrimary()) { + inline else => |code| return @field(Error, @tagName(code)), + } + } /// Returns the primary result code. fn asPrimary(self: Result) Primary { @@ -422,72 +434,66 @@ const Result = enum(i32) { }; 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 }, - // }, - // }; + const Schema = struct { + contacts: Contacts, + foo: Foo, - // 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 Foo = struct { + bar: usize, + }; - std.debug.print("{}\n", .{ db }); + const Contacts = struct { + contact_id: usize, // CHANGEME: to `Type` - db.foo(); + const all = &.{ .contact_id }; + }; + }; - // 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 }); + var db: Database(Schema) = try .init(.temp_in_memory); + defer assert(db.deinit() == .ok); + _ = db.select(.contacts, &.{ .contact_id }, .all); + var ty = db.select(.contacts, Schema.Contacts.all, .all); + ty.table = .foo; - // const db: Database(schema) = try .init(.temp_in_memory); - // _ = db; - // defer db.deinit(); + // // SELECT * == SELECT ALL * + // db.select(.all).from(...) + // + // db.select(.distinct(...)).from(...)... + // + // // If the simple SELECT is a SELECT ALL, then the entire set of result rows are returned by the SELECT. If neither ALL or DISTINCT are present, then the behavior is as if ALL were specified. + // db.select(enum { all, distinct }, + // * == .all(), + // table-name.* == .table(.table_name), + // + // ) +} - // 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 - // \\ ); +// test { + // 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`. + // + // CREATE TABLE contacts ( + // contact_id INTEGER PRIMARY KEY, + // first_name TEXT NOT NULL DEFAULT "Hans", + // last_name TEXT NOT NULL DEFAULT "Mustermann", + // nickname TEXT, + // email TEXT NOT NULL UNIQUE, + // phone TEXT NOT NULL UNIQUE // ); + // const S = struct { + // contacts: struct { + // contact_id: PrimaryKey(Integer, .{}), + // first_name: VarChar(255, .{}) = "Hans", + // last_name: VarChar(255, .{}) = "Mustermann", + // nickname: ?VarChar(255, .{}), + // email: VarChar(255, .{.unique}), + // phone: VarChar(255, .{.unique}), + // }, + // }; // - // std.debug.print("{any}\n", .{ query }); -} + // // NOTE: init connects to DB and creates tables if not already present + // var db: Database(Schema(S)) = try .init(.temp_in_memory); + // db.deinit(); +// } 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 }); + + +} |