feat: add TableDeleteArgs and decodeTableDelete to protocol

This commit is contained in:
dualfroz committed 2026-09-07 22:13:44 +02:00
1 parent 5d433ed37b
commit d09dc6aaf0
1 file changed
+159
+159
View File
@@ -2036,3 +2036,162 @@ pub fn encodeTableDelete(allocator: Allocator, table: []const u8, exprText: []co
return buf.toOwnedSlice();
}
pub const TableDeleteArgs = struct { table: []const u8, exprText: []const u8 };
/// `table` and `exprText` are borrowed slices into `payload`, the same
/// convention `decodeTableExplain`/`decodeTableUpdate` use.
pub fn decodeTableDelete(payload: []const u8) !TableDeleteArgs {
var c = Cursor{ .data = payload };
const table = try c.takeLenPrefixed();
const exprText = c.data[c.pos..];
return .{ .table = table, .exprText = exprText };
}
// --- table export / table import ---
/// Which interchange format a `table_export`/`table_import` request uses -
/// the wire counterpart of the CLI's `csv`/`json` string argument (see
/// `main.zig`'s local `table export`/`table import`), carried as one byte
/// instead of a length-prefixed string since there are only ever two.
pub const DumpFormat = enum(u8) {
csv = 0,
json = 1,
};
/// Wire request for a remote `table export`: a table name and which format
/// (`dump.zig`'s `exportCsvWriter`/`exportJsonWriter`) to render it in. No
/// predicate/paging - export always dumps the whole table, matching the
/// local CLI's `table export <table> csv|json <path>`.
pub fn encodeTableExport(allocator: Allocator, table: []const u8, format: DumpFormat) ![]u8 {
var buf = std.ArrayList(u8).init(allocator);
errdefer buf.deinit();
const w = encode.ByteWriter.init(&buf);
try putLenPrefixed(w, table);
try w.byte(@intFromEnum(format));
return buf.toOwnedSlice();
}
pub const TableExportArgs = struct { table: []const u8, format: DumpFormat };
pub fn decodeTableExport(payload: []const u8) !TableExportArgs {
var c = Cursor{ .data = payload };
const table = try c.takeLenPrefixed();
const formatByte = try c.takeByte();
const format = std.meta.intToEnum(DumpFormat, formatByte) catch return error.Malformed;
return .{ .table = table, .format = format };
}
/// Wire response for `table_export`: the row count `dump.zig`'s exporter
/// reported (so the CLI can print the same count the local `table export`
/// prints) followed by the rendered content bytes verbatim - a "count then
/// rest of the payload" shape, the same idea `exprText` fields elsewhere in
/// this file use for their own trailing variable-length piece.
pub fn encodeExportResult(allocator: Allocator, rowCount: u64, content: []const u8) ![]u8 {
var buf = std.ArrayList(u8).init(allocator);
errdefer buf.deinit();
const w = encode.ByteWriter.init(&buf);
try w.putU64(rowCount);
try w.slice(content);
return buf.toOwnedSlice();
}
pub const ExportResult = struct { rowCount: u64, content: []const u8 };
/// `content` is a borrowed slice into `payload`, the same convention
/// `decodeTableExplain`'s `exprText` uses - the caller decides whether to
/// copy it (`client.zig`'s `tableExport` dupes it into an allocation the
/// caller owns once the response frame itself is about to be freed).
pub fn decodeExportResult(payload: []const u8) !ExportResult {
var c = Cursor{ .data = payload };
const rowCount = try c.takeU64();
const content = payload[c.pos..];
return .{ .rowCount = rowCount, .content = content };
}
/// Wire request for a remote `table import`: a table name, which format the
/// following `content` bytes are encoded in, and the content itself.
/// `content` comes last (decoded as "everything left in the payload", the
/// same convention every `exprText` field in this file relies on) since it
/// is arbitrary-length and, unlike `table`, needs no length prefix of its
/// own.
pub fn encodeTableImport(allocator: Allocator, table: []const u8, format: DumpFormat, content: []const u8) ![]u8 {
var buf = std.ArrayList(u8).init(allocator);
errdefer buf.deinit();
const w = encode.ByteWriter.init(&buf);
try putLenPrefixed(w, table);
try w.byte(@intFromEnum(format));
try w.slice(content);
return buf.toOwnedSlice();
}
pub const TableImportArgs = struct { table: []const u8, format: DumpFormat, content: []const u8 };
/// `table` and `content` are borrowed slices into `payload`, the same
/// convention every other decode function in this file uses.
pub fn decodeTableImport(payload: []const u8) !TableImportArgs {
var c = Cursor{ .data = payload };
const table = try c.takeLenPrefixed();
const formatByte = try c.takeByte();
const format = std.meta.intToEnum(DumpFormat, formatByte) catch return error.Malformed;
const content = payload[c.pos..];
return .{ .table = table, .format = format, .content = content };
}
// --- stats ---
pub fn encodeStats(allocator: Allocator, stats: dbmod.Stats) ![]u8 {
var buf = std.ArrayList(u8).init(allocator);
errdefer buf.deinit();
const w = encode.ByteWriter.init(&buf);
try w.putU64(@intCast(stats.memtableEntries));
try w.putU64(@intCast(stats.memtableBytes));
try w.putU64(@intCast(stats.sstableCount));
try w.putU64(stats.liveEntries);
try w.putU64(stats.physicalBytes);
try w.putU64(stats.cacheHits);
try w.putU64(stats.cacheMisses);
try w.putU64(stats.nextSeq);
for (stats.levels) |level| {
try w.putU64(@intCast(level.tables));
try w.putU64(level.entries);
try w.putU64(level.physicalBytes);
}
return buf.toOwnedSlice();
}
pub fn decodeStats(payload: []const u8) !dbmod.Stats {
var c = Cursor{ .data = payload };
var stats = dbmod.Stats{};
stats.memtableEntries = @intCast(try c.takeU64());
stats.memtableBytes = @intCast(try c.takeU64());
stats.sstableCount = @intCast(try c.takeU64());
stats.liveEntries = try c.takeU64();
stats.physicalBytes = try c.takeU64();
stats.cacheHits = try c.takeU64();
stats.cacheMisses = try c.takeU64();
stats.nextSeq = try c.takeU64();
var i: usize = 0;
while (i < stats.levels.len) : (i += 1) {
stats.levels[i] = .{
.tables = @intCast(try c.takeU64()),
.entries = try c.takeU64(),
.physicalBytes = try c.takeU64(),
};
}
return stats;
}
test "frame round trip through an in-memory buffer" {
const allocator = std.testing.allocator;
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
try writeFrame(buf.writer(), @intFromEnum(Op.get), "hello");
var stream = std.io.fixedBufferStream(buf.items);
var frame = try readFrame(allocator, stream.reader());
defer frame.deinit();
try std.testing.expectEqual(@intFromEnum(Op.get), frame.tag);
try std.testing.expectEqualStrings("hello", frame.payload);
}