aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2026-05-28 16:30:47 +0200
committerNathan Reiner <nathan@nathanreiner.xyz>2026-05-28 16:30:47 +0200
commit94d91c425cf7e70492fc87eab770b366a59a8a40 (patch)
tree812e668a2287d1933fa977b08a18c75e90f78db7
parentcd32a3ca0173676ed31486b5fa4dbd8c744cdec5 (diff)
parente18128f73c2b78cb9505754facff3835b2f70ba4 (diff)
Merge branch 'develop' of https://gitlab.com/Jockerkat/memora into dev
-rw-r--r--src/db/sqlite.zig387
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 });
}