feat: add seedAggregateOrders to server
This commit is contained in:
1 parent
977e1b0363
commit
28fa271324
1 file changed
+154
+154
@@ -3747,3 +3747,157 @@ test "server table_select orders a remote result by a field before windowing it,
|
||||
try std.testing.expectEqual(@as(i64, 1), asc[4].get(querymod.pkField).?.int);
|
||||
try std.testing.expectEqual(@as(i64, 50), asc[4].get("age").?.int);
|
||||
|
||||
// Descending, windowed to the middle page (offset 1, limit 2). Age
|
||||
// decreases as pk increases here, so descending-by-age happens to land
|
||||
// on the same row order as ascending pk (pk1..5 = ages 50,40,30,20,10)
|
||||
// - the offset/limit window (pk 2, pk 3) is still only correct if the
|
||||
// full 5-row set was sorted first and THEN windowed, not windowed off
|
||||
// the raw scan and left unsorted (which would coincidentally return
|
||||
// the same two rows here, but in the ascending-age windowed case below
|
||||
// would not).
|
||||
const midDesc = try client.tableSelect(allocator, "users", "", 1, 2, &.{.{ .field = "age", .descending = true }});
|
||||
defer protocol.freeRows(allocator, midDesc);
|
||||
try std.testing.expectEqual(@as(usize, 2), midDesc.len);
|
||||
try std.testing.expectEqual(@as(i64, 2), midDesc[0].get(querymod.pkField).?.int);
|
||||
try std.testing.expectEqual(@as(i64, 40), midDesc[0].get("age").?.int);
|
||||
try std.testing.expectEqual(@as(i64, 3), midDesc[1].get(querymod.pkField).?.int);
|
||||
try std.testing.expectEqual(@as(i64, 30), midDesc[1].get("age").?.int);
|
||||
|
||||
// Ascending, same window (offset 1, limit 2): ascending-by-age order is
|
||||
// pk 5,4,3,2,1 (ages 10,20,30,40,50), so offset 1..3 is pk 4 then pk 3
|
||||
// - a DIFFERENT pair than the descending window above, which a
|
||||
// window-before-sort bug (paging the raw pk scan, then sorting only
|
||||
// the already-cut window) would fail to distinguish.
|
||||
const midAsc = try client.tableSelect(allocator, "users", "", 1, 2, &.{.{ .field = "age" }});
|
||||
defer protocol.freeRows(allocator, midAsc);
|
||||
try std.testing.expectEqual(@as(usize, 2), midAsc.len);
|
||||
try std.testing.expectEqual(@as(i64, 4), midAsc[0].get(querymod.pkField).?.int);
|
||||
try std.testing.expectEqual(@as(i64, 20), midAsc[0].get("age").?.int);
|
||||
try std.testing.expectEqual(@as(i64, 3), midAsc[1].get(querymod.pkField).?.int);
|
||||
try std.testing.expectEqual(@as(i64, 30), midAsc[1].get("age").?.int);
|
||||
|
||||
// Cross-check: table_explain reports the `age` index is used for the
|
||||
// predicate that would narrow this query, same as the ordered path's
|
||||
// `selectAllMatches` internally reuses.
|
||||
var plan = try client.tableExplain(allocator, "users", "age gte i:0");
|
||||
defer plan.deinit(allocator);
|
||||
try std.testing.expectEqual(querymod.PlanKind.index_range, plan.kind);
|
||||
}
|
||||
|
||||
// A second, INDEPENDENT `orderBy` column exercises a path the single-column
|
||||
// test above cannot: `handleTableSelect` decoding a wire count > 1 and
|
||||
// `sortRowsBy`'s tie-breaking chain, both new with multi-column support.
|
||||
test "server table_select orders a remote result by two columns, sorting then breaking ties" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const path = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(path);
|
||||
|
||||
var db = try testDb(allocator, path);
|
||||
defer db.close();
|
||||
|
||||
{
|
||||
var table = try querymod.Table.open(&db, allocator, "people");
|
||||
defer table.deinit();
|
||||
// Two cities, scores deliberately NOT already grouped by city in
|
||||
// scan order, so a single-column `city` sort alone would leave
|
||||
// each city's rows unsorted by `score` - only a real tiebreak
|
||||
// fixes that.
|
||||
try table.put(.{ .int = 1 }, &.{ .{ .name = "city", .value = .{ .text = "warsaw" } }, .{ .name = "score", .value = .{ .int = 30 } } });
|
||||
try table.put(.{ .int = 2 }, &.{ .{ .name = "city", .value = .{ .text = "krakow" } }, .{ .name = "score", .value = .{ .int = 20 } } });
|
||||
try table.put(.{ .int = 3 }, &.{ .{ .name = "city", .value = .{ .text = "warsaw" } }, .{ .name = "score", .value = .{ .int = 10 } } });
|
||||
try table.put(.{ .int = 4 }, &.{ .{ .name = "city", .value = .{ .text = "krakow" } }, .{ .name = "score", .value = .{ .int = 40 } } });
|
||||
}
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var server = try Server.init(allocator, &db, address);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
const client_mod = @import("client.zig");
|
||||
var client = try client_mod.Client.connect(allocator, server.boundAddress());
|
||||
defer client.close();
|
||||
|
||||
const rows = try client.tableSelect(allocator, "people", "", 0, 0, &.{ .{ .field = "city" }, .{ .field = "score" } });
|
||||
defer protocol.freeRows(allocator, rows);
|
||||
try std.testing.expectEqual(@as(usize, 4), rows.len);
|
||||
try std.testing.expectEqual(@as(i64, 2), rows[0].get(querymod.pkField).?.int); // krakow/20
|
||||
try std.testing.expectEqual(@as(i64, 4), rows[1].get(querymod.pkField).?.int); // krakow/40
|
||||
try std.testing.expectEqual(@as(i64, 3), rows[2].get(querymod.pkField).?.int); // warsaw/10
|
||||
try std.testing.expectEqual(@as(i64, 1), rows[3].get(querymod.pkField).?.int); // warsaw/30
|
||||
}
|
||||
|
||||
test "server table_select rejects a malformed expr with bad_request, not a crash" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const path = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(path);
|
||||
|
||||
var db = try testDb(allocator, path);
|
||||
defer db.close();
|
||||
|
||||
{
|
||||
var table = try querymod.Table.open(&db, allocator, "users");
|
||||
defer table.deinit();
|
||||
try table.put(.{ .int = 1 }, &.{.{ .name = "city", .value = .{ .text = "warsaw" } }});
|
||||
}
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var server = try Server.init(allocator, &db, address);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
const client_mod = @import("client.zig");
|
||||
var client = try client_mod.Client.connect(allocator, server.boundAddress());
|
||||
defer client.close();
|
||||
|
||||
const err = client.tableSelect(allocator, "users", "city bogus s:warsaw", 0, 0, &.{});
|
||||
try std.testing.expectError(client_mod.ClientError.ServerBadRequest, err);
|
||||
}
|
||||
|
||||
fn seedAggregateOrders(allocator: Allocator, db: *Db) !void {
|
||||
var orders = try querymod.Table.open(db, allocator, "orders");
|
||||
defer orders.deinit();
|
||||
try orders.put(.{ .int = 1 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "total", .value = .{ .int = 100 } },
|
||||
});
|
||||
try orders.put(.{ .int = 2 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "total", .value = .{ .int = 250 } },
|
||||
});
|
||||
try orders.put(.{ .int = 3 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "krakow" } },
|
||||
.{ .name = "total", .value = .{ .int = 40 } },
|
||||
});
|
||||
}
|
||||
|
||||
test "server table_aggregate groups remote rows by field with count/sum, matching the local library result" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const path = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(path);
|
||||
|
||||
var db = try testDb(allocator, path);
|
||||
defer db.close();
|
||||
try seedAggregateOrders(allocator, &db);
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var server = try Server.init(allocator, &db, address);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
const client_mod = @import("client.zig");
|
||||
var client = try client_mod.Client.connect(allocator, server.boundAddress());
|
||||
defer client.close();
|
||||
|
||||
const specs = [_]aggregatemod.AggSpec{
|
||||
.{ .field = "", .kind = .count },
|
||||
.{ .field = "total", .kind = .sum },
|
||||
};
|
||||
const groups = try client.tableAggregateGroups(allocator, "orders", "city", &specs, 0, 0, &.{}, "", "");
|
||||
defer protocol.freeAggGroupRows(allocator, groups);
|
||||
|
||||
Reference in new issue
Block a user