feat: expand server
This commit is contained in:
1 parent
48ff5cf38e
commit
cac096469e
1 file changed
+159
+159
@@ -2330,3 +2330,162 @@ test "per-connection rate limit gives each connection its own budget" {
|
|||||||
var clientB = try client_mod.Client.connect(allocator, server.boundAddress());
|
var clientB = try client_mod.Client.connect(allocator, server.boundAddress());
|
||||||
defer clientB.close();
|
defer clientB.close();
|
||||||
|
|
||||||
|
// Exhaust A's private budget first: B must be unaffected because each
|
||||||
|
// connection got its own bucket, not a bucket shared across the server.
|
||||||
|
try clientA.ping();
|
||||||
|
try clientA.ping();
|
||||||
|
try std.testing.expectError(client_mod.ClientError.ServerRateLimited, clientA.ping());
|
||||||
|
|
||||||
|
try clientB.ping();
|
||||||
|
try clientB.ping();
|
||||||
|
try std.testing.expectError(client_mod.ClientError.ServerRateLimited, clientB.ping());
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(u64, 2), server.connRateLimitedTotal.get());
|
||||||
|
try std.testing.expectEqual(@as(u64, 0), server.rateLimitedTotal.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "per-connection and global rate limits both apply, connection budget checked first" {
|
||||||
|
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();
|
||||||
|
|
||||||
|
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||||
|
var server = try Server.init(allocator, &db, address);
|
||||||
|
server.enablePerConnectionRateLimit(10, 1);
|
||||||
|
server.enableRateLimit(2, 1);
|
||||||
|
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();
|
||||||
|
|
||||||
|
try client.ping();
|
||||||
|
try client.ping();
|
||||||
|
try std.testing.expectError(client_mod.ClientError.ServerRateLimited, client.ping());
|
||||||
|
|
||||||
|
// The global bucket was the one that actually ran out, not the
|
||||||
|
// per-connection one (which had plenty of headroom left).
|
||||||
|
try std.testing.expectEqual(@as(u64, 1), server.rateLimitedTotal.get());
|
||||||
|
try std.testing.expectEqual(@as(u64, 0), server.connRateLimitedTotal.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "server table_select executes a remote expr against real rows, using an index when available" {
|
||||||
|
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.createIndex("city");
|
||||||
|
try table.put(.{ .int = 1 }, &.{
|
||||||
|
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||||
|
.{ .name = "age", .value = .{ .int = 20 } },
|
||||||
|
});
|
||||||
|
try table.put(.{ .int = 2 }, &.{
|
||||||
|
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||||
|
.{ .name = "age", .value = .{ .int = 40 } },
|
||||||
|
});
|
||||||
|
try table.put(.{ .int = 3 }, &.{
|
||||||
|
.{ .name = "city", .value = .{ .text = "krakow" } },
|
||||||
|
.{ .name = "age", .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, "users", "city eq s:warsaw and age gte i:30", 0, 0, &.{});
|
||||||
|
defer protocol.freeRows(allocator, rows);
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), rows.len);
|
||||||
|
try std.testing.expectEqual(@as(i64, 2), rows[0].get(querymod.pkField).?.int);
|
||||||
|
|
||||||
|
const all = try client.tableSelect(allocator, "users", "", 0, 0, &.{});
|
||||||
|
defer protocol.freeRows(allocator, all);
|
||||||
|
try std.testing.expectEqual(@as(usize, 3), all.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "server table_explain reports index_eq, composite_eq, and full_scan over real TCP, matching what table_select would actually use" {
|
||||||
|
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.createIndex("age");
|
||||||
|
try table.createCompositeIndex("city_age", &.{ "city", "age" });
|
||||||
|
try table.put(.{ .int = 1 }, &.{
|
||||||
|
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||||
|
.{ .name = "age", .value = .{ .int = 30 } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Single-field index eq on `age`.
|
||||||
|
var agePlan = try client.tableExplain(allocator, "users", "age eq i:30");
|
||||||
|
defer agePlan.deinit(allocator);
|
||||||
|
try std.testing.expectEqual(querymod.PlanKind.index_eq, agePlan.kind);
|
||||||
|
try std.testing.expectEqualStrings("age", agePlan.indexName.?);
|
||||||
|
|
||||||
|
// Composite index, full prefix matched by equality on both columns.
|
||||||
|
var compositePlan = try client.tableExplain(allocator, "users", "city eq s:warsaw and age eq i:30");
|
||||||
|
defer compositePlan.deinit(allocator);
|
||||||
|
try std.testing.expectEqual(querymod.PlanKind.composite_eq, compositePlan.kind);
|
||||||
|
try std.testing.expectEqualStrings("city_age", compositePlan.indexName.?);
|
||||||
|
try std.testing.expectEqual(@as(usize, 2), compositePlan.matchedFields);
|
||||||
|
try std.testing.expectEqual(@as(usize, 2), compositePlan.totalFields);
|
||||||
|
|
||||||
|
// No predicate on an indexed field anywhere -> full scan.
|
||||||
|
var scanPlan = try client.tableExplain(allocator, "users", "city eq s:warsaw");
|
||||||
|
defer scanPlan.deinit(allocator);
|
||||||
|
try std.testing.expectEqual(querymod.PlanKind.full_scan, scanPlan.kind);
|
||||||
|
|
||||||
|
// Cross-check: what table_explain reports for the composite query is
|
||||||
|
// exactly what table_select actually finds when it runs it - the whole
|
||||||
|
// point of an explain feature is not lying about what execution does.
|
||||||
|
const rows = try client.tableSelect(allocator, "users", "city eq s:warsaw and age eq i:30", 0, 0, &.{});
|
||||||
|
defer protocol.freeRows(allocator, rows);
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), rows.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "server table_explain 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();
|
||||||
|
|
||||||
Reference in new issue
Block a user