Skip to documentation
SLOP

tiny.sql.index

Reference tiny.sql index

Defined in tiny.sql.

API (14)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallsNo direct callersprivate sourcelib.sql.src.ReaderlookupProjectiontracescopeindex.Readerlookup
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersprivate sourcelib.sql.src.ReaderlookupProjectiontracescopeindex.ReaderlookupPayloads
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callerstree.Readeropenindex.Readeropen
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsindex.Readerscanprivate sourcelib.sql.src.ReaderrangeProjectiontracescopeindex.Readerrange
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersindex.Readerrangetracescopeindex.Readerscan
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersprivate sourcelib.sql.src.ReaderrangeProjectiontracescopeindex.ReaderscanPayloads
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callerstracescopetree.Readersummarizeindex.Readersummarize
Static calls · unresolved targets: 0 · external targets: 1.

Source: lib/sql/src/index.zig

zig
const std = @import("std");const file = @import("file.zig");const key = @import("key.zig");const page = @import("page.zig");const row = @import("row.zig");const trace = @import("trace.zig");const tree = @import("tree.zig");const wal = @import("wal.zig");const Allocator = std.mem.Allocator;pub const Error = tree.Error || row.Error || key.Error;pub const Options = struct {    tree: tree.Options,    columns: []const row.Column = &.{},};pub const Entry = struct {    rowid: i64,    key: []const u8,    payload: []const u8 = "",};pub const Bound = struct {    values: []const row.Value,    inclusive: bool,};pub const Reader = struct {    entries: tree.Reader,    columns: []const row.Column,    pub fn open(snapshot: file.Snapshot, options: Options) Error!Reader {        return .{            .entries = try tree.Reader.open(snapshot, options.tree),            .columns = options.columns,        };    }    /// Starts a scan of the entries from `start` up to `end` in `target`.    pub fn scan(        self: *const Reader,        target: *Scan,        allocator: Allocator,        start: ?[]const row.Value,        end: ?[]const row.Value,    ) Error!void {        const phase = trace.scope("index.scan");        defer phase.end();        try self.range(            target,            allocator,            if (start) |values| .{ .values = values, .inclusive = true } else null,            if (end) |values| .{ .values = values, .inclusive = false } else null,        );    }    pub fn range(        self: *const Reader,        target: *Scan,        allocator: Allocator,        start: ?Bound,        end: ?Bound,    ) Error!void {        const phase = trace.scope("index.range");        defer phase.end();        try self.rangeProjection(target, allocator, start, end, .key);    }    pub fn scanPayloads(        self: *const Reader,        target: *Scan,        allocator: Allocator,        start: ?[]const row.Value,        end: ?[]const row.Value,    ) Error!void {        const phase = trace.scope("index.scan_payloads");        defer phase.end();        try self.rangeProjection(            target,            allocator,            if (start) |values| .{ .values = values, .inclusive = true } else null,            if (end) |values| .{ .values = values, .inclusive = false } else null,            .value,        );    }    pub fn lookup(        self: *const Reader,        target: *Scan,        allocator: Allocator,        prefix: []const row.Value,    ) Error!void {        const phase = trace.scope("index.lookup");        defer phase.end();        try self.lookupProjection(target, allocator, prefix, .key);    }    pub fn lookupPayloads(        self: *const Reader,        target: *Scan,        allocator: Allocator,        prefix: []const row.Value,    ) Error!void {        const phase = trace.scope("index.lookup_payloads");        defer phase.end();        try self.lookupProjection(target, allocator, prefix, .value);    }    pub fn summarize(self: *const Reader) Error!tree.Summary {        const phase = trace.scope("index.summarize");        defer phase.end();        return try self.entries.summarize();    }    fn rangeProjection(        self: *const Reader,        target: *Scan,        allocator: Allocator,        start: ?Bound,        end: ?Bound,        projection: tree.Projection,    ) Error!void {        var start_bytes: [page.size]u8 = undefined;        var start_end_bytes: [page.size]u8 = undefined;        var end_bytes: [page.size]u8 = undefined;        var end_end_bytes: [page.size]u8 = undefined;        const start_key = if (start) |bound| start_key: {            const prefix = try key.encodeIndexPrefix(&start_bytes, bound.values, self.columns);            break :start_key if (bound.inclusive)                prefix            else                try key.encodePrefixEnd(&start_end_bytes, prefix);        } else null;        const end_key = if (end) |bound| end_key: {            const prefix = try key.encodeIndexPrefix(&end_bytes, bound.values, self.columns);            break :end_key if (bound.inclusive)                try key.encodePrefixEnd(&end_end_bytes, prefix)            else                prefix;        } else null;        try self.entries.scan(&target.entries, allocator, start_key, end_key, projection);    }    fn lookupProjection(        self: *const Reader,        target: *Scan,        allocator: Allocator,        prefix: []const row.Value,        projection: tree.Projection,    ) Error!void {        var start_bytes: [page.size]u8 = undefined;        var end_bytes: [page.size]u8 = undefined;        const start_key = try key.encodeIndexPrefix(&start_bytes, prefix, self.columns);        const end_key = try key.encodePrefixEnd(&end_bytes, start_key);        try self.entries.scan(&target.entries, allocator, start_key, end_key, projection);    }};pub const Index = struct {    entries: tree.Tree,    columns: []const row.Column,    pub fn open(database: *file.Database, options: Options) Error!Index {        return .{            .entries = try tree.Tree.open(database, options.tree),            .columns = options.columns,        };    }    pub fn reader(self: *const Index, snapshot: file.Snapshot) Error!Reader {        return .{            .entries = try self.entries.reader(snapshot),            .columns = self.columns,        };    }    pub fn put(self: *Index, rowid: i64, values: []const row.Value, options: file.CommitOptions) Error!file.Commit {        const phase = trace.scope("index.put");        defer phase.end();        var write = try tree.Write.beginTree(&self.entries);        defer write.deinit();        try self.putPayloadIn(&write, rowid, values, "");        return try write.commit(options);    }    pub fn putIn(self: *Index, write: *tree.Write, rowid: i64, values: []const row.Value) Error!void {        try self.putPayloadIn(write, rowid, values, "");    }    pub fn putPayload(self: *Index, rowid: i64, values: []const row.Value, payload: []const u8, options: file.CommitOptions) Error!file.Commit {        const phase = trace.scope("index.put_payload");        defer phase.end();        var write = try tree.Write.beginTree(&self.entries);        defer write.deinit();        try self.putPayloadIn(&write, rowid, values, payload);        return try write.commit(options);    }    pub fn putPayloadIn(self: *Index, write: *tree.Write, rowid: i64, values: []const row.Value, payload: []const u8) Error!void {        var key_bytes: [page.size]u8 = undefined;        const encoded = key.encodeIndex(&key_bytes, values, self.columns, rowid) catch |err| switch (err) {            error.OutputTooSmall => return error.KeyTooLarge,            else => return err,        };        try write.put(&self.entries, encoded, payload);    }    pub fn delete(self: *Index, rowid: i64, values: []const row.Value, options: file.CommitOptions) Error!file.Commit {        const phase = trace.scope("index.delete");        defer phase.end();        var write = try tree.Write.beginTree(&self.entries);        defer write.deinit();        try self.deleteIn(&write, rowid, values);        return try write.commit(options);    }    pub fn deleteIn(self: *Index, write: *tree.Write, rowid: i64, values: []const row.Value) Error!void {        var key_bytes: [page.size]u8 = undefined;        const encoded = key.encodeIndex(&key_bytes, values, self.columns, rowid) catch |err| switch (err) {            error.OutputTooSmall => return error.KeyTooLarge,            else => return err,        };        try write.delete(&self.entries, encoded);    }    pub fn scan(        self: *const Index,        target: *Scan,        allocator: Allocator,        start: ?[]const row.Value,        end: ?[]const row.Value,    ) Error!void {        var read = try self.entries.database.beginRead();        defer read.deinit();        const opened = try self.reader(read.snapshot());        try opened.scan(target, allocator, start, end);    }    pub fn range(        self: *const Index,        target: *Scan,        allocator: Allocator,        start: ?Bound,        end: ?Bound,    ) Error!void {        var read = try self.entries.database.beginRead();        defer read.deinit();        const opened = try self.reader(read.snapshot());        try opened.range(target, allocator, start, end);    }    pub fn scanPayloads(        self: *const Index,        target: *Scan,        allocator: Allocator,        start: ?[]const row.Value,        end: ?[]const row.Value,    ) Error!void {        var read = try self.entries.database.beginRead();        defer read.deinit();        const opened = try self.reader(read.snapshot());        try opened.scanPayloads(target, allocator, start, end);    }    pub fn lookup(        self: *const Index,        target: *Scan,        allocator: Allocator,        prefix: []const row.Value,    ) Error!void {        var read = try self.entries.database.beginRead();        defer read.deinit();        const opened = try self.reader(read.snapshot());        try opened.lookup(target, allocator, prefix);    }    pub fn lookupPayloads(        self: *const Index,        target: *Scan,        allocator: Allocator,        prefix: []const row.Value,    ) Error!void {        var read = try self.entries.database.beginRead();        defer read.deinit();        const opened = try self.reader(read.snapshot());        try opened.lookupPayloads(target, allocator, prefix);    }    pub fn summarize(self: *const Index) Error!tree.Summary {        var read = try self.entries.database.beginRead();        defer read.deinit();        const opened = try self.reader(read.snapshot());        return try opened.summarize();    }    pub fn summarizeIn(self: *const Index, write: *const tree.Write) Error!tree.Summary {        const phase = trace.scope("index.summarize_in");        defer phase.end();        return try self.entries.summarizeIn(write);    }};pub const Scan = struct {    entries: tree.Scan,    pub fn deinit(self: *Scan) void {        self.entries.deinit();        self.* = undefined;    }    pub fn next(self: *Scan) Error!?Entry {        if (try self.entries.next()) |entry| {            return .{                .rowid = try key.decodeIndexRowId(entry.key),                .key = entry.key,                .payload = entry.bytes,            };        }        return null;    }};test "secondary index stores duplicate values in rowid order" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    {        var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{            .paths = .{ .database = "index.db", .wal = "index.wal" },            .header = testingHeader(),        });        defer database.deinit();        try database.reserve(.{ .wal_frames = 96 });        var index = try Index.open(&database, .{ .tree = .{ .meta_page = 3, .root_page = 4 } });        _ = try index.put(3, &.{.{ .text = "orange" }}, .{ .durability = .buffered });        _ = try index.put(2, &.{.{ .text = "apple" }}, .{ .durability = .buffered });        _ = try index.put(-1, &.{.{ .text = "apple" }}, .{ .durability = .buffered });        _ = try index.put(7, &.{.{ .text = "pear" }}, .{ .durability = .buffered });        try database.syncWal();    }    var reopened = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "index.db", .wal = "index.wal" },        .header = recoveredHeader(),    });    defer reopened.deinit();    var index = try Index.open(&reopened, .{ .tree = .{ .meta_page = 3, .root_page = 4 } });    var scan: Scan = undefined;    try index.scan(&scan, std.testing.allocator, null, null);    defer scan.deinit();    try std.testing.expectEqual(@as(i64, -1), (try scan.next()).?.rowid);    try std.testing.expectEqual(@as(i64, 2), (try scan.next()).?.rowid);    try std.testing.expectEqual(@as(i64, 3), (try scan.next()).?.rowid);    try std.testing.expectEqual(@as(i64, 7), (try scan.next()).?.rowid);    try std.testing.expect(try scan.next() == null);    var lookup: Scan = undefined;    try index.lookup(&lookup, std.testing.allocator, &.{.{ .text = "apple" }});    defer lookup.deinit();    try std.testing.expectEqual(@as(i64, -1), (try lookup.next()).?.rowid);    try std.testing.expectEqual(@as(i64, 2), (try lookup.next()).?.rowid);    try std.testing.expect(try lookup.next() == null);    _ = try index.delete(-1, &.{.{ .text = "apple" }}, .{ .durability = .buffered });    var remaining: Scan = undefined;    try index.lookup(&remaining, std.testing.allocator, &.{.{ .text = "apple" }});    defer remaining.deinit();    try std.testing.expectEqual(@as(i64, 2), (try remaining.next()).?.rowid);    try std.testing.expect(try remaining.next() == null);}test "secondary index stores payloads in rowid order" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "index.db", .wal = "index.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 96 });    var index = try Index.open(&database, .{ .tree = .{ .meta_page = 3, .root_page = 4 } });    _ = try index.putPayload(4, &.{.{ .text = "apple" }}, "four", .{ .durability = .buffered });    _ = try index.putPayload(1, &.{.{ .text = "apple" }}, "one", .{ .durability = .buffered });    _ = try index.putPayload(3, &.{.{ .text = "orange" }}, "three", .{ .durability = .buffered });    var lookup: Scan = undefined;    try index.lookupPayloads(&lookup, std.testing.allocator, &.{.{ .text = "apple" }});    defer lookup.deinit();    const first = (try lookup.next()).?;    try std.testing.expectEqual(@as(i64, 1), first.rowid);    try std.testing.expectEqualStrings("one", first.payload);    const second = (try lookup.next()).?;    try std.testing.expectEqual(@as(i64, 4), second.rowid);    try std.testing.expectEqualStrings("four", second.payload);    try std.testing.expect(try lookup.next() == null);    var key_lookup: Scan = undefined;    try index.lookup(&key_lookup, std.testing.allocator, &.{.{ .text = "apple" }});    defer key_lookup.deinit();    try std.testing.expectEqual(@as(usize, 0), (try key_lookup.next()).?.payload.len);}test "secondary index range scan uses encoded value bounds" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "index.db", .wal = "index.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 96 });    var index = try Index.open(&database, .{ .tree = .{ .meta_page = 3, .root_page = 4 } });    _ = try index.put(10, &.{.{ .integer = -3 }}, .{ .durability = .buffered });    _ = try index.put(11, &.{.{ .integer = 0 }}, .{ .durability = .buffered });    _ = try index.put(12, &.{.{ .integer = 1 }}, .{ .durability = .buffered });    _ = try index.put(13, &.{.{ .integer = 3 }}, .{ .durability = .buffered });    var scan: Scan = undefined;    try index.scan(&scan, std.testing.allocator, &.{.{ .integer = 0 }}, &.{.{ .integer = 3 }});    defer scan.deinit();    try std.testing.expectEqual(@as(i64, 11), (try scan.next()).?.rowid);    try std.testing.expectEqual(@as(i64, 12), (try scan.next()).?.rowid);    try std.testing.expect(try scan.next() == null);    var inclusive: Scan = undefined;    try index.range(        &inclusive,        std.testing.allocator,        .{ .values = &.{.{ .integer = 0 }}, .inclusive = true },        .{ .values = &.{.{ .integer = 3 }}, .inclusive = true },    );    defer inclusive.deinit();    try std.testing.expectEqual(@as(i64, 11), (try inclusive.next()).?.rowid);    try std.testing.expectEqual(@as(i64, 12), (try inclusive.next()).?.rowid);    try std.testing.expectEqual(@as(i64, 13), (try inclusive.next()).?.rowid);    try std.testing.expect(try inclusive.next() == null);    var exclusive_start: Scan = undefined;    try index.range(        &exclusive_start,        std.testing.allocator,        .{ .values = &.{.{ .integer = 0 }}, .inclusive = false },        null,    );    defer exclusive_start.deinit();    try std.testing.expectEqual(@as(i64, 12), (try exclusive_start.next()).?.rowid);    try std.testing.expectEqual(@as(i64, 13), (try exclusive_start.next()).?.rowid);    try std.testing.expect(try exclusive_start.next() == null);}test "secondary index honors configured text collation" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "index.db", .wal = "index.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 96 });    const columns = [_]row.Column{.{ .collation = .nocase }};    var index = try Index.open(&database, .{        .tree = .{ .meta_page = 3, .root_page = 4 },        .columns = &columns,    });    _ = try index.put(2, &.{.{ .text = "Alpha" }}, .{ .durability = .buffered });    _ = try index.put(1, &.{.{ .text = "alpha" }}, .{ .durability = .buffered });    var lookup: Scan = undefined;    try index.lookup(&lookup, std.testing.allocator, &.{.{ .text = "ALPHA" }});    defer lookup.deinit();    try std.testing.expectEqual(@as(i64, 1), (try lookup.next()).?.rowid);    try std.testing.expectEqual(@as(i64, 2), (try lookup.next()).?.rowid);    try std.testing.expect(try lookup.next() == null);}fn testingHeader() wal.Header {    return .{        .sequence = 801,        .salt = .{ .first = 0x7171_9191, .second = 0x5353_6363 },    };}fn recoveredHeader() wal.Header {    return .{        .sequence = 802,        .salt = .{ .first = 0x8888_aaaa, .second = 0xbbbb_9999 },    };}

Source: lib/sql/src/root.zig:29

zig
pub const index = @import("index.zig");

Audit

Definitions12
Public names12
Members6
Version26.7.0
Revisiondaab053ee433