test(store): add point, flush, recovery, and batch tests
This commit is contained in:
1 parent
c94bc91946
commit
ffc4fadc47
1 file changed
+263
+263
@@ -885,3 +885,266 @@ fn parseSstId(name: []const u8) ?u64 {
|
||||
return std.fmt.parseInt(u64, digits, 10) catch null;
|
||||
}
|
||||
|
||||
fn openTemp(tmp: *std.testing.TmpDir, allocator: Allocator, options: Options) !Db {
|
||||
var pathBuf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const base = try tmp.dir.realpath(".", &pathBuf);
|
||||
var dbPathBuf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const dbPath = try std.fmt.bufPrint(&dbPathBuf, "{s}/store", .{base});
|
||||
return Db.open(allocator, dbPath, options);
|
||||
}
|
||||
|
||||
test "put get delete" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
var db = try openTemp(&tmp, allocator, .{});
|
||||
defer db.close();
|
||||
|
||||
try db.put("apple", "red");
|
||||
try db.put("banana", "yellow");
|
||||
|
||||
const apple = (try db.get(allocator, "apple")).?;
|
||||
defer allocator.free(apple);
|
||||
try std.testing.expectEqualStrings("red", apple);
|
||||
|
||||
try db.put("apple", "green");
|
||||
const apple2 = (try db.get(allocator, "apple")).?;
|
||||
defer allocator.free(apple2);
|
||||
try std.testing.expectEqualStrings("green", apple2);
|
||||
|
||||
try db.delete("apple");
|
||||
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "apple"));
|
||||
|
||||
const banana = (try db.get(allocator, "banana")).?;
|
||||
defer allocator.free(banana);
|
||||
try std.testing.expectEqualStrings("yellow", banana);
|
||||
}
|
||||
|
||||
test "wal recovery after reopen" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
{
|
||||
var db = try openTemp(&tmp, allocator, .{});
|
||||
defer db.close();
|
||||
try db.put("city", "warsaw");
|
||||
try db.put("river", "vistula");
|
||||
try db.delete("city");
|
||||
try std.testing.expectEqual(@as(usize, 0), db.sstables.items.len);
|
||||
}
|
||||
|
||||
var db = try openTemp(&tmp, allocator, .{});
|
||||
defer db.close();
|
||||
try std.testing.expectEqual(@as(usize, 0), db.sstables.items.len);
|
||||
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "city"));
|
||||
const river = (try db.get(allocator, "river")).?;
|
||||
defer allocator.free(river);
|
||||
try std.testing.expectEqualStrings("vistula", river);
|
||||
}
|
||||
|
||||
test "flush to sstable and reopen" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
{
|
||||
var db = try openTemp(&tmp, allocator, .{ .flushThreshold = 64 });
|
||||
defer db.close();
|
||||
var buf: [16]u8 = undefined;
|
||||
var i: usize = 0;
|
||||
while (i < 200) : (i += 1) {
|
||||
const key = try std.fmt.bufPrint(&buf, "k{d:0>5}", .{i});
|
||||
try db.put(key, "value-payload");
|
||||
}
|
||||
try std.testing.expect(db.sstables.items.len > 0);
|
||||
}
|
||||
|
||||
var db = try openTemp(&tmp, allocator, .{ .flushThreshold = 64 });
|
||||
defer db.close();
|
||||
var buf: [16]u8 = undefined;
|
||||
var i: usize = 0;
|
||||
while (i < 200) : (i += 1) {
|
||||
const key = try std.fmt.bufPrint(&buf, "k{d:0>5}", .{i});
|
||||
const got = (try db.get(allocator, key)).?;
|
||||
defer allocator.free(got);
|
||||
try std.testing.expectEqualStrings("value-payload", got);
|
||||
}
|
||||
}
|
||||
|
||||
test "compaction dedups and drops tombstones" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
var db = try openTemp(&tmp, allocator, .{ .flushThreshold = 1 });
|
||||
defer db.close();
|
||||
|
||||
try db.put("a", "1");
|
||||
try db.put("b", "2");
|
||||
try db.put("c", "3");
|
||||
try db.put("d", "4");
|
||||
try db.put("a", "11");
|
||||
try db.delete("c");
|
||||
try db.flush();
|
||||
|
||||
try std.testing.expect(db.sstables.items.len > 1);
|
||||
try db.compact();
|
||||
try std.testing.expectEqual(@as(usize, 1), db.sstables.items.len);
|
||||
|
||||
const a = (try db.get(allocator, "a")).?;
|
||||
defer allocator.free(a);
|
||||
try std.testing.expectEqualStrings("11", a);
|
||||
|
||||
const b = (try db.get(allocator, "b")).?;
|
||||
defer allocator.free(b);
|
||||
try std.testing.expectEqualStrings("2", b);
|
||||
|
||||
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "c"));
|
||||
|
||||
const d = (try db.get(allocator, "d")).?;
|
||||
defer allocator.free(d);
|
||||
try std.testing.expectEqualStrings("4", d);
|
||||
}
|
||||
|
||||
test "scan merges memtable and sstable with tombstones" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
var db = try openTemp(&tmp, allocator, .{});
|
||||
defer db.close();
|
||||
|
||||
try db.put("a", "1");
|
||||
try db.put("b", "2");
|
||||
try db.put("c", "3");
|
||||
try db.put("d", "4");
|
||||
try db.put("e", "5");
|
||||
try db.flush();
|
||||
|
||||
try db.put("c", "cNew");
|
||||
try db.delete("b");
|
||||
|
||||
var iter = try db.scan(allocator, null, null);
|
||||
defer iter.deinit();
|
||||
|
||||
const expectedKeys = [_][]const u8{ "a", "c", "d", "e" };
|
||||
const expectedVals = [_][]const u8{ "1", "cNew", "4", "5" };
|
||||
var idx: usize = 0;
|
||||
while (try iter.next()) |item| {
|
||||
try std.testing.expect(idx < expectedKeys.len);
|
||||
try std.testing.expectEqualStrings(expectedKeys[idx], item.key);
|
||||
try std.testing.expectEqualStrings(expectedVals[idx], item.value);
|
||||
idx += 1;
|
||||
}
|
||||
try std.testing.expectEqual(expectedKeys.len, idx);
|
||||
}
|
||||
|
||||
test "scan honors range bounds" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
var db = try openTemp(&tmp, allocator, .{ .flushThreshold = 32 });
|
||||
defer db.close();
|
||||
|
||||
var keyBuf: [8]u8 = undefined;
|
||||
var i: usize = 0;
|
||||
while (i < 26) : (i += 1) {
|
||||
const key = try std.fmt.bufPrint(&keyBuf, "{c}", .{@as(u8, 'a') + @as(u8, @intCast(i))});
|
||||
try db.put(key, "x");
|
||||
}
|
||||
|
||||
var iter = try db.scan(allocator, "d", "h");
|
||||
defer iter.deinit();
|
||||
const expected = [_][]const u8{ "d", "e", "f", "g" };
|
||||
var idx: usize = 0;
|
||||
while (try iter.next()) |item| {
|
||||
try std.testing.expect(idx < expected.len);
|
||||
try std.testing.expectEqualStrings(expected[idx], item.key);
|
||||
idx += 1;
|
||||
}
|
||||
try std.testing.expectEqual(expected.len, idx);
|
||||
}
|
||||
|
||||
test "snapshot reads pre-overwrite value" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
var db = try openTemp(&tmp, allocator, .{});
|
||||
defer db.close();
|
||||
|
||||
try db.put("color", "red");
|
||||
try db.put("shape", "circle");
|
||||
|
||||
var snap = try db.snapshot();
|
||||
|
||||
try db.put("color", "green");
|
||||
try db.delete("shape");
|
||||
try db.put("color", "blue");
|
||||
|
||||
const snapColor = (try snap.get(allocator, "color")).?;
|
||||
defer allocator.free(snapColor);
|
||||
try std.testing.expectEqualStrings("red", snapColor);
|
||||
|
||||
const snapShape = (try snap.get(allocator, "shape")).?;
|
||||
defer allocator.free(snapShape);
|
||||
try std.testing.expectEqualStrings("circle", snapShape);
|
||||
|
||||
const liveColor = (try db.get(allocator, "color")).?;
|
||||
defer allocator.free(liveColor);
|
||||
try std.testing.expectEqualStrings("blue", liveColor);
|
||||
|
||||
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "shape"));
|
||||
|
||||
var snapIter = try snap.scan(allocator, null, null);
|
||||
defer snapIter.deinit();
|
||||
var snapCount: usize = 0;
|
||||
while (try snapIter.next()) |entry| {
|
||||
if (std.mem.eql(u8, entry.key, "color")) try std.testing.expectEqualStrings("red", entry.value);
|
||||
snapCount += 1;
|
||||
}
|
||||
try std.testing.expectEqual(@as(usize, 2), snapCount);
|
||||
}
|
||||
|
||||
test "write batch applies all or nothing" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
{
|
||||
var db = try openTemp(&tmp, allocator, .{});
|
||||
defer db.close();
|
||||
|
||||
try db.put("existing", "keep");
|
||||
const seqBefore = db.nextSeq;
|
||||
|
||||
var batch = WriteBatch.init(allocator);
|
||||
defer batch.deinit();
|
||||
try batch.put("one", "1");
|
||||
try batch.put("two", "2");
|
||||
try batch.put("three", "3");
|
||||
try batch.delete("existing");
|
||||
try db.apply(&batch);
|
||||
|
||||
try std.testing.expectEqual(seqBefore + 4, db.nextSeq);
|
||||
|
||||
const one = (try db.get(allocator, "one")).?;
|
||||
defer allocator.free(one);
|
||||
try std.testing.expectEqualStrings("1", one);
|
||||
const three = (try db.get(allocator, "three")).?;
|
||||
defer allocator.free(three);
|
||||
try std.testing.expectEqualStrings("3", three);
|
||||
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "existing"));
|
||||
}
|
||||
|
||||
var db = try openTemp(&tmp, allocator, .{});
|
||||
defer db.close();
|
||||
const two = (try db.get(allocator, "two")).?;
|
||||
defer allocator.free(two);
|
||||
try std.testing.expectEqualStrings("2", two);
|
||||
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "existing"));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user