From 018ecace143e4a9066bf907aafd0a81a0bbc0ee1 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 16:04:05 +0200 Subject: [PATCH] feat: add handleTableUpdate and handleTableDelete to server --- src/server.zig | 152 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/src/server.zig b/src/server.zig index 8a30e44..935201d 100644 --- a/src/server.zig +++ b/src/server.zig @@ -1699,3 +1699,155 @@ pub const Server = struct { }; 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 --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
[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
csv|json `, 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
csv|json `, 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); + } +