tiny.sql.row
Defined in tiny.sql.
API (13)
Actions
Public operations.
Types and contracts
Public types and contracts.
CollationColumnCursor: A cursor is a traversal over one row's columns, of a size fixed at compile time.ErrorStorageValueView
Source
Source: lib/sql/src/root.zig:39
zig
pub const row = @import("row.zig");Source: lib/sql/src/row.zig:5
zig
const std = @import("std");const simd = @import("simd");const Bytes = simd.ScalableTag(u8);pub const Error = error{ ColumnOutOfBounds, InvalidRow, OutputTooSmall, RowTooLarge,};pub const Storage = enum { nil, integer, text, blob,};pub const Collation = enum { binary, nocase, rtrim,};pub const Column = struct { collation: Collation = .binary,};pub fn Spec(comptime ColumnTag: type) type { @setEvalBranchQuota(100_000); const enum_info = switch (@typeInfo(ColumnTag)) { .@"enum" => |info| info, else => @compileError("row spec columns must be an enum"), }; if (enum_info.field_names.len == 0) @compileError("row spec must contain a column"); const columns = std.meta.tags(ColumnTag); return SpecColumns(ColumnTag, columns[0..]);}fn SpecColumns(comptime ColumnTag: type, comptime selected: []const ColumnTag) type { @setEvalBranchQuota(100_000); validateSpecColumns(ColumnTag, selected); return struct { pub const Column: type = ColumnTag; pub const Indices: type = SpecIndices(ColumnTag, selected); pub const column_count = selected.len; pub const columns = columnList(ColumnTag, selected, 0); pub fn Project(comptime projection: []const ColumnTag) type { return projectSpec(ColumnTag, selected, projection); } pub fn parameters(comptime prefix_count: usize) []const u8 { return specParameters(column_count, prefix_count); } pub fn assignments(comptime prefix_count: usize) []const u8 { return specAssignments(ColumnTag, selected, prefix_count); } pub fn index(comptime column: ColumnTag) usize { return specIndex(ColumnTag, selected, column); } pub fn at(base: usize, comptime column: ColumnTag) usize { return specAt(ColumnTag, selected, base, column); } pub fn atBase(base: usize) Indices { return specAtBase(Indices, column_count, base); } pub fn parameter(comptime prefix_count: usize, comptime column: ColumnTag) usize { return specParameter(ColumnTag, selected, prefix_count, column); } pub fn indices( comptime base: usize, comptime projection: []const ColumnTag, ) [projection.len]usize { return specIndices(ColumnTag, selected, base, projection); } pub fn assertStruct(comptime Record: type) void { specAssertStruct(ColumnTag, selected, Record); } pub fn assertStructWidth(comptime Record: type, comptime extra_fields: usize) void { specAssertStructWidth(column_count, Record, extra_fields); } pub fn matches(view: View, base: usize) bool { return specMatches(column_count, view, base); } };}fn SpecIndices(comptime ColumnTag: type, comptime selected: []const ColumnTag) type { return struct { base: usize, pub fn at(self: @This(), comptime column: ColumnTag) usize { return specAt(ColumnTag, selected, self.base, column); } };}fn projectSpec( comptime ColumnTag: type, comptime selected: []const ColumnTag, comptime projection: []const ColumnTag,) type { @setEvalBranchQuota(100_000); for (projection) |column| { if (!containsColumn(ColumnTag, selected, column)) { @compileError(std.fmt.comptimePrint( "row projection contains unknown column {s}", .{@tagName(column)}, )); } } return SpecColumns(ColumnTag, projection);}fn specParameters(comptime column_count: usize, comptime prefix_count: usize) []const u8 { @setEvalBranchQuota(100_000); return comptime parameterList(prefixedCount(prefix_count, column_count), 1);}fn specAssignments( comptime ColumnTag: type, comptime selected: []const ColumnTag, comptime prefix_count: usize,) []const u8 { @setEvalBranchQuota(100_000); _ = prefixedCount(prefix_count, selected.len); return comptime assignmentList(ColumnTag, selected, prefix_count, 0);}fn specIndex( comptime ColumnTag: type, comptime selected: []const ColumnTag, comptime column: ColumnTag,) usize { inline for (selected, 0..) |candidate, ordinal| { if (candidate == column) return ordinal; } @compileError(std.fmt.comptimePrint( "row spec does not contain column {s}", .{@tagName(column)}, ));}fn specAt( comptime ColumnTag: type, comptime selected: []const ColumnTag, base: usize, comptime column: ColumnTag,) usize { const relative = specIndex(ColumnTag, selected, column); std.debug.assert(base <= std.math.maxInt(usize) - relative); return base + relative;}fn specAtBase(comptime Indices: type, column_count: usize, base: usize) Indices { std.debug.assert(column_count > 0); std.debug.assert(base <= std.math.maxInt(usize) - (column_count - 1)); return .{ .base = base };}fn specParameter( comptime ColumnTag: type, comptime selected: []const ColumnTag, comptime prefix_count: usize, comptime column: ColumnTag,) usize { _ = prefixedCount(prefix_count, selected.len); return prefix_count + specIndex(ColumnTag, selected, column) + 1;}fn specIndices( comptime ColumnTag: type, comptime selected: []const ColumnTag, comptime base: usize, comptime projection: []const ColumnTag,) [projection.len]usize { @setEvalBranchQuota(100_000); const Projected = projectSpec(ColumnTag, selected, projection); _ = Projected; var result: [projection.len]usize = undefined; inline for (projection, 0..) |column, ordinal| { result[ordinal] = specAt(ColumnTag, selected, base, column); } return result;}fn specAssertStruct( comptime ColumnTag: type, comptime selected: []const ColumnTag, comptime Record: type,) void { const info = structInfo(Record); if (info.field_names.len != selected.len) { @compileError("row spec and record field counts differ"); } for (selected, info.field_names) |column, field_name| { if (!std.mem.eql(u8, @tagName(column), field_name)) { @compileError(std.fmt.comptimePrint( "row column {s} does not match record field {s}", .{ @tagName(column), field_name }, )); } }}fn specAssertStructWidth( comptime column_count: usize, comptime Record: type, comptime extra_fields: usize,) void { const info = structInfo(Record); const expected = prefixedCount(extra_fields, column_count); if (info.field_names.len != expected) { @compileError("row spec and record field counts differ"); }}fn specMatches(column_count: usize, view: View, base: usize) bool { if (base > std.math.maxInt(usize) - column_count) return false; return view.columnCount() == base + column_count;}fn structInfo(comptime Record: type) std.builtin.Type.Struct { return switch (@typeInfo(Record)) { .@"struct" => |info| info, else => @compileError("row spec record must be a struct"), };}fn validateSpecColumns(comptime ColumnTag: type, comptime selected: []const ColumnTag) void { if (selected.len == 0) @compileError("row spec must contain a column"); for (selected, 0..) |column, index| { validateColumnName(@tagName(column)); for (selected[0..index]) |prior| { if (prior == column) { @compileError(std.fmt.comptimePrint( "row spec contains duplicate column {s}", .{@tagName(column)}, )); } } }}fn validateColumnName(comptime name: []const u8) void { if (name.len == 0) @compileError("row spec column name is empty"); for (name, 0..) |byte, index| { const letter = byte >= 'a' and byte <= 'z'; const digit = byte >= '0' and byte <= '9'; if (!letter and byte != '_' and (index == 0 or !digit)) { @compileError(std.fmt.comptimePrint( "row spec column name is not a SQL identifier: {s}", .{name}, )); } }}fn containsColumn( comptime ColumnTag: type, comptime columns: []const ColumnTag, comptime needle: ColumnTag,) bool { for (columns) |column| { if (column == needle) return true; } return false;}fn prefixedCount(comptime prefix_count: usize, comptime column_count: usize) usize { if (prefix_count > std.math.maxInt(usize) - column_count) { @compileError("row spec parameter count overflows usize"); } return prefix_count + column_count;}fn columnList( comptime ColumnTag: type, comptime columns: []const ColumnTag, comptime index: usize,) []const u8 { if (index == columns.len) return ""; const separator = if (index == 0) "" else ", "; return separator ++ @tagName(columns[index]) ++ columnList(ColumnTag, columns, index + 1);}fn parameterList(comptime count: usize, comptime parameter: usize) []const u8 { if (parameter > count) return ""; const separator = if (parameter == 1) "" else ", "; return separator ++ std.fmt.comptimePrint("?{d}", .{parameter}) ++ parameterList(count, parameter + 1);}fn assignmentList( comptime ColumnTag: type, comptime columns: []const ColumnTag, comptime prefix_count: usize, comptime index: usize,) []const u8 { if (index == columns.len) return ""; const separator = if (index == 0) "" else ", "; const parameter = prefix_count + index + 1; return separator ++ @tagName(columns[index]) ++ " = " ++ std.fmt.comptimePrint("?{d}", .{parameter}) ++ assignmentList(ColumnTag, columns, prefix_count, index + 1);}pub const Value = union(Storage) { nil, integer: i64, text: []const u8, blob: []const u8,};const Varint = struct { value: u64, len: usize,};pub const View = struct { bytes: []const u8, header_len: usize, header_start: usize, count: usize, pub fn init(bytes: []const u8) Error!View { const header = try readVarint(bytes); if (header.value > std.math.maxInt(usize)) return error.RowTooLarge; const header_len: usize = @intCast(header.value); if (header_len < header.len or header_len > bytes.len) return error.InvalidRow; var header_cursor = header.len; var body_len: usize = 0; var count: usize = 0; while (header_cursor < header_len) { const serial = try readVarint(bytes[header_cursor..header_len]); const size = try serialBodySize(serial.value); body_len = try checkedAdd(body_len, size); header_cursor += serial.len; count += 1; } if (header_cursor != header_len) return error.InvalidRow; if (body_len > bytes.len - header_len) return error.InvalidRow; return .{ .bytes = bytes, .header_len = header_len, .header_start = header.len, .count = count, }; } pub fn columnCount(self: View) usize { return self.count; } /// Returns a cursor that opens a traversal of this row at its first column, /// so a caller reading more than one column of the row walks the header /// once. The cursor carries the two offsets the traversal needs and calls /// no allocator. The cursor reads the row's borrowed bytes as it goes, so /// those bytes stay unchanged while the cursor is in use. pub fn cursor(self: View) Cursor { return .{ .view = self, .header_offset = self.header_start, .body_offset = self.header_len, }; } pub fn column(self: View, index: usize) Error!Value { var reader = self.cursor(); return reader.column(index); } pub fn project(self: View, indexes: []const usize, target: []Value) Error![]Value { if (target.len < indexes.len) return error.OutputTooSmall; var reader = self.cursor(); for (indexes, 0..) |index, ordinal| { target[ordinal] = try reader.column(index); } return target[0..indexes.len]; } pub fn compare(self: View, other: View, columns: []const Column) Error!std.math.Order { const count = @max(self.count, other.count); var left_cursor = self.cursor(); var right_cursor = other.cursor(); var index: usize = 0; while (index < count) : (index += 1) { const left: Value = if (index < self.count) try left_cursor.column(index) else .nil; const right: Value = if (index < other.count) try right_cursor.column(index) else .nil; const collation = if (index < columns.len) columns[index].collation else Collation.binary; const order = compareValues(left, right, collation); if (order != .eq) return order; } return .eq; }};/// A cursor is a traversal over one row's columns, of a size fixed at compile/// time. A caller holds one across more than one read of the same row. The/// cursor holds the row view, the offset it has reached in the header, the/// offset it has reached in the bodies, and the number of columns it has/// passed. Reading columns in increasing order walks forward once over each/// header field it has yet to see, while asking for a column at or before the/// one it has most recently returned sends the traversal back to the first/// column and walks forward again, which covers asking for the same column/// twice. The traversal returns `ColumnOutOfBounds` for an index beyond the/// row's column count.pub const Cursor = struct { view: View, header_offset: usize, body_offset: usize, ordinal: usize = 0, pub fn column(self: *Cursor, index: usize) Error!Value { std.debug.assert(self.ordinal <= self.view.count); if (index >= self.view.count) return error.ColumnOutOfBounds; if (index < self.ordinal) self.* = self.view.cursor(); while (self.ordinal <= index) { const serial = try readVarint(self.view.bytes[self.header_offset..self.view.header_len]); const size = try serialBodySize(serial.value); const start = self.body_offset; const end = try checkedAdd(start, size); if (end > self.view.bytes.len) return error.InvalidRow; self.header_offset += serial.len; self.body_offset = end; const ordinal = self.ordinal; self.ordinal += 1; if (ordinal == index) return try decode(serial.value, self.view.bytes[start..end]); } unreachable; }};pub fn encodedSize(values: []const Value) Error!usize { var serial_len: usize = 0; var body_len: usize = 0; for (values) |value| { const serial = try serialType(value); serial_len = try checkedAdd(serial_len, varintSize(serial)); body_len = try checkedAdd(body_len, try valueBodySize(value)); } const header_len = headerLength(serial_len); return try checkedAdd(header_len, body_len);}pub fn encode(target: []u8, values: []const Value) Error![]const u8 { const size = try encodedSize(values); if (target.len < size) return error.OutputTooSmall; var serial_len: usize = 0; for (values) |value| serial_len += varintSize(try serialType(value)); const header_len = headerLength(serial_len); var cursor = try writeVarint(target, @intCast(header_len)); for (values) |value| cursor += try writeVarint(target[cursor..], try serialType(value)); if (cursor != header_len) return error.InvalidRow; for (values) |value| { const written = try writeBody(target[cursor..size], value); cursor += written; } if (cursor != size) return error.InvalidRow; return target[0..size];}pub fn compare(left: []const u8, right: []const u8, columns: []const Column) Error!std.math.Order { return try (try View.init(left)).compare(try View.init(right), columns);}pub fn compareValues(left: Value, right: Value, collation: Collation) std.math.Order { const left_rank = storageRank(std.meta.activeTag(left)); const right_rank = storageRank(std.meta.activeTag(right)); if (left_rank < right_rank) return .lt; if (left_rank > right_rank) return .gt; return switch (left) { .nil => .eq, .integer => |left_integer| switch (right) { .integer => |right_integer| compareInteger(left_integer, right_integer), else => unreachable, }, .text => |left_text| switch (right) { .text => |right_text| compareText(left_text, right_text, collation), else => unreachable, }, .blob => |left_blob| switch (right) { .blob => |right_blob| simd.order(Bytes, left_blob, right_blob), else => unreachable, }, };}fn headerLength(serial_len: usize) usize { var header_len = serial_len + 1; while (true) { const next = serial_len + varintSize(@intCast(header_len)); if (next == header_len) return header_len; header_len = next; }}fn serialType(value: Value) Error!u64 { return switch (value) { .nil => 0, .integer => |integer| integerSerial(integer), .text => |text| try sizedSerial(text.len, 13), .blob => |blob| try sizedSerial(blob.len, 12), };}fn sizedSerial(len: usize, base: u64) Error!u64 { if (len > (std.math.maxInt(u64) - base) / 2) return error.RowTooLarge; return @as(u64, @intCast(len)) * 2 + base;}fn integerSerial(value: i64) u64 { if (value == 0) return 8; if (value == 1) return 9; if (value >= -128 and value <= 127) return 1; if (value >= std.math.minInt(i16) and value <= std.math.maxInt(i16)) return 2; if (value >= -8_388_608 and value <= 8_388_607) return 3; if (value >= std.math.minInt(i32) and value <= std.math.maxInt(i32)) return 4; if (value >= -140_737_488_355_328 and value <= 140_737_488_355_327) return 5; return 6;}fn valueBodySize(value: Value) Error!usize { return try serialBodySize(try serialType(value));}fn serialBodySize(serial: u64) Error!usize { return switch (serial) { 0, 8, 9 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 6, 6 => 8, 7, 10, 11 => error.InvalidRow, else => { if (serial < 12) return error.InvalidRow; const size = if (serial % 2 == 0) (serial - 12) / 2 else (serial - 13) / 2; if (size > std.math.maxInt(usize)) return error.RowTooLarge; return @intCast(size); }, };}fn writeBody(target: []u8, value: Value) Error!usize { const serial = try serialType(value); const size = try serialBodySize(serial); if (target.len < size) return error.OutputTooSmall; switch (value) { .nil => {}, .integer => |integer| writeInteger(target[0..size], integer), .text => |text| @memcpy(target[0..text.len], text), .blob => |blob| @memcpy(target[0..blob.len], blob), } return size;}fn decode(serial: u64, body: []const u8) Error!Value { return switch (serial) { 0 => .nil, 1, 2, 3, 4, 5, 6 => .{ .integer = readInteger(body) }, 8 => .{ .integer = 0 }, 9 => .{ .integer = 1 }, else => { if (serial < 12) return error.InvalidRow; if (serial % 2 == 0) return .{ .blob = body }; return .{ .text = body }; }, };}fn writeInteger(target: []u8, value: i64) void { var bytes: [8]u8 = undefined; std.mem.writeInt(i64, bytes[0..8], value, .big); @memcpy(target, bytes[8 - target.len ..]);}fn readInteger(bytes: []const u8) i64 { var target: [8]u8 = undefined; @memset(target[0..], if (bytes[0] & 0x80 != 0) 0xff else 0); @memcpy(target[8 - bytes.len ..], bytes); return std.mem.readInt(i64, target[0..8], .big);}fn varintSize(value: u64) usize { var remaining = value; var size: usize = 1; while (remaining > 0x7f) { remaining >>= 7; size += 1; } return size;}fn writeVarint(target: []u8, value: u64) Error!usize { const size = varintSize(value); if (target.len < size) return error.OutputTooSmall; var shift = (size - 1) * 7; var index: usize = 0; while (index < size) : (index += 1) { var byte: u8 = @intCast((value >> @intCast(shift)) & 0x7f); if (index + 1 < size) byte |= 0x80; target[index] = byte; if (shift >= 7) shift -= 7; } return size;}fn readVarint(bytes: []const u8) Error!Varint { if (bytes.len == 0) return error.InvalidRow; var value: u64 = 0; var index: usize = 0; while (index < bytes.len and index < 10) : (index += 1) { const payload: u64 = bytes[index] & 0x7f; if (value > (std.math.maxInt(u64) - payload) >> 7) return error.RowTooLarge; value = (value << 7) | payload; if (bytes[index] & 0x80 == 0) { return .{ .value = value, .len = index + 1 }; } } return error.InvalidRow;}fn checkedAdd(left: usize, right: usize) Error!usize { return std.math.add(usize, left, right) catch error.RowTooLarge;}fn storageRank(storage: Storage) u8 { return switch (storage) { .nil => 0, .integer => 1, .text => 2, .blob => 3, };}fn compareInteger(left: i64, right: i64) std.math.Order { if (left < right) return .lt; if (left > right) return .gt; return .eq;}fn compareText(left: []const u8, right: []const u8, collation: Collation) std.math.Order { return switch (collation) { .binary => simd.order(Bytes, left, right), .nocase => compareNoCase(left, right), .rtrim => simd.order(Bytes, trimRightSpaces(left), trimRightSpaces(right)), };}fn compareNoCase(left: []const u8, right: []const u8) std.math.Order { const min_len = @min(left.len, right.len); var index: usize = 0; while (index < min_len) : (index += 1) { const left_byte = asciiLower(left[index]); const right_byte = asciiLower(right[index]); if (left_byte < right_byte) return .lt; if (left_byte > right_byte) return .gt; } if (left.len < right.len) return .lt; if (left.len > right.len) return .gt; return .eq;}fn asciiLower(byte: u8) u8 { if (byte >= 'A' and byte <= 'Z') return byte + ('a' - 'A'); return byte;}fn trimRightSpaces(bytes: []const u8) []const u8 { var end = bytes.len; while (end > 0 and bytes[end - 1] == ' ') end -= 1; return bytes[0..end];}test "row encodes typed values and projections" { var bytes: [128]u8 = undefined; const encoded = try encode(&bytes, &.{ Value.nil, .{ .integer = -129 }, .{ .text = "Alpha" }, .{ .blob = &.{ 1, 2, 3 } }, }); const view = try View.init(encoded); try std.testing.expectEqual(@as(usize, 4), view.columnCount()); try std.testing.expectEqual(Storage.nil, std.meta.activeTag(try view.column(0))); try std.testing.expectEqual(@as(i64, -129), (try view.column(1)).integer); try std.testing.expectEqualStrings("Alpha", (try view.column(2)).text); try std.testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, (try view.column(3)).blob); var projected: [2]Value = undefined; const values = try view.project(&.{ 2, 1 }, &projected); try std.testing.expectEqualStrings("Alpha", values[0].text); try std.testing.expectEqual(@as(i64, -129), values[1].integer);}test "row cursor preserves forward backward repeated and rejected reads" { var bytes: [128]u8 = undefined; const encoded = try encode(&bytes, &.{ .{ .integer = -129 }, .{ .text = "borrowed" }, .nil, .{ .integer = 0x123456789 }, }); const view = try View.init(encoded); var reader = view.cursor(); for ([_]usize{ 0, 1, 2, 3, 3, 1, 0, 3 }) |index| { const expected = try view.column(index); const actual = try reader.column(index); try std.testing.expectEqual(std.math.Order.eq, compareValues(expected, actual, .binary)); } try std.testing.expectError(error.ColumnOutOfBounds, reader.column(4)); const borrowed = (try reader.column(1)).text; try std.testing.expectEqualStrings("borrowed", borrowed); try std.testing.expectEqual((try view.column(1)).text.ptr, borrowed.ptr); var output: [4]Value = undefined; const projected = try view.project(&.{ 3, 0, 3, 1 }, &output); try std.testing.expectEqual(@as(i64, 0x123456789), projected[0].integer); try std.testing.expectEqual(@as(i64, -129), projected[1].integer); try std.testing.expectEqual(projected[0].integer, projected[2].integer); try std.testing.expectEqual(borrowed.ptr, projected[3].text.ptr);}test "row cursor preserves noncanonical header prefixes and empty rows" { const view = try View.init(&.{ 0x80, 0x04, 0x08, 0x09 }); var reader = view.cursor(); try std.testing.expectEqual(@as(i64, 0), (try reader.column(0)).integer); try std.testing.expectEqual(@as(i64, 1), (try reader.column(1)).integer); var empty = (try View.init(&.{1})).cursor(); try std.testing.expectError(error.ColumnOutOfBounds, empty.column(0)); var output: [0]Value = .{}; try std.testing.expectEqual(@as(usize, 0), (try view.project(&.{}, &output)).len);}test "row comparison preserves trailing nil columns and the first unequal value" { var left_bytes: [128]u8 = undefined; var right_bytes: [128]u8 = undefined; const left = try encode(&left_bytes, &.{ .{ .integer = 10 }, .nil }); const right = try encode(&right_bytes, &.{.{ .integer = 10 }}); try std.testing.expectEqual(std.math.Order.eq, try compare(left, right, &.{})); const larger = try encode(&right_bytes, &.{ .{ .integer = 10 }, .nil, .{ .text = "a" } }); try std.testing.expectEqual(std.math.Order.lt, try compare(left, larger, &.{})); try std.testing.expectEqual(std.math.Order.gt, try compare(larger, left, &.{}));}test "row compares storage classes and collations" { try std.testing.expectEqual(std.math.Order.lt, compareValues(Value.nil, .{ .integer = -1 }, .binary)); try std.testing.expectEqual(std.math.Order.lt, compareValues(.{ .integer = 9 }, .{ .text = "0" }, .binary)); try std.testing.expectEqual(std.math.Order.lt, compareValues(.{ .text = "z" }, .{ .blob = "a" }, .binary)); try std.testing.expectEqual(std.math.Order.gt, compareValues(.{ .text = "a" }, .{ .text = "A" }, .binary)); try std.testing.expectEqual(std.math.Order.eq, compareValues(.{ .text = "a" }, .{ .text = "A" }, .nocase)); try std.testing.expectEqual(std.math.Order.eq, compareValues(.{ .text = "a " }, .{ .text = "a" }, .rtrim));}test "row comparison walks columns left to right" { var left_bytes: [128]u8 = undefined; var right_bytes: [128]u8 = undefined; const left = try encode(&left_bytes, &.{ .{ .integer = 10 }, .{ .text = "abc" }, }); const right = try encode(&right_bytes, &.{ .{ .integer = 10 }, .{ .text = "ABC" }, }); try std.testing.expectEqual(std.math.Order.gt, try compare(left, right, &.{ .{}, .{ .collation = .binary } })); try std.testing.expectEqual(std.math.Order.eq, try compare(left, right, &.{ .{}, .{ .collation = .nocase } }));}test "row rejects invalid and truncated data" { var bytes: [32]u8 = undefined; const encoded = try encode(&bytes, &.{.{ .text = "abcd" }}); try std.testing.expectError(error.InvalidRow, View.init(encoded[0 .. encoded.len - 1])); const view = try View.init(encoded); try std.testing.expectError(error.ColumnOutOfBounds, view.column(1)); var projected: [0]Value = .{}; try std.testing.expectError(error.OutputTooSmall, view.project(&.{0}, &projected));}test "row spec derives SQL text arity projections and indices" { const Record = Spec(enum { id, title, created_at }); const Summary = Record.Project(&.{ .title, .id }); try std.testing.expectEqualStrings("id, title, created_at", Record.columns); try std.testing.expectEqualStrings("?1, ?2, ?3, ?4", Record.parameters(1)); try std.testing.expectEqualStrings( "id = ?2, title = ?3, created_at = ?4", Record.assignments(1), ); try std.testing.expectEqual(@as(usize, 1), Record.index(.title)); try std.testing.expectEqual(@as(usize, 4), Record.at(3, .title)); try std.testing.expectEqual(@as(usize, 4), Record.atBase(3).at(.title)); try std.testing.expectEqual(@as(usize, 3), Record.parameter(1, .title)); try std.testing.expectEqual([2]usize{ 4, 2 }, Record.indices(2, &.{ .created_at, .id })); try std.testing.expectEqualStrings("title, id", Summary.columns); try std.testing.expectEqual(@as(usize, 1), Summary.index(.id)); var bytes: [128]u8 = undefined; const encoded = try encode(&bytes, &.{ .{ .integer = 7 }, .{ .text = "tiny-row" }, .{ .text = "Row specification" }, .{ .integer = 42 }, }); const view = try View.init(encoded); try std.testing.expect(Record.matches(view, 1)); try std.testing.expect(!Record.matches(view, 0));}Complete caller list for row.encode
26 direct callers.
lib.sql.src.catalog.copyDefinitions[function] — private source atlib/sql/src/catalog.zig:2651in nearest public ownertiny.sql.cataloglib.sql.src.catalog.copyOpenedDefinitions[function] — private source atlib/sql/src/catalog.zig:2677in nearest public ownertiny.sql.cataloglib.sql.src.catalog.encodeDefinitions[function] — private source atlib/sql/src/catalog.zig:2579in nearest public ownertiny.sql.cataloglib.sql.src.plan.test_prepared_relation_data_changes_preserve_captured_schema[function] — test source atlib/sql/src/plan.zig:703in nearest public ownertiny.sql.plantiny.sql.Relation.put[method] atlib/sql/src/relation.zig:289tiny.sql.relation.applyUpdate[function] atlib/sql/src/relation.zig:81lib.sql.src.relation.test_relation_applies_edit_batches_to_table_and_secondary_indexes[function] — test source atlib/sql/src/relation.zig:759in nearest public ownertiny.sql.relationlib.sql.src.relation.test_relation_applies_update_edits_within_batches[function] — test source atlib/sql/src/relation.zig:904in nearest public ownertiny.sql.relationlib.sql.src.relation.test_relation_rejects_an_index_key_larger_than_a_page_and_stays_writable[function] — test source atlib/sql/src/relation.zig:1028in nearest public ownertiny.sql.relationlib.sql.src.row.test_row_comparison_preserves_trailing_nil_columns_and_the_first_unequal_value[function] — test source atlib/sql/src/row.zig:748in nearest public ownertiny.sql.rowlib.sql.src.row.test_row_comparison_walks_columns_left_to_right[function] — test source atlib/sql/src/row.zig:768in nearest public ownertiny.sql.rowlib.sql.src.row.test_row_cursor_preserves_forward_backward_repeated_and_rejected_reads[function] — test source atlib/sql/src/row.zig:710in nearest public ownertiny.sql.rowlib.sql.src.row.test_row_encodes_typed_values_and_projections[function] — test source atlib/sql/src/row.zig:688in nearest public ownertiny.sql.rowlib.sql.src.row.test_row_rejects_invalid_and_truncated_data[function] — test source atlib/sql/src/row.zig:784in nearest public ownertiny.sql.rowlib.sql.src.row.test_row_spec_derives_SQL_text_arity_projections_and_indices[function] — test source atlib/sql/src/row.zig:794in nearest public ownertiny.sql.rowlib.sql.src.search.engine.Search.putTextIn[method] — private source atlib/sql/src/search/engine.zig:981in nearest public ownerlib.sql.src.search.enginelib.sql.src.session.relation.appendPutEdit[function] — private source atlib/sql/src/session/relation.zig:23in nearest public ownerlib.sql.src.session.relationlib.sql.src.session.staging.storage.Storage.appendPut[method] — private source atlib/sql/src/session/staging/storage.zig:319in nearest public ownerlib.sql.src.session.staging.storagelib.sql.src.statement.result.selectedIndexRow[function] — private source atlib/sql/src/statement/result.zig:287in nearest public ownerlib.sql.src.statement.resultlib.sql.src.statement.result.selectedIndexRowInto[function] — private source atlib/sql/src/statement/result.zig:313in nearest public ownerlib.sql.src.statement.resultlib.sql.src.statement.result.selectedRow[function] — private source atlib/sql/src/statement/result.zig:244in nearest public ownerlib.sql.src.statement.resultlib.sql.src.statement.result.selectedRowInto[function] — private source atlib/sql/src/statement/result.zig:264in nearest public ownerlib.sql.src.statement.resulttiny.sql.RowIdTable.putIn[method] atlib/sql/src/table.zig:295lib.sql.src.table.test_table_value_storage_is_sealed_before_inline_overflow_and_rejection_reads[function] — test source atlib/sql/src/table.zig:519in nearest public ownertiny.sql.tablelib.sql.src.version.test_index_root_from_rows_retains_exact_encoded_key_storage[function] — test source atlib/sql/src/version.zig:1943in nearest public ownertiny.sql.versionlib.sql.src.version.test_relation_value_applies_row_edits_from_an_immutable_base[function] — test source atlib/sql/src/version.zig:1846in nearest public ownertiny.sql.version
Complete caller list for row.encodedSize
15 direct callers.
tiny.sql.catalog.validateDefinition[function] atlib/sql/src/catalog.zig:1861tiny.sql.Relation.put[method] atlib/sql/src/relation.zig:289tiny.sql.relation.applyUpdate[function] atlib/sql/src/relation.zig:81lib.sql.src.relation.test_relation_rejects_an_index_key_larger_than_a_page_and_stays_writable[function] — test source atlib/sql/src/relation.zig:1028in nearest public ownertiny.sql.relationtiny.sql.row.encode[function] atlib/sql/src/row.zig:457lib.sql.src.search.engine.Search.putTextIn[method] — private source atlib/sql/src/search/engine.zig:981in nearest public ownerlib.sql.src.search.enginelib.sql.src.session.relation.appendPutEdit[function] — private source atlib/sql/src/session/relation.zig:23in nearest public ownerlib.sql.src.session.relationlib.sql.src.session.staging.storage.Storage.appendPut[method] — private source atlib/sql/src/session/staging/storage.zig:319in nearest public ownerlib.sql.src.session.staging.storagelib.sql.src.session.test.test_database_write_limits_bound_staged_edit_storage[function] — test source atlib/sql/src/session/test.zig:185in nearest public ownerlib.sql.src.session.testlib.sql.src.statement.execute.PendingEdit.addDemand[method] — private source atlib/sql/src/statement/execute.zig:1016in nearest public ownerlib.sql.src.statement.executelib.sql.src.statement.execute.test_prepared_statements_stage_bounded_writes_without_steady_allocation[function] — test source atlib/sql/src/statement/execute.zig:2270in nearest public ownerlib.sql.src.statement.executelib.sql.src.statement.result.selectedIndexRow[function] — private source atlib/sql/src/statement/result.zig:287in nearest public ownerlib.sql.src.statement.resultlib.sql.src.statement.result.selectedIndexRowInto[function] — private source atlib/sql/src/statement/result.zig:313in nearest public ownerlib.sql.src.statement.resultlib.sql.src.statement.result.selectedRow[function] — private source atlib/sql/src/statement/result.zig:244in nearest public ownerlib.sql.src.statement.resultlib.sql.src.statement.result.selectedRowInto[function] — private source atlib/sql/src/statement/result.zig:264in nearest public ownerlib.sql.src.statement.result
Audit
| Definitions | 8 |
|---|---|
| Public names | 8 |
| Members | 8 |
| Version | 26.7.0 |
| Revision | daab053ee433 |