feat: add handleTableAggregate to server
This commit is contained in:
1 parent
6cd6ec5574
commit
a7551a7923
1 file changed
+140
+140
@@ -1243,3 +1243,143 @@ pub const Server = struct {
|
||||
};
|
||||
defer right.deinit();
|
||||
|
||||
var leftParsed: ?exprmod.Parsed = null;
|
||||
defer if (leftParsed) |*p| p.deinit();
|
||||
if (args.leftExprText.len > 0) {
|
||||
leftParsed = exprmod.parse(self.allocator, args.leftExprText) catch |err| {
|
||||
return writeErr(writer, .bad_request, @errorName(err));
|
||||
};
|
||||
}
|
||||
const leftPred: ?querymod.Predicate = if (leftParsed) |p| p.predicate else null;
|
||||
|
||||
var rightParsed: ?exprmod.Parsed = null;
|
||||
defer if (rightParsed) |*p| p.deinit();
|
||||
if (args.rightExprText.len > 0) {
|
||||
rightParsed = exprmod.parse(self.allocator, args.rightExprText) catch |err| {
|
||||
return writeErr(writer, .bad_request, @errorName(err));
|
||||
};
|
||||
}
|
||||
const rightPred: ?querymod.Predicate = if (rightParsed) |p| p.predicate else null;
|
||||
|
||||
var pairs = std.ArrayList(protocol.JoinPair).init(self.allocator);
|
||||
defer {
|
||||
for (pairs.items) |*p| {
|
||||
if (p.left) |*l| l.deinit();
|
||||
if (p.right) |*r| r.deinit();
|
||||
}
|
||||
pairs.deinit();
|
||||
}
|
||||
|
||||
const page = querymod.Page{ .offset = args.offset, .limit = args.limit };
|
||||
|
||||
switch (args.mode) {
|
||||
.inner => {
|
||||
const joined = joinmod.innerJoin(self.allocator, &left, args.leftField, leftPred, &right, args.rightField, rightPred) catch |err| {
|
||||
return writeErr(writer, .internal, @errorName(err));
|
||||
};
|
||||
const paged = joinmod.applyPage(joinmod.JoinedRow, self.allocator, joined, page) catch |err| {
|
||||
return writeErr(writer, .internal, @errorName(err));
|
||||
};
|
||||
defer self.allocator.free(paged);
|
||||
for (paged) |p| try pairs.append(.{ .left = p.left, .right = p.right });
|
||||
},
|
||||
.left => {
|
||||
const joined = joinmod.leftOuterJoin(self.allocator, &left, args.leftField, leftPred, &right, args.rightField, rightPred) catch |err| {
|
||||
return writeErr(writer, .internal, @errorName(err));
|
||||
};
|
||||
const paged = joinmod.applyPage(joinmod.OuterJoinedRow, self.allocator, joined, page) catch |err| {
|
||||
return writeErr(writer, .internal, @errorName(err));
|
||||
};
|
||||
defer self.allocator.free(paged);
|
||||
for (paged) |p| try pairs.append(.{ .left = p.left, .right = p.right });
|
||||
},
|
||||
.right => {
|
||||
const joined = joinmod.rightOuterJoin(self.allocator, &left, args.leftField, leftPred, &right, args.rightField, rightPred) catch |err| {
|
||||
return writeErr(writer, .internal, @errorName(err));
|
||||
};
|
||||
const paged = joinmod.applyPage(joinmod.RightOuterJoinedRow, self.allocator, joined, page) catch |err| {
|
||||
return writeErr(writer, .internal, @errorName(err));
|
||||
};
|
||||
defer self.allocator.free(paged);
|
||||
for (paged) |p| try pairs.append(.{ .left = p.left, .right = p.right });
|
||||
},
|
||||
.full => {
|
||||
const joined = joinmod.fullOuterJoin(self.allocator, &left, args.leftField, leftPred, &right, args.rightField, rightPred) catch |err| {
|
||||
return writeErr(writer, .internal, @errorName(err));
|
||||
};
|
||||
const paged = joinmod.applyPage(joinmod.FullOuterJoinedRow, self.allocator, joined, page) catch |err| {
|
||||
return writeErr(writer, .internal, @errorName(err));
|
||||
};
|
||||
defer self.allocator.free(paged);
|
||||
for (paged) |p| try pairs.append(.{ .left = p.left, .right = p.right });
|
||||
},
|
||||
}
|
||||
|
||||
const out = protocol.encodeJoinedRows(self.allocator, pairs.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_aggregate` request: opens the named table over the
|
||||
/// same `*Db` the rest of the server serves, filters rows with the same
|
||||
/// `expr.zig` grammar `table_select`/`table_join` already use (empty
|
||||
/// text selects every row), then hands the filtered rows to
|
||||
/// `aggregate.zig` - `runGlobal` when `args.groupField` is empty (the
|
||||
/// network counterpart of the CLI's `*` sentinel for "no GROUP BY"),
|
||||
/// `run` otherwise. When `args.havingText` is non-empty, it is parsed
|
||||
/// with the same `expr.zig` grammar and applied AFTER aggregation but
|
||||
/// BEFORE paging - `globalMatchesHaving` on the `runGlobal` path (an
|
||||
/// excluded single result comes back as zero values, not an error) or
|
||||
/// `filterHaving` on the grouped path - then, on the grouped path only,
|
||||
/// a non-empty `args.orderBy` sorts the HAVING-narrowed groups via
|
||||
/// `aggregate.sortGroupsBy` (an unknown order field is `bad_request`,
|
||||
/// same treatment as a malformed HAVING expr) - then windowed by
|
||||
/// `args.offset`/`args.limit` through `aggregate.applyPageGroups` (0/0 =
|
||||
/// unlimited, ignored for the `runGlobal` path since that always
|
||||
/// produces at most one result row, same as `args.orderBy`). Two distinct wire shapes come back
|
||||
/// (`encodeAggGlobalResult` vs `encodeAggGroupRows`) because the client
|
||||
/// already knows which one it asked for (it is the one that chose
|
||||
/// `groupField`), so no extra discriminator byte is needed on the wire -
|
||||
/// the client just decodes with whichever matches its own request, the
|
||||
/// same reasoning `Client.tableAggregateGlobal`/`tableAggregateGroups`
|
||||
/// document. A bad table name, malformed expr, or a spec referencing a
|
||||
/// non-numeric field (`error.NonNumericField`/`error.Overflow` from
|
||||
/// `aggregate.zig`) is a client mistake (`bad_request`), not a server
|
||||
/// fault.
|
||||
fn handleTableAggregate(self: *Server, payload: []const u8, writer: anytype) !void {
|
||||
const args = protocol.decodeTableAggregate(self.allocator, payload) catch {
|
||||
return writeErr(writer, .bad_request, "malformed table_aggregate payload");
|
||||
};
|
||||
defer self.allocator.free(args.specs);
|
||||
defer self.allocator.free(args.orderBy);
|
||||
|
||||
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;
|
||||
|
||||
var havingParsed: ?exprmod.Parsed = null;
|
||||
defer if (havingParsed) |*p| p.deinit();
|
||||
if (args.havingText.len > 0) {
|
||||
havingParsed = exprmod.parse(self.allocator, args.havingText) catch |err| {
|
||||
return writeErr(writer, .bad_request, @errorName(err));
|
||||
};
|
||||
}
|
||||
const havingPred: ?querymod.Predicate = if (havingParsed) |p| p.predicate else null;
|
||||
|
||||
const rows = table.select(pred) catch |err| {
|
||||
return writeErr(writer, .internal, @errorName(err));
|
||||
};
|
||||
defer querymod.freeRows(self.allocator, rows);
|
||||
|
||||
Reference in new issue
Block a user