Skip to documentation
SLOP

tiny.sql.row

Reference tiny.sql row

Defined in tiny.sql.

API (13)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.sqlrow
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callersRowViewcursorprivate sourcelib.sql.src.rowcheckedAddprivate sourcelib.sql.src.rowdecodeprivate sourcelib.sql.src.rowreadVarintprivate sourcelib.sql.src.rowserialBodySizerow.Cursorcolumn
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.sql.src.rowtest: row comparison preserves traili...test sourcelib.sql.src.rowtest: row comparison walks columns le...RowViewinitrowcompare
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.sql.src.relationvaluesEqualRowViewcomparetest sourcelib.sql.src.rowtest: row compares storage classes an...test sourcelib.sql.src.rowtest: row cursor preserves forward ba...private sourcelib.sql.src.statement.predicatevalueMatchesprivate sourcelib.sql.src.statement.resultsortedRowOrderprivate sourcelib.sql.src.rowcompareIntegerprivate sourcelib.sql.src.rowcompareTextprivate sourcelib.sql.src.rowstorageRankrowcompareValues
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsprivate sourcelib.sql.src.catalogcopyDefinitionsprivate sourcelib.sql.src.catalogcopyOpenedDefinitionsprivate sourcelib.sql.src.catalogencodeDefinitionstest sourcelib.sql.src.plantest: prepared relation data changes ...Relationput+21 morerowencodedSizeprivate sourcelib.sql.src.rowheaderLengthprivate sourcelib.sql.src.rowserialTypeprivate sourcelib.sql.src.rowvarintSizeprivate sourcelib.sql.src.rowwriteBodyprivate sourcelib.sql.src.rowwriteVarintrowencode
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallscatalogvalidateDefinitionRelationputrelationapplyUpdatetest sourcelib.sql.src.relationtest: relation rejects an index key l...rowencode+10 moreprivate sourcelib.sql.src.rowcheckedAddprivate sourcelib.sql.src.rowheaderLengthprivate sourcelib.sql.src.rowserialTypeprivate sourcelib.sql.src.rowvalueBodySizeprivate sourcelib.sql.src.rowvarintSizerowencodedSize
Static calls · unresolved targets: 0 · external targets: 0.

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.

Complete caller list for row.encodedSize

15 direct callers.

Audit

Definitions8
Public names8
Members8
Version26.7.0
Revisiondaab053ee433