Skip to documentation
SLOP

tiny.sql.store

Reference tiny.sql store

Defined in tiny.sql.

API (7)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallsNo direct callersprivate sourcelib.sql.src.store.RangeskipKeyprivate sourcelib.sql.src.storeeqlKeystore.Rangenext
Static calls · unresolved targets: 0 · external targets: 3.

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

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

Source: lib/sql/src/store.zig

zig
const std = @import("std");const simd = @import("simd");const trace = @import("trace.zig");const Bytes = simd.ScalableTag(u8);const Allocator = std.mem.Allocator;const StoreError = error{    InvalidRange,    TransactionClosed,    TransactionConflict,};pub const Error = Allocator.Error || StoreError;pub const Entry = struct {    key: []const u8,    value: []const u8,    generation: u64,};const Version = struct {    key: []u8,    value: []u8,    generation: u64,    deleted: bool = false,};const Mutation = struct {    key: []u8,    value: []u8 = &.{},    deleted: bool = false,    fn deinit(self: *Mutation, allocator: Allocator) void {        freeOwned(allocator, self.key);        if (!self.deleted) freeOwned(allocator, self.value);        self.* = .{ .key = &.{}, .value = &.{}, .deleted = true };    }};pub const Store = struct {    allocator: Allocator,    versions: std.ArrayList(Version) = .empty,    generation: u64 = 0,    pub fn init(allocator: Allocator) Store {        return .{ .allocator = allocator };    }    pub fn deinit(self: *Store) void {        for (self.versions.items) |*version| {            freeOwned(self.allocator, version.key);            if (!version.deleted) freeOwned(self.allocator, version.value);        }        self.versions.deinit(self.allocator);        self.* = .{ .allocator = self.allocator };    }    pub fn currentGeneration(self: *const Store) u64 {        return self.generation;    }    pub fn beginRead(self: *const Store) Snapshot {        return .{ .store = self, .generation = self.generation };    }    pub fn beginWrite(self: *Store) Write {        return .{ .store = self, .snapshot_generation = self.generation };    }    pub fn get(self: *const Store, key: []const u8) ?[]const u8 {        const phase = trace.scope("store.get");        defer phase.end();        return valueFromIndex(self, latestVisibleIndex(self, key, self.generation));    }    pub fn getAt(self: *const Store, key: []const u8, generation: u64) ?[]const u8 {        return valueFromIndex(self, latestVisibleIndex(self, key, generation));    }    pub fn range(self: *const Store, start: ?[]const u8, end: ?[]const u8) Error!Range {        return rangeAt(self, start, end, self.generation);    }    pub fn rangeAt(self: *const Store, start: ?[]const u8, end: ?[]const u8, generation: u64) Error!Range {        if (start) |lower| {            if (end) |upper| {                if (simd.order(Bytes, lower, upper) == .gt) return error.InvalidRange;            }        }        return .{            .store = self,            .start = start,            .end = end,            .generation = generation,            .index = if (start) |lower| lowerBoundKey(self.versions.items, lower) else 0,        };    }    pub fn compact(self: *Store, oldest_visible_generation: u64) void {        const phase = trace.scope("store.compact");        defer phase.end();        var read_index: usize = 0;        var write_index: usize = 0;        while (read_index < self.versions.items.len) {            const key = self.versions.items[read_index].key;            var group_end = read_index;            var has_oldest_version = false;            while (group_end < self.versions.items.len and eqlKey(self.versions.items[group_end].key, key)) : (group_end += 1) {                const version = self.versions.items[group_end];                if (version.generation == oldest_visible_generation) has_oldest_version = true;            }            var keep_floor: ?usize = null;            var scan = read_index;            while (scan < group_end) : (scan += 1) {                const version = self.versions.items[scan];                if (version.generation < oldest_visible_generation) {                    if (keep_floor == null or self.versions.items[keep_floor.?].generation < version.generation) {                        keep_floor = scan;                    }                }            }            scan = read_index;            while (scan < group_end) : (scan += 1) {                const keep =                    (!has_oldest_version and keep_floor != null and scan == keep_floor.?) or                    self.versions.items[scan].generation >= oldest_visible_generation;                if (keep) {                    self.versions.items[write_index] = self.versions.items[scan];                    write_index += 1;                } else {                    freeOwned(self.allocator, self.versions.items[scan].key);                    if (!self.versions.items[scan].deleted) freeOwned(self.allocator, self.versions.items[scan].value);                }            }            read_index = group_end;        }        self.versions.shrinkRetainingCapacity(write_index);    }};pub const Snapshot = struct {    store: *const Store,    generation: u64,    pub fn get(self: Snapshot, key: []const u8) ?[]const u8 {        const phase = trace.scope("snapshot.get");        defer phase.end();        return self.store.getAt(key, self.generation);    }    pub fn range(self: Snapshot, start: ?[]const u8, end: ?[]const u8) Error!Range {        return self.store.rangeAt(start, end, self.generation);    }};pub const Write = struct {    store: *Store,    snapshot_generation: u64,    mutations: std.ArrayList(Mutation) = .empty,    closed: bool = false,    pub fn deinit(self: *Write) void {        for (self.mutations.items) |*mutation| mutation.deinit(self.store.allocator);        self.mutations.deinit(self.store.allocator);        self.closed = true;    }    pub fn get(self: *const Write, key: []const u8) Error!?[]const u8 {        if (self.closed) return error.TransactionClosed;        if (self.localMutation(key)) |mutation| {            if (mutation.deleted) return null;            return mutation.value;        }        return self.store.getAt(key, self.snapshot_generation);    }    pub fn put(self: *Write, key: []const u8, value: []const u8) Error!void {        if (self.closed) return error.TransactionClosed;        const phase = trace.scope("write.put");        defer phase.end();        try self.setMutation(key, value, false);    }    pub fn delete(self: *Write, key: []const u8) Error!void {        if (self.closed) return error.TransactionClosed;        const phase = trace.scope("write.delete");        defer phase.end();        try self.setMutation(key, &.{}, true);    }    pub fn commit(self: *Write) Error!u64 {        if (self.closed) return error.TransactionClosed;        const phase = trace.scope("write.commit");        defer phase.end();        if (self.mutations.items.len == 0) {            self.closed = true;            return self.store.generation;        }        for (self.mutations.items) |mutation| {            if (latestGenerationForKey(self.store, mutation.key)) |generation| {                if (generation > self.snapshot_generation) return error.TransactionConflict;            }        }        const next_generation = self.store.generation + 1;        try self.store.versions.ensureTotalCapacity(self.store.allocator, self.store.versions.items.len + self.mutations.items.len);        for (self.mutations.items) |mutation| {            const insert_index = lowerBoundVersion(self.store.versions.items, mutation.key, next_generation);            self.store.versions.insertAssumeCapacity(insert_index, .{                .key = mutation.key,                .value = mutation.value,                .generation = next_generation,                .deleted = mutation.deleted,            });        }        std.debug.assert(versionsAreOrdered(self.store.versions.items));        self.store.generation = next_generation;        self.mutations.clearRetainingCapacity();        self.closed = true;        trace.progress("write.commit.complete");        return next_generation;    }    fn setMutation(self: *Write, key: []const u8, value: []const u8, deleted: bool) Error!void {        if (self.localMutationIndex(key)) |index| {            var mutation = &self.mutations.items[index];            if (!mutation.deleted) freeOwned(self.store.allocator, mutation.value);            mutation.value = &.{};            if (!deleted) mutation.value = try self.store.allocator.dupe(u8, value);            mutation.deleted = deleted;            return;        }        const owned_key = try self.store.allocator.dupe(u8, key);        errdefer freeOwned(self.store.allocator, owned_key);        var owned_value: []u8 = &.{};        if (!deleted) owned_value = try self.store.allocator.dupe(u8, value);        errdefer if (!deleted) freeOwned(self.store.allocator, owned_value);        try self.mutations.append(self.store.allocator, .{            .key = owned_key,            .value = owned_value,            .deleted = deleted,        });        std.mem.sort(Mutation, self.mutations.items, {}, mutationLessThan);    }    fn localMutation(self: *const Write, key: []const u8) ?Mutation {        if (self.localMutationIndex(key)) |index| return self.mutations.items[index];        return null;    }    fn localMutationIndex(self: *const Write, key: []const u8) ?usize {        var low: usize = 0;        var high = self.mutations.items.len;        while (low < high) {            const mid = low + (high - low) / 2;            switch (simd.order(Bytes, self.mutations.items[mid].key, key)) {                .lt => low = mid + 1,                .eq => return mid,                .gt => high = mid,            }        }        return null;    }};pub const Range = struct {    store: *const Store,    start: ?[]const u8,    end: ?[]const u8,    generation: u64,    index: usize,    pub fn next(self: *Range) ?Entry {        const phase = trace.scope("range.next");        defer phase.end();        while (self.index < self.store.versions.items.len) {            const key = self.store.versions.items[self.index].key;            if (self.start) |lower| {                if (simd.order(Bytes, key, lower) == .lt) {                    self.skipKey(key);                    continue;                }            }            if (self.end) |upper| {                if (simd.order(Bytes, key, upper) != .lt) return null;            }            var best_index: ?usize = null;            while (self.index < self.store.versions.items.len and eqlKey(self.store.versions.items[self.index].key, key)) : (self.index += 1) {                const candidate = self.store.versions.items[self.index];                if (candidate.generation <= self.generation) {                    if (best_index == null or self.store.versions.items[best_index.?].generation < candidate.generation) {                        best_index = self.index;                    }                }            }            if (best_index) |index| {                const version = self.store.versions.items[index];                if (!version.deleted) {                    return .{                        .key = version.key,                        .value = version.value,                        .generation = version.generation,                    };                }            }        }        return null;    }    fn skipKey(self: *Range, key: []const u8) void {        while (self.index < self.store.versions.items.len and eqlKey(self.store.versions.items[self.index].key, key)) {            self.index += 1;        }    }};fn latestVisibleIndex(store: *const Store, key: []const u8, generation: u64) ?usize {    const start = lowerBoundKey(store.versions.items, key);    var index = start;    var best: ?usize = null;    while (index < store.versions.items.len and eqlKey(store.versions.items[index].key, key)) : (index += 1) {        const version = store.versions.items[index];        if (version.generation <= generation) {            if (best == null or store.versions.items[best.?].generation < version.generation) {                best = index;            }        }    }    return best;}fn valueFromIndex(store: *const Store, index: ?usize) ?[]const u8 {    const found = index orelse return null;    const version = store.versions.items[found];    if (version.deleted) return null;    return version.value;}fn latestGenerationForKey(self: *const Store, key: []const u8) ?u64 {    const start = lowerBoundKey(self.versions.items, key);    var index = start;    var latest: ?u64 = null;    while (index < self.versions.items.len and eqlKey(self.versions.items[index].key, key)) : (index += 1) {        latest = self.versions.items[index].generation;    }    return latest;}fn lowerBoundKey(versions: []const Version, key: []const u8) usize {    var low: usize = 0;    var high = versions.len;    while (low < high) {        const mid = low + (high - low) / 2;        switch (simd.order(Bytes, versions[mid].key, key)) {            .lt => low = mid + 1,            .eq, .gt => high = mid,        }    }    return low;}fn lowerBoundVersion(versions: []const Version, key: []const u8, generation: u64) usize {    var low: usize = 0;    var high = versions.len;    while (low < high) {        const mid = low + (high - low) / 2;        if (versionPrecedesKeyGeneration(versions[mid], key, generation)) {            low = mid + 1;        } else {            high = mid;        }    }    return low;}fn versionLessThan(_: void, left: Version, right: Version) bool {    return versionPrecedesKeyGeneration(left, right.key, right.generation);}fn versionPrecedesKeyGeneration(version: Version, key: []const u8, generation: u64) bool {    return switch (simd.order(Bytes, version.key, key)) {        .lt => true,        .gt => false,        .eq => version.generation < generation,    };}fn versionsAreOrdered(versions: []const Version) bool {    if (versions.len < 2) return true;    var index: usize = 1;    while (index < versions.len) : (index += 1) {        if (!versionLessThan({}, versions[index - 1], versions[index])) return false;    }    return true;}fn versionCount(store: *const Store, key: []const u8) usize {    const start = lowerBoundKey(store.versions.items, key);    var index = start;    var count: usize = 0;    while (index < store.versions.items.len and eqlKey(store.versions.items[index].key, key)) : (index += 1) {        count += 1;    }    return count;}fn hasVersion(store: *const Store, key: []const u8, generation: u64) bool {    const start = lowerBoundKey(store.versions.items, key);    var index = start;    while (index < store.versions.items.len and eqlKey(store.versions.items[index].key, key)) : (index += 1) {        if (store.versions.items[index].generation == generation) return true;    }    return false;}fn mutationLessThan(_: void, left: Mutation, right: Mutation) bool {    return switch (simd.order(Bytes, left.key, right.key)) {        .lt => true,        .gt => false,        .eq => false,    };}fn eqlKey(left: []const u8, right: []const u8) bool {    return std.mem.eql(u8, left, right);}fn freeOwned(allocator: Allocator, bytes: []u8) void {    if (bytes.len != 0) allocator.free(bytes);}fn putCommitted(store: *Store, key: []const u8, value: []const u8) !u64 {    var tx = store.beginWrite();    defer tx.deinit();    try tx.put(key, value);    return tx.commit();}fn deleteCommitted(store: *Store, key: []const u8) !u64 {    var tx = store.beginWrite();    defer tx.deinit();    try tx.delete(key);    return tx.commit();}test "committed writes are visible in key order" {    var store = Store.init(std.testing.allocator);    defer store.deinit();    _ = try putCommitted(&store, "b", "two");    _ = try putCommitted(&store, "a", "one");    try std.testing.expectEqualStrings("one", store.get("a").?);    try std.testing.expectEqualStrings("two", store.get("b").?);    var range = try store.range(null, null);    const first = range.next().?;    const second = range.next().?;    try std.testing.expectEqualStrings("a", first.key);    try std.testing.expectEqualStrings("b", second.key);    try std.testing.expect(range.next() == null);}test "snapshots preserve old values across later commits" {    var store = Store.init(std.testing.allocator);    defer store.deinit();    _ = try putCommitted(&store, "name", "first");    const snapshot = store.beginRead();    _ = try putCommitted(&store, "name", "second");    try std.testing.expectEqualStrings("first", snapshot.get("name").?);    try std.testing.expectEqualStrings("second", store.get("name").?);}test "delete creates a tombstone for new readers" {    var store = Store.init(std.testing.allocator);    defer store.deinit();    _ = try putCommitted(&store, "k", "v");    const snapshot = store.beginRead();    _ = try deleteCommitted(&store, "k");    try std.testing.expectEqualStrings("v", snapshot.get("k").?);    try std.testing.expect(store.get("k") == null);}test "write transactions detect same key conflicts" {    var store = Store.init(std.testing.allocator);    defer store.deinit();    var left = store.beginWrite();    defer left.deinit();    var right = store.beginWrite();    defer right.deinit();    try left.put("k", "left");    _ = try left.commit();    try right.put("k", "right");    try std.testing.expectError(error.TransactionConflict, right.commit());}test "write transactions allow disjoint commits from the same snapshot" {    var store = Store.init(std.testing.allocator);    defer store.deinit();    var left = store.beginWrite();    defer left.deinit();    var right = store.beginWrite();    defer right.deinit();    try left.put("a", "left");    try right.put("b", "right");    _ = try left.commit();    _ = try right.commit();    try std.testing.expectEqualStrings("left", store.get("a").?);    try std.testing.expectEqualStrings("right", store.get("b").?);}test "range scan honors half open bounds and tombstones" {    var store = Store.init(std.testing.allocator);    defer store.deinit();    _ = try putCommitted(&store, "a", "1");    _ = try putCommitted(&store, "b", "2");    _ = try putCommitted(&store, "c", "3");    _ = try deleteCommitted(&store, "b");    var range = try store.range("a", "c");    const first = range.next().?;    try std.testing.expectEqualStrings("a", first.key);    try std.testing.expect(range.next() == null);}test "multi key commits preserve canonical version order" {    var store = Store.init(std.testing.allocator);    defer store.deinit();    var seed = store.beginWrite();    defer seed.deinit();    try seed.put("b", "old-b");    try seed.put("d", "old-d");    _ = try seed.commit();    var tx = store.beginWrite();    defer tx.deinit();    try tx.put("c", "new-c");    try tx.put("a", "new-a");    try tx.put("b", "new-b");    _ = try tx.commit();    try std.testing.expect(versionsAreOrdered(store.versions.items));    try std.testing.expectEqual(@as(usize, 5), store.versions.items.len);    try std.testing.expectEqualStrings("a", store.versions.items[0].key);    try std.testing.expectEqual(@as(u64, 2), store.versions.items[0].generation);    try std.testing.expectEqualStrings("b", store.versions.items[1].key);    try std.testing.expectEqual(@as(u64, 1), store.versions.items[1].generation);    try std.testing.expectEqualStrings("b", store.versions.items[2].key);    try std.testing.expectEqual(@as(u64, 2), store.versions.items[2].generation);    try std.testing.expectEqualStrings("c", store.versions.items[3].key);    try std.testing.expectEqual(@as(u64, 2), store.versions.items[3].generation);    try std.testing.expectEqualStrings("d", store.versions.items[4].key);    try std.testing.expectEqual(@as(u64, 1), store.versions.items[4].generation);}test "compaction drops obsolete floor when oldest generation is present" {    var store = Store.init(std.testing.allocator);    defer store.deinit();    _ = try putCommitted(&store, "k", "v1");    const snapshot_generation = try putCommitted(&store, "k", "v2");    _ = try putCommitted(&store, "k", "v3");    store.compact(snapshot_generation);    try std.testing.expect(versionsAreOrdered(store.versions.items));    try std.testing.expectEqual(@as(usize, 2), versionCount(&store, "k"));    try std.testing.expect(!hasVersion(&store, "k", 1));    try std.testing.expectEqualStrings("v2", store.getAt("k", snapshot_generation).?);    try std.testing.expectEqualStrings("v3", store.get("k").?);}test "compaction keeps floor when oldest generation falls between versions" {    var store = Store.init(std.testing.allocator);    defer store.deinit();    _ = try putCommitted(&store, "k", "v1");    _ = try putCommitted(&store, "other", "advance");    _ = try putCommitted(&store, "k", "v3");    store.compact(2);    try std.testing.expect(versionsAreOrdered(store.versions.items));    try std.testing.expectEqual(@as(usize, 2), versionCount(&store, "k"));    try std.testing.expect(hasVersion(&store, "k", 1));    try std.testing.expect(hasVersion(&store, "k", 3));    try std.testing.expectEqualStrings("v1", store.getAt("k", 2).?);    try std.testing.expectEqualStrings("v3", store.get("k").?);}

Audit

Definitions3
Public names3
Members5
Version26.7.0
Revisiondaab053ee433