diff --git a/src/db.zig b/src/db.zig index 5e825a7..f5d8fa5 100644 --- a/src/db.zig +++ b/src/db.zig @@ -137,3 +137,104 @@ pub const Db = struct { 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 {}; + } +