feat(store): add write path, point reads, and flush

This commit is contained in:
dualfroz committed 2026-08-25 19:15:00 +02:00
1 parent 6be9d8b710
commit 57360650ba
1 file changed
+101
+101
View File
@@ -137,3 +137,104 @@ pub const Db = struct {
self.allocator.free(self.dirPath); self.allocator.free(self.dirPath);
} }
pub fn put(self: *Db, key: []const u8, value: []const u8) !void {
try self.write(.value, key, value);
}
pub fn delete(self: *Db, key: []const u8) !void {
try self.write(.tombstone, key, &.{});
}
fn write(self: *Db, kind: Kind, key: []const u8, value: []const u8) !void {
const seq = self.nextSeq;
try self.wal.append(kind, seq, key, value);
try self.wal.sync();
try self.memtable.upsert(kind, seq, key, value);
self.nextSeq += 1;
if (self.memtable.byteSize >= self.flushThreshold) try self.flush();
}
pub fn apply(self: *Db, batch: *WriteBatch) !void {
if (batch.ops.items.len == 0) return;
var records = try self.allocator.alloc(Record, batch.ops.items.len);
defer self.allocator.free(records);
for (batch.ops.items, 0..) |op, i| {
records[i] = .{ .key = op.key, .value = op.value, .seq = self.nextSeq + @as(u64, @intCast(i)), .kind = op.kind };
}
try self.wal.appendBatch(records);
try self.wal.sync();
for (records) |record| {
try self.memtable.upsert(record.kind, record.seq, record.key, record.value);
}
self.nextSeq += records.len;
if (self.memtable.byteSize >= self.flushThreshold) try self.flush();
}
pub fn get(self: *Db, allocator: Allocator, key: []const u8) !?[]u8 {
return self.getAsOf(allocator, key, std.math.maxInt(u64));
}
pub fn getAsOf(self: *Db, allocator: Allocator, key: []const u8, maxSeq: u64) !?[]u8 {
var bestSeq: ?u64 = null;
var bestKind: Kind = .value;
var owned: ?[]u8 = null;
errdefer if (owned) |v| allocator.free(v);
if (self.memtable.findAsOf(key, maxSeq)) |rec| {
bestSeq = rec.seq;
bestKind = rec.kind;
owned = try allocator.dupe(u8, rec.value);
}
for (self.sstables.items) |reader| {
const hit = try reader.findCachedAsOf(&self.blockCache, allocator, key, maxSeq) orelse continue;
if (bestSeq == null or hit.seq > bestSeq.?) {
if (owned) |v| allocator.free(v);
bestSeq = hit.seq;
bestKind = hit.kind;
owned = hit.value;
} else {
allocator.free(hit.value);
}
}
if (bestSeq == null) return null;
if (bestKind == .tombstone) {
if (owned) |v| allocator.free(v);
return null;
}
return owned;
}
pub fn snapshot(self: *Db) !Snapshot {
try self.flush();
return .{ .db = self, .seq = if (self.nextSeq == 0) 0 else self.nextSeq - 1 };
}
pub fn flush(self: *Db) !void {
if (self.memtable.count == 0) return;
const fileId = self.nextFileId;
var nameBuf: [64]u8 = undefined;
const name = try sstName(&nameBuf, fileId);
{
var builder = try Builder.init(self.allocator, self.dir, name, self.memtable.count);
defer builder.deinit();
var cur = self.memtable.cursor(null);
while (cur.valid()) : (cur.advance()) try builder.add(cur.record());
try builder.finish();
}
const reader = try self.allocator.create(Reader);
errdefer self.allocator.destroy(reader);
reader.* = try Reader.open(self.allocator, self.dir, name, fileId);
errdefer reader.deinit();
reader.level = 0;
try self.sstables.insert(0, reader);
self.nextFileId += 1;
self.memtable.clear();
try self.wal.reset();
try self.writeManifest();
std.posix.fsync(self.dir.fd) catch {};
}