feat: add handleTableUpdate and handleTableDelete to server

This commit is contained in:
dualfroz committed 2026-09-11 16:04:05 +02:00
1 parent 9b3ffe3d4b
commit 018ecace14
1 file changed
+152
+152
View File
@@ -1699,3 +1699,155 @@ pub const Server = struct {
}; };
defer plan.deinit(self.allocator); defer plan.deinit(self.allocator);
const out = protocol.encodePlan(self.allocator, plan) catch |err| {
return writeErr(writer, .internal, @errorName(err));
};
defer self.allocator.free(out);
try protocol.writeFrame(writer, @intFromEnum(protocol.Status.ok), out);
}
/// Executes a `table_update` request: the network counterpart of the
/// CLI's local `table update <table> --set ... [expr...]`, running
/// `query.Table.updateWhere` against the server's `*Db`. The wire
/// `sets` field list arrives pre-encoded with `Row.encode` (the same
/// trick `decodeTxnOp`'s `fieldsBytes` uses), so it is decoded into a
/// real `Row` here before `updateWhere` ever sees it. The response is a
/// bare row count reusing `encodeTtlSweepCount`'s u64 wire shape - the
/// same "no new response type, an existing shape already fits"
/// precedent `coll_ttl_op` set for `ttl_op`'s result encoding.
fn handleTableUpdate(self: *Server, payload: []const u8, writer: anytype) !void {
const args = protocol.decodeTableUpdate(payload) catch {
return writeErr(writer, .bad_request, "malformed table_update payload");
};
var setsRow = rowmod.Row.decode(self.allocator, args.setsBytes) catch {
return writeErr(writer, .bad_request, "malformed table_update set fields");
};
defer setsRow.deinit();
var table = querymod.Table.open(self.db, self.allocator, args.table) catch |err| {
return writeErr(writer, .bad_request, @errorName(err));
};
defer table.deinit();
var parsed: ?exprmod.Parsed = null;
defer if (parsed) |*p| p.deinit();
if (args.exprText.len > 0) {
parsed = exprmod.parse(self.allocator, args.exprText) catch |err| {
return writeErr(writer, .bad_request, @errorName(err));
};
}
const pred: ?querymod.Predicate = if (parsed) |p| p.predicate else null;
const updated = table.updateWhere(pred, setsRow.fields.items) catch |err| {
return writeErr(writer, .internal, @errorName(err));
};
const out = protocol.encodeTtlSweepCount(self.allocator, @intCast(updated)) catch |err| {
return writeErr(writer, .internal, @errorName(err));
};
defer self.allocator.free(out);
try protocol.writeFrame(writer, @intFromEnum(protocol.Status.ok), out);
}
/// Executes a `table_delete` request: the network counterpart of the
/// CLI's local `table delete <table> [expr...]`, running
/// `query.Table.deleteWhere` against the server's `*Db`. Same wire shape
/// as `table_explain` (table + exprText, no `SET` clause) and same
/// response shape as `table_update` (a bare row count reusing
/// `encodeTtlSweepCount`'s u64 wire shape).
fn handleTableDelete(self: *Server, payload: []const u8, writer: anytype) !void {
const args = protocol.decodeTableDelete(payload) catch {
return writeErr(writer, .bad_request, "malformed table_delete payload");
};
var table = querymod.Table.open(self.db, self.allocator, args.table) catch |err| {
return writeErr(writer, .bad_request, @errorName(err));
};
defer table.deinit();
var parsed: ?exprmod.Parsed = null;
defer if (parsed) |*p| p.deinit();
if (args.exprText.len > 0) {
parsed = exprmod.parse(self.allocator, args.exprText) catch |err| {
return writeErr(writer, .bad_request, @errorName(err));
};
}
const pred: ?querymod.Predicate = if (parsed) |p| p.predicate else null;
const deleted = table.deleteWhere(pred) catch |err| {
return writeErr(writer, .internal, @errorName(err));
};
const out = protocol.encodeTtlSweepCount(self.allocator, @intCast(deleted)) catch |err| {
return writeErr(writer, .internal, @errorName(err));
};
defer self.allocator.free(out);
try protocol.writeFrame(writer, @intFromEnum(protocol.Status.ok), out);
}
/// Executes a `table_export` request: the network counterpart of the
/// CLI's local `table export <table> csv|json <path>`, rendering the
/// whole table through `dump.zig`'s `exportCsvWriter`/`exportJsonWriter`
/// (the same functions the local path calls, just pointed at an
/// in-memory `ArrayList` writer instead of a file, since there is no
/// filesystem path to write to on the server side of a request) and
/// sending the row count plus the rendered bytes back in one frame.
fn handleTableExport(self: *Server, payload: []const u8, writer: anytype) !void {
const args = protocol.decodeTableExport(payload) catch {
return writeErr(writer, .bad_request, "malformed table_export payload");
};
var table = querymod.Table.open(self.db, self.allocator, args.table) catch |err| {
return writeErr(writer, .bad_request, @errorName(err));
};
defer table.deinit();
var buf = std.ArrayList(u8).init(self.allocator);
defer buf.deinit();
const rowCount = switch (args.format) {
.csv => dumpmod.exportCsvWriter(self.allocator, &table, buf.writer()),
.json => dumpmod.exportJsonWriter(self.allocator, &table, buf.writer()),
} catch |err| {
return writeErr(writer, .internal, @errorName(err));
};
const out = protocol.encodeExportResult(self.allocator, @intCast(rowCount), buf.items) catch |err| {
return writeErr(writer, .internal, @errorName(err));
};
defer self.allocator.free(out);
try protocol.writeFrame(writer, @intFromEnum(protocol.Status.ok), out);
}
/// Executes a `table_import` request: the network counterpart of the
/// CLI's local `table import <table> csv|json <path>`, feeding the
/// wire-carried `content` bytes straight into `dump.zig`'s
/// `importCsvContent`/`importJsonContent` (the same functions the local
/// path calls after reading a file into memory - the client already did
/// that reading, so the server only ever sees bytes, never a path)
/// against the server's `*Db`. The response is a bare row count, the
/// same u64 wire shape `table_update`/`table_delete` already use.
fn handleTableImport(self: *Server, payload: []const u8, writer: anytype) !void {
const args = protocol.decodeTableImport(payload) catch {
return writeErr(writer, .bad_request, "malformed table_import payload");
};
var table = querymod.Table.open(self.db, self.allocator, args.table) catch |err| {
return writeErr(writer, .bad_request, @errorName(err));
};
defer table.deinit();
const imported = switch (args.format) {
.csv => dumpmod.importCsvContent(self.allocator, &table, args.content),
.json => dumpmod.importJsonContent(self.allocator, &table, args.content),
} catch |err| {
return writeErr(writer, .internal, @errorName(err));
};
const out = protocol.encodeTtlSweepCount(self.allocator, @intCast(imported)) catch |err| {
return writeErr(writer, .internal, @errorName(err));
};
defer self.allocator.free(out);
try protocol.writeFrame(writer, @intFromEnum(protocol.Status.ok), out);
}