diff --git a/packages/datastore/src/ColumnType.zig b/packages/datastore/src/ColumnType.zig new file mode 100644 index 0000000..6f5e1a8 --- /dev/null +++ b/packages/datastore/src/ColumnType.zig @@ -0,0 +1,156 @@ +const std = @import("std"); +const vm = @import("vecmath"); + +pub const ColumnType = enum { + /// 1 byte, 0x00 or 0x01 + bool, + /// 1 bit, bit-packed + bit, + u8, + u16, + u32, + u64, + i8, + i16, + i32, + i64, + f32, + f64, + /// RGBA u8 + color, + /// RGBA f16 + color_hdr, + /// Stored as ×8 AOSOA + mat3x2, + /// Stored as ×8 AOSOA + mat4x4, + /// Stored as ×8 AOSOA + complex, + /// Stored as ×8 AOSOA + quaternion, + /// Stored as ×8 AOSOA + vec2, + /// Stored as ×8 AOSOA + vec2i, + /// Stored as ×8 AOSOA + vec3, + /// Stored as ×8 AOSOA + vec3i, + /// Stored as ×8 AOSOA + vec4, + /// Stored as ×8 AOSOA + vec4i, + /// 8 B (1 B len ++ <= 7 B str) + str8, + /// 16 B (1 B len ++ <= 15 B str) + str16, + /// 32 B (1 B len ++ <= 31 B str) + str32, + /// 64 B (1 B len ++ <= 63 B str) + str64, + /// 128 B (1B len ++ <= 127 B str) + str128, + /// 256 B (1 B len ++ <= 255 B str) + str256, + /// 512 B (2 B len ++ <= 510 B str) + str512, + /// 1 kiB (2 B len ++ <= 1022 B str) + str1k, + /// 2 kiB (2 B len ++ <= 2046 B str) + str2k, + /// 4 kiB (2 B len ++ <= 4094 B str) + str4k, + + pub fn valuesPerCluster(self: ColumnType) usize { + return switch (self) { + .bool => 1, + .bit => 8, + .u8 => 1, + .u16 => 1, + .u32 => 1, + .u64 => 1, + .i8 => 1, + .i16 => 1, + .i32 => 1, + .i64 => 1, + .f32 => 1, + .f64 => 1, + .color => 1, + .color_hdr => 1, + .mat3x2 => 8, + .mat4x4 => 8, + .complex => 8, + .quaternion => 8, + .vec2 => 8, + .vec2i => 8, + .vec3 => 8, + .vec3i => 8, + .vec4 => 8, + .vec4i => 8, + .str8 => 1, + .str16 => 1, + .str32 => 1, + .str64 => 1, + .str128 => 1, + .str256 => 1, + .str512 => 1, + .str1k => 1, + .str2k => 1, + .str4k => 1, + }; + } + + pub fn clusterByteSize(self: ColumnType) usize { + return switch (self) { + .bool => 1, + .bit => 1, + .u8 => 1, + .u16 => 2, + .u32 => 4, + .u64 => 8, + .i8 => 1, + .i16 => 2, + .i32 => 4, + .i64 => 8, + .f32 => 4, + .f64 => 8, + .color => 4, + .color_hdr => 8, + .mat3x2 => 24 * 8, + .mat4x4 => 64 * 8, + .complex => 8 * 8, + .quaternion => 16 * 8, + .vec2 => 8 * 8, + .vec2i => 8 * 8, + .vec3 => 12 * 8, + .vec3i => 12 * 8, + .vec4 => 16 * 8, + .vec4i => 16 * 8, + .str8 => 8, + .str16 => 16, + .str32 => 32, + .str64 => 64, + .str128 => 128, + .str256 => 256, + .str512 => 512, + .str1k => 1024, + .str2k => 2048, + .str4k => 4096, + }; + } + + pub fn valuesPerPage(self: ColumnType, page_size: usize) usize { + const clusters_per_page = page_size / self.clusterByteSize(); + return self.valuesPerCluster() * clusters_per_page; + } + + test valuesPerPage { + const page_size = 4096; + + try std.testing.expectEqual(page_size, valuesPerPage(.bool, page_size)); + try std.testing.expectEqual(8 * page_size, valuesPerPage(.bit, page_size)); + try std.testing.expectEqual(page_size / 4, valuesPerPage(.u32, page_size)); + try std.testing.expectEqual(page_size / 8, valuesPerPage(.u64, page_size)); + try std.testing.expectEqual(page_size / @sizeOf(vm.Vector3x8) * 8, valuesPerPage(.vec3, page_size)); + } +}; diff --git a/packages/datastore/src/TaggedValue.zig b/packages/datastore/src/TaggedValue.zig new file mode 100644 index 0000000..1b9910a --- /dev/null +++ b/packages/datastore/src/TaggedValue.zig @@ -0,0 +1,32 @@ +const std = @import("std"); +const vm = @import("vecmath"); + +pub const TaggedValue = union(enum) { + /// Corresponds to `ColumnType.boolean` and `ColumnType.bit`. + bool: bool, + u8: u8, + u16: u16, + u32: u32, + u64: u64, + i8: i8, + i16: i16, + i32: i32, + i64: i64, + f32: f32, + f64: f64, + color: vm.Color, + color_hdr: vm.ColorHdr, + mat3x2: vm.Matrix3x2, + mat4x4: vm.Matrix4x4, + complex: vm.Complex, + quaternion: vm.Quaternion, + vec2: vm.Vector2, + vec2i: vm.Vector2Int, + vec3: vm.Vector3, + vec3i: vm.Vector3Int, + vec4: vm.Vector4, + vec4i: vm.Vector4Int, + /// Corresponds to all string types in `ColumnType`, from `ColumnType.str8` + /// to `ColumnType.str4k`. + str: []const u8, +}; diff --git a/packages/datastore/src/root.zig b/packages/datastore/src/root.zig index 4e18b6d..9e38803 100644 --- a/packages/datastore/src/root.zig +++ b/packages/datastore/src/root.zig @@ -1,6 +1,9 @@ const std = @import("std"); const vm = @import("vecmath"); +pub const ColumnType = @import("ColumnType.zig").ColumnType; +pub const TaggedValue = @import("TaggedValue.zig").TaggedValue; + pub const page_size = 4096; pub const page_align = std.mem.Alignment.fromByteUnits(page_size); @@ -27,6 +30,35 @@ fn IdMixin(comptime Enum: type) type { }; } +fn FixedString(comptime Len: type, comptime total_size: usize) type { + return extern struct { + len: Len, + str: [max_str_len]u8, + + const max_str_len = total_size - @sizeOf(Len); + + pub fn slice(self: *@This()) []const u8 { + return self.str[0..self.len]; + } + + pub fn copy(self: *@This(), src: []const u8) void { + std.debug.assert(src.len <= max_str_len); + + self.len = @intCast(src.len); + @memcpy(self.str[0..src.len], src); + @memset(self.str[src.len..], 0); + } + + pub fn tryCopy(self: *@This(), src: []const u8) error{RangeError}!void { + if (src.len > max_str_len) { + return error.RangeError; + } + + self.copy(src); + } + }; +} + pub const ColumnId = enum(u16) { _, @@ -51,205 +83,6 @@ pub const RowId = enum(u64) { pub const next = IdMixin(RowId).next; }; -pub const ColumnType = enum { - /// 1 byte, 0x00 or 0x01 - boolean, - /// 1 bit, bit-packed - bit, - u8, - u16, - u32, - u64, - i8, - i16, - i32, - i64, - f32, - f64, - /// RGBA u8 - color, - /// RGBA f16 - color_hdr, - mat3x2, - mat4x4, - complex, - quaternion, - vec2, - vec2i, - vec3, - vec3i, - vec4, - vec4i, - /// 16 B (1 B len ++ 15 B str) - str16, - /// 64 B (1 B len ++ 63 B str) - str64, - /// 256 B (1 B len ++ 255 B str) - str256, - /// 1 kiB (2 B len ++ <= 1022 B str) - str1k, - /// 4 kiB (2 B len ++ <= 4094 B str) - str4k, - - pub fn countPerPage(self: ColumnType) usize { - return switch (self) { - .str16 => page_size / 16, - .str64 => page_size / 64, - .str256 => page_size / 256, - .str1k => page_size / 1024, - .str4k => page_size / 4096, - inline else => |x| page_size / @sizeOf(ZigTypeSimd(x)) * x.simdWidth(), - }; - } - - test countPerPage { - try std.testing.expectEqual(page_size, countPerPage(.boolean)); - try std.testing.expectEqual(8 * page_size, countPerPage(.bit)); - try std.testing.expectEqual(page_size / 4, countPerPage(.u32)); - try std.testing.expectEqual(page_size / 8, countPerPage(.u64)); - try std.testing.expectEqual(page_size / @sizeOf(vm.Vector3x8) * 8, countPerPage(.vec3)); - } - - pub fn simdWidth(self: ColumnType) usize { - return switch (self) { - .boolean => 1, - .bit => 8, - .u8 => 1, - .u16 => 1, - .u32 => 1, - .u64 => 1, - .i8 => 1, - .i16 => 1, - .i32 => 1, - .i64 => 1, - .f32 => 1, - .f64 => 1, - .color => 1, - .color_hdr => 1, - .mat3x2 => 8, - .mat4x4 => 8, - .complex => 8, - .quaternion => 8, - .vec2 => 8, - .vec2i => 8, - .vec3 => 8, - .vec3i => 8, - .vec4 => 8, - .vec4i => 8, - .str16 => 1, - .str64 => 1, - .str256 => 1, - .str1k => 1, - .str4k => 1, - }; - } - - pub fn ZigType(comptime self: ColumnType) type { - return switch (self) { - .boolean => bool, - .bit => u1, - .u8 => u8, - .u16 => u16, - .u32 => u32, - .u64 => u64, - .i8 => i8, - .i16 => i16, - .i32 => i32, - .i64 => i64, - .f32 => f32, - .f64 => f64, - .color => vm.Color, - .color_hdr => vm.ColorHdr, - .mat3x2 => vm.Matrix3x2, - .mat4x4 => vm.Matrix4x4, - .complex => vm.Complex, - .quaternion => vm.Quaternion, - .vec2 => vm.Vector2, - .vec2i => vm.Vector2Int, - .vec3 => vm.Vector3, - .vec3i => vm.Vector3Int, - .vec4 => vm.Vector4, - .vec4i => vm.Vector4Int, - .str16 => []const u8, - .str64 => []const u8, - .str256 => []const u8, - .str1k => []const u8, - .str4k => []const u8, - }; - } - - pub fn ZigTypeSimd(comptime self: ColumnType) type { - return switch (self) { - .boolean => bool, - .bit => u8, - .u8 => u8, - .u16 => u16, - .u32 => u32, - .u64 => u64, - .i8 => i8, - .i16 => i16, - .i32 => i32, - .i64 => i64, - .f32 => f32, - .f64 => f64, - .color => vm.Color, - .color_hdr => vm.ColorHdr, - .mat3x2 => vm.Matrix3x2x8, - .mat4x4 => vm.Matrix4x4x8, - .complex => vm.Complex_x8, - .quaternion => vm.Quaternion_x8, - .vec2 => vm.Vector2x8, - .vec2i => vm.Vector2Int_x8, - .vec3 => vm.Vector3x8, - .vec3i => vm.Vector3Int_x8, - .vec4 => vm.Vector4x8, - .vec4i => vm.Vector4Int_x8, - .str16 => []const u8, - .str64 => []const u8, - .str256 => []const u8, - .str1k => []const u8, - .str4k => []const u8, - }; - } -}; - -pub const TypedValue = union(enum) { - boolean: bool, - uint: u64, - int: i64, - f32: f32, - f64: f64, - color: vm.Color, - color_hdr: vm.ColorHdr, - mat3x2: vm.Matrix3x2, - mat4x4: vm.Matrix4x4, - complex: vm.Complex, - quaternion: vm.Quaternion, - vec2: vm.Vector2, - vec2i: vm.Vector2Int, - vec3: vm.Vector3, - vec3i: vm.Vector3Int, - vec4: vm.Vector4, - vec4i: vm.Vector4Int, - str: []const u8, - - pub fn initBool(x: bool) TypedValue { - return .{ .boolean = x }; - } - - pub fn initUint(x: u64) TypedValue { - return .{ .uint = x }; - } - - pub fn initColor(x: vm.Color) TypedValue { - return .{ .color = x }; - } - - pub fn initStr(x: []const u8) TypedValue { - return .{ .str = x }; - } -}; - pub const TableDefinition = struct { id: TableId, column_definitions: []const ColumnDefinition, @@ -262,7 +95,7 @@ pub const ColumnDefinition = struct { pub const CellValue = struct { id: ColumnId, - value: TypedValue, + value: TaggedValue, }; pub const Database = struct { @@ -331,6 +164,14 @@ pub const Database = struct { table.next_row_id = try row_id.next(); return row_id; } + + pub fn getValue(self: *Database, table_id: TableId, column_id: ColumnId, row_id: RowId) !TaggedValue { + const table = self.tables.getPtr(table_id) orelse return error.TableNotFound; + const column = table.columns.getPtr(column_id) orelse return error.ColumnNotFound; + + if (row_id.toInt() >= table.next_row_id.toInt()) return error.RowIdNotFound; + return column.get(row_id); + } }; pub const Table = struct { @@ -407,144 +248,188 @@ pub const Column = struct { self.* = undefined; } - pub fn set(self: *Column, row_id: RowId, value: TypedValue, allocator: std.mem.Allocator) !void { - const count_per_page = self.type.countPerPage(); + pub fn get(self: *Column, row_id: RowId) TaggedValue { + const values_per_page = self.type.valuesPerPage(page_size); - const page_i = @as(usize, row_id.toInt() / count_per_page); - const page_j = @as(usize, row_id.toInt() % count_per_page); + const page_index = @as(usize, row_id.toInt() / values_per_page); + const value_index = @as(usize, row_id.toInt() % values_per_page); - try self.pages.ensureTotalCapacity(allocator, page_i + 1); - while (page_i >= self.pages.items.len) { + const page = &self.pages.items[page_index]; + + return switch (self.type) { + .bool => .{ .bool = page.get(bool, value_index) }, + .bit => .{ .bool = page.getBit(value_index) }, + .u8 => .{ .u8 = page.get(u8, value_index) }, + .u16 => .{ .u16 = page.get(u16, value_index) }, + .u32 => .{ .u32 = page.get(u32, value_index) }, + .u64 => .{ .u64 = page.get(u64, value_index) }, + .i8 => .{ .i8 = page.get(i8, value_index) }, + .i16 => .{ .i16 = page.get(i16, value_index) }, + .i32 => .{ .i32 = page.get(i32, value_index) }, + .i64 => .{ .i64 = page.get(i64, value_index) }, + .f32 => .{ .f32 = page.get(f32, value_index) }, + .f64 => .{ .f64 = page.get(f64, value_index) }, + .color => .{ .color = page.get(vm.Color, value_index) }, + .color_hdr => .{ .color_hdr = page.get(vm.ColorHdr, value_index) }, + .mat3x2 => .{ .mat3x2 = page.getClustered(vm.Matrix3x2, 8, value_index) }, + .mat4x4 => .{ .mat4x4 = page.getClustered(vm.Matrix4x4, 8, value_index) }, + .complex => .{ .complex = page.getClustered(vm.Complex, 8, value_index) }, + .quaternion => .{ .quaternion = page.getClustered(vm.Quaternion, 8, value_index) }, + .vec2 => .{ .vec2 = page.getClustered(vm.Vector2, 8, value_index) }, + .vec2i => .{ .vec2i = page.getClustered(vm.Vector2Int, 8, value_index) }, + .vec3 => .{ .vec3 = page.getClustered(vm.Vector3, 8, value_index) }, + .vec3i => .{ .vec3i = page.getClustered(vm.Vector3Int, 8, value_index) }, + .vec4 => .{ .vec4 = page.getClustered(vm.Vector4, 8, value_index) }, + .vec4i => .{ .vec4i = page.getClustered(vm.Vector4Int, 8, value_index) }, + .str8 => .{ .str = page.getPtr(FixedString(u8, 8), value_index).slice() }, + .str16 => .{ .str = page.getPtr(FixedString(u8, 16), value_index).slice() }, + .str32 => .{ .str = page.getPtr(FixedString(u8, 32), value_index).slice() }, + .str64 => .{ .str = page.getPtr(FixedString(u8, 64), value_index).slice() }, + .str128 => .{ .str = page.getPtr(FixedString(u8, 128), value_index).slice() }, + .str256 => .{ .str = page.getPtr(FixedString(u8, 256), value_index).slice() }, + .str512 => .{ .str = page.getPtr(FixedString(u16, 512), value_index).slice() }, + .str1k => .{ .str = page.getPtr(FixedString(u16, 1024), value_index).slice() }, + .str2k => .{ .str = page.getPtr(FixedString(u16, 2048), value_index).slice() }, + .str4k => .{ .str = page.getPtr(FixedString(u16, 4096), value_index).slice() }, + }; + } + + pub fn getBool(self: *Column, row_id: RowId) !bool { + const values_per_page = self.type.valuesPerPage(page_size); + + const page_index = @as(usize, row_id.toInt() / values_per_page); + const value_index = @as(usize, row_id.toInt() % values_per_page); + + const page = &self.pages.items[page_index]; + + return switch (self.type) { + .bool => page.get(bool, value_index), + .bit => page.getBit(value_index), + else => error.TypeError, + }; + } + + pub fn set(self: *Column, row_id: RowId, value: TaggedValue, allocator: std.mem.Allocator) !void { + const values_per_page = self.type.valuesPerPage(page_size); + + const page_index = @as(usize, row_id.toInt() / values_per_page); + const value_index = @as(usize, row_id.toInt() % values_per_page); + + try self.pages.ensureTotalCapacity(allocator, page_index + 1); + while (page_index >= self.pages.items.len) { self.pages.appendAssumeCapacity(try .init()); } - const page = &self.pages.items[page_i]; + const page = &self.pages.items[page_index]; - switch (value) { - .boolean => |x| switch (self.type) { - .boolean => page.assign(bool, page_j, x), - .bit => page.setBit(page_j, x), - else => return error.TypeError, + return switch (value) { + .bool => |x| switch (self.type) { + .bool => page.set(bool, value_index, x), + .bit => page.setBit(value_index, x), + else => error.TypeError, }, - .uint => |x| switch (self.type) { - .u8 => page.assign(u8, page_j, std.math.cast(u8, x) orelse return error.RangeError), - .u16 => page.assign(u16, page_j, std.math.cast(u16, x) orelse return error.RangeError), - .u32 => page.assign(u32, page_j, std.math.cast(u32, x) orelse return error.RangeError), - .u64 => page.assign(u64, page_j, x), - else => return error.TypeError, + .u8 => |x| switch (self.type) { + .u8 => page.set(u8, value_index, x), + else => error.TypeError, }, - .int => |x| switch (self.type) { - .i8 => page.assign(i8, page_j, std.math.cast(i8, x) orelse return error.RangeError), - .i16 => page.assign(i16, page_j, std.math.cast(i16, x) orelse return error.RangeError), - .i32 => page.assign(i32, page_j, std.math.cast(i32, x) orelse return error.RangeError), - .i64 => page.assign(i64, page_j, x), - else => return error.TypeError, + .u16 => |x| switch (self.type) { + .u16 => page.set(u16, value_index, x), + else => error.TypeError, }, - .f32 => |x| { - if (self.type != .f32) return error.TypeError; - page.assign(f32, page_j, x); + .u32 => |x| switch (self.type) { + .u32 => page.set(u32, value_index, x), + else => error.TypeError, }, - .f64 => |x| { - if (self.type != .f64) return error.TypeError; - page.assign(f64, page_j, x); + .u64 => |x| switch (self.type) { + .u64 => page.set(u64, value_index, x), + else => error.TypeError, }, - .color => |x| { - if (self.type != .color) return error.TypeError; - page.assign(vm.Color, page_j, x); + .i8 => |x| switch (self.type) { + .i8 => page.set(i8, value_index, x), + else => error.TypeError, }, - .color_hdr => |x| { - if (self.type != .color_hdr) return error.TypeError; - page.assign(vm.ColorHdr, page_j, x); + .i16 => |x| switch (self.type) { + .i16 => page.set(i16, value_index, x), + else => error.TypeError, }, - .mat3x2 => |x| { - _ = x; - @panic("TODO"); - //if (self.type != .mat3x2) return error.TypeError; - //const simd_i = page_j / 8; - //const simd_j = page_j % 8; - //const ptr = page.getPtr(vm.Matrix3x2x8, simd_i); - //ptr.ix[simd_j] = x.ix; - //ptr.iy[simd_j] = x.iy; - //ptr.jx[simd_j] = x.jx; - //ptr.jy[simd_j] = x.jy; - //ptr.tx[simd_j] = x.tx; - //ptr.ty[simd_j] = x.ty; + .i32 => |x| switch (self.type) { + .i32 => page.set(i32, value_index, x), + else => error.TypeError, }, - .mat4x4 => |x| { - _ = x; - @panic("TODO"); + .i64 => |x| switch (self.type) { + .i64 => page.set(i64, value_index, x), + else => error.TypeError, }, - .complex => |x| { - _ = x; - @panic("TODO"); + .f32 => |x| switch (self.type) { + .f32 => page.set(f32, value_index, x), + else => error.TypeError, }, - .quaternion => |x| { - _ = x; - @panic("TODO"); + .f64 => |x| switch (self.type) { + .f64 => page.set(f64, value_index, x), + else => error.TypeError, }, - .vec2 => |x| { - _ = x; - @panic("TODO"); + .color => |x| switch (self.type) { + .color => page.set(vm.Color, value_index, x), + else => error.TypeError, }, - .vec2i => |x| { - _ = x; - @panic("TODO"); + .color_hdr => |x| switch (self.type) { + .color_hdr => page.set(vm.ColorHdr, value_index, x), + else => error.TypeError, }, - .vec3 => |x| { - _ = x; - @panic("TODO"); + .mat3x2 => |x| switch (self.type) { + .mat3x2 => page.setClustered(vm.Matrix3x2, 8, value_index, x), + else => error.TypeError, }, - .vec3i => |x| { - _ = x; - @panic("TODO"); + .mat4x4 => |x| switch (self.type) { + .mat4x4 => page.setClustered(vm.Matrix4x4, 8, value_index, x), + else => error.TypeError, }, - .vec4 => |x| { - _ = x; - @panic("TODO"); + .complex => |x| switch (self.type) { + .complex => page.set(vm.Complex, value_index, x), + else => error.TypeError, }, - .vec4i => |x| { - _ = x; - @panic("TODO"); + .quaternion => |x| switch (self.type) { + .quaternion => page.set(vm.Quaternion, value_index, x), + else => error.TypeError, + }, + .vec2 => |x| switch (self.type) { + .vec2 => page.set(vm.Vector2, value_index, x), + else => error.TypeError, + }, + .vec2i => |x| switch (self.type) { + .vec2i => page.set(vm.Vector2Int, value_index, x), + else => error.TypeError, + }, + .vec3 => |x| switch (self.type) { + .vec3 => page.set(vm.Vector3, value_index, x), + else => error.TypeError, + }, + .vec3i => |x| switch (self.type) { + .vec3i => page.set(vm.Vector3Int, value_index, x), + else => error.TypeError, + }, + .vec4 => |x| switch (self.type) { + .vec4 => page.set(vm.Vector4, value_index, x), + else => error.TypeError, + }, + .vec4i => |x| switch (self.type) { + .vec4i => page.set(vm.Vector4Int, value_index, x), + else => error.TypeError, }, .str => |x| switch (self.type) { - .str16 => { - if (x.len > 15) return error.RangeError; - const slice = page.ptr[page_j * 16 .. (page_j + 1) * 16]; - @as(*u8, @ptrCast(slice[0..1])).* = @intCast(x.len); - @memcpy(slice[1 .. 1 + x.len], x); - @memset(slice[1 + x.len ..], 0); - }, - .str64 => { - if (x.len > 63) return error.RangeError; - const slice = page.ptr[page_j * 64 .. (page_j + 1) * 64]; - @as(*u8, @ptrCast(slice[0..1])).* = @intCast(x.len); - @memcpy(slice[1 .. 1 + x.len], x); - @memset(slice[1 + x.len ..], 0); - }, - .str256 => { - if (x.len > 255) return error.RangeError; - const slice = page.ptr[page_j * 256 .. (page_j + 1) * 256]; - @as(*u8, @ptrCast(slice[0..1])).* = @intCast(x.len); - @memcpy(slice[1 .. 1 + x.len], x); - @memset(slice[1 + x.len ..], 0); - }, - .str1k => { - if (x.len > 1022) return error.RangeError; - const slice = page.ptr[page_j * 1024 .. (page_j + 1) * 1024]; - @as(*u16, @ptrCast(@alignCast(slice[0..2]))).* = @intCast(x.len); - @memcpy(slice[2 .. 2 + x.len], x); - @memset(slice[2 + x.len ..], 0); - }, - .str4k => { - if (x.len > 4094) return error.RangeError; - const slice = page.ptr[page_j * 4096 .. (page_j + 1) * 4096]; - @as(*u16, @ptrCast(@alignCast(slice[0..2]))).* = @intCast(x.len); - @memcpy(slice[2 .. 2 + x.len], x); - @memset(slice[2 + x.len ..], 0); - }, - else => return error.TypeError, + .str8 => page.getPtr(FixedString(u8, 8), value_index).tryCopy(x), + .str16 => page.getPtr(FixedString(u8, 16), value_index).tryCopy(x), + .str32 => page.getPtr(FixedString(u8, 32), value_index).tryCopy(x), + .str64 => page.getPtr(FixedString(u8, 64), value_index).tryCopy(x), + .str128 => page.getPtr(FixedString(u8, 128), value_index).tryCopy(x), + .str256 => page.getPtr(FixedString(u8, 256), value_index).tryCopy(x), + .str512 => page.getPtr(FixedString(u16, 512), value_index).tryCopy(x), + .str1k => page.getPtr(FixedString(u16, 1024), value_index).tryCopy(x), + .str2k => page.getPtr(FixedString(u16, 2048), value_index).tryCopy(x), + .str4k => page.getPtr(FixedString(u16, 4096), value_index).tryCopy(x), + else => error.TypeError, }, - } + }; } }; @@ -565,18 +450,63 @@ pub const Page = struct { self.* = undefined; } - pub fn assign(self: Page, comptime T: type, index: usize, value: T) void { + pub fn get(self: Page, comptime T: type, index: usize) T { const byte_offset = index * @sizeOf(T); std.debug.assert(byte_offset + @sizeOf(T) <= page_size); - @as([*]T, @ptrCast(self.ptr))[index] = value; + return @as([*]T, @ptrCast(self.ptr))[index]; } pub fn getPtr(self: Page, comptime T: type, index: usize) *T { const byte_offset = index * @sizeOf(T); std.debug.assert(byte_offset + @sizeOf(T) <= page_size); - return @ptrCast(@alignCast(self.ptr[byte_offset..].ptr)); + return &@as([*]T, @ptrCast(self.ptr))[index]; + } + + pub fn getBit(self: Page, bit_index: usize) bool { + const byte_index = bit_index / 8; + const bit_shift = @as(u3, @intCast(bit_index % 8)); + + const mask = @as(u8, 1) << bit_shift; + return self.ptr[byte_index] & mask != 0; + } + + pub fn getClustered(self: Page, comptime T: type, comptime values_per_cluster: usize, index: usize) T { + const struct_info = switch (@typeInfo(T)) { + .@"struct" => |s| s, + else => @compileError("Expected " ++ @typeName(T) ++ " to be an extern struct."), + }; + + if (struct_info.layout != .@"extern") { + @compileError("Expected " ++ @typeName(T) ++ " to be an extern struct."); + } + + const cluster_index = index / values_per_cluster; + const value_index = index % values_per_cluster; + + const cluster_base_ptr = @as([*]u8, self.ptr) + cluster_index * @sizeOf(T) * values_per_cluster; + + var ret: T = undefined; + inline for (struct_info.fields) |field| { + if (field.is_comptime) { + @compileError("Unexpected comptime field " ++ @typeName(T) ++ "." ++ field.name ++ "."); + } + + const cluster_field_offset = @offsetOf(T, field.name) * values_per_cluster; + const cluster_field_ptr = @as(*[values_per_cluster]field.type, @ptrCast(@alignCast(cluster_base_ptr + cluster_field_offset))); + + @field(ret, field.name) = cluster_field_ptr[value_index]; + } + + return ret; + } + + pub fn set(self: Page, comptime T: type, index: usize, value: T) void { + const byte_offset = index * @sizeOf(T); + std.debug.assert(byte_offset + @sizeOf(T) <= page_size); + + @as([*]T, @ptrCast(self.ptr))[index] = value; } pub fn setBit(self: Page, bit_index: usize, value: bool) void { @@ -591,6 +521,33 @@ pub const Page = struct { self.ptr[byte_index] &= mask; } } + + pub fn setClustered(self: Page, comptime T: type, comptime values_per_cluster: usize, index: usize, value: T) void { + const struct_info = switch (@typeInfo(T)) { + .@"struct" => |s| s, + else => @compileError("Expected " ++ @typeName(T) ++ " to be an extern struct."), + }; + + if (struct_info.layout != .@"extern") { + @compileError("Expected " ++ @typeName(T) ++ " to be an extern struct."); + } + + const cluster_index = index / values_per_cluster; + const value_index = index % values_per_cluster; + + const cluster_base_ptr = @as([*]u8, self.ptr) + cluster_index * @sizeOf(T) * values_per_cluster; + + inline for (struct_info.fields) |field| { + if (field.is_comptime) { + @compileError("Unexpected comptime field " ++ @typeName(T) ++ "." ++ field.name ++ "."); + } + + const cluster_field_offset = @offsetOf(T, field.name) * values_per_cluster; + const cluster_field_ptr = @as(*[values_per_cluster]field.type, @ptrCast(@alignCast(cluster_base_ptr + cluster_field_offset))); + + cluster_field_ptr[value_index] = @field(value, field.name); + } + } }; test { @@ -616,7 +573,7 @@ test { }, .{ .id = column_ids.password_hash, - .type = .str256, + .type = .str128, }, .{ .id = column_ids.color, @@ -625,33 +582,45 @@ test { }, }); - _ = try db.insertRow(table_ids.users, &.{ + const admin_id = try db.insertRow(table_ids.users, &.{ .{ .id = column_ids.username, - .value = .initStr("admin"), + .value = .{ .str = "admin" }, }, .{ .id = column_ids.password_hash, - .value = .initStr("$argon2id$v=19$m=16,t=2,p=1$R0l1WjlpM1BUSXZ3dU0xbQ$muWG51NiQjaIe6RTh6LJgk/7VJMvtbtJiN5Z11fEFbI"), + .value = .{ .str = "$argon2id$v=19$m=16,t=2,p=1$R0l1WjlpM1BUSXZ3dU0xbQ$muWG51NiQjaIe6RTh6LJgk/7VJMvtbtJiN5Z11fEFbI" }, }, .{ .id = column_ids.color, - .value = .initColor(.red), + .value = .{ .color = .red }, }, }); - _ = try db.insertRow(table_ids.users, &.{ + const user_id = try db.insertRow(table_ids.users, &.{ .{ .id = column_ids.username, - .value = .initStr("user"), + .value = .{ .str = "user" }, }, .{ .id = column_ids.password_hash, - .value = .initStr("$argon2id$v=19$m=16,t=2,p=1$RlNyZTR3dklRU1hVb3hHSA$8EDVcnkQouADrpvb5bONOqQWrUil1Jc/YaZGe3t34q0"), + .value = .{ .str = "$argon2id$v=19$m=16,t=2,p=1$RlNyZTR3dklRU1hVb3hHSA$8EDVcnkQouADrpvb5bONOqQWrUil1Jc/YaZGe3t34q0" }, }, .{ .id = column_ids.color, - .value = .initColor(.blue), + .value = .{ .color = .blue }, }, }); + + const admin_username = try db.getValue(table_ids.users, column_ids.username, admin_id); + const admin_color = try db.getValue(table_ids.users, column_ids.color, admin_id); + + const user_username = try db.getValue(table_ids.users, column_ids.username, user_id); + const user_color = try db.getValue(table_ids.users, column_ids.color, user_id); + + try std.testing.expectEqualStrings("admin", admin_username.str); + try std.testing.expectEqual(vm.Color.red, admin_color.color); + + try std.testing.expectEqualStrings("user", user_username.str); + try std.testing.expectEqual(vm.Color.blue, user_color.color); }