feat: build out server
This commit is contained in:
1 parent
cac096469e
commit
7bd98b0580
1 file changed
+156
+156
@@ -2489,3 +2489,159 @@ test "server table_explain rejects a malformed expr with bad_request, not a cras
|
||||
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 }, &.{});
|
||||
}
|
||||
|
||||
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.tableExplain(allocator, "users", "city bogus s:warsaw");
|
||||
try std.testing.expectError(client_mod.ClientError.ServerBadRequest, err);
|
||||
|
||||
// The connection survives a bad_request and can still serve a good
|
||||
// request right after - same expectation `table_select`'s malformed-expr
|
||||
// test above holds for its own opcode.
|
||||
var plan = try client.tableExplain(allocator, "users", "");
|
||||
defer plan.deinit(allocator);
|
||||
try std.testing.expectEqual(querymod.PlanKind.full_scan, plan.kind);
|
||||
}
|
||||
|
||||
test "server table_update applies a set clause to matching rows over real TCP, keeping an index in sync" {
|
||||
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 = "active", .value = .{ .int = 0 } } });
|
||||
try table.put(.{ .int = 2 }, &.{ .{ .name = "city", .value = .{ .text = "warsaw" } }, .{ .name = "active", .value = .{ .int = 0 } } });
|
||||
try table.put(.{ .int = 3 }, &.{ .{ .name = "city", .value = .{ .text = "krakow" } }, .{ .name = "active", .value = .{ .int = 0 } } });
|
||||
}
|
||||
|
||||
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 sets = [_]rowmod.Field{.{ .name = "city", .value = .{ .text = "gdansk" } }};
|
||||
const updated = try client.tableUpdate("users", &sets, "city eq s:warsaw");
|
||||
try std.testing.expectEqual(@as(u64, 2), updated);
|
||||
|
||||
// The old index entry is gone for both moved rows...
|
||||
const stale = try client.tableSelect(allocator, "users", "city eq s:warsaw", 0, 0, &.{});
|
||||
defer protocol.freeRows(allocator, stale);
|
||||
try std.testing.expectEqual(@as(usize, 0), stale.len);
|
||||
|
||||
// ...a fresh one under the new value finds them, and the row not
|
||||
// matched by the predicate (pk 3, krakow) was left untouched.
|
||||
const moved = try client.tableSelect(allocator, "users", "city eq s:gdansk", 0, 0, &.{});
|
||||
defer protocol.freeRows(allocator, moved);
|
||||
try std.testing.expectEqual(@as(usize, 2), moved.len);
|
||||
|
||||
const untouched = try client.tableSelect(allocator, "users", "city eq s:krakow", 0, 0, &.{});
|
||||
defer protocol.freeRows(allocator, untouched);
|
||||
try std.testing.expectEqual(@as(usize, 1), untouched.len);
|
||||
}
|
||||
|
||||
test "server table_update 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 }, &.{});
|
||||
}
|
||||
|
||||
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 sets = [_]rowmod.Field{.{ .name = "active", .value = .{ .int = 1 } }};
|
||||
const err = client.tableUpdate("users", &sets, "city bogus s:warsaw");
|
||||
try std.testing.expectError(client_mod.ClientError.ServerBadRequest, err);
|
||||
|
||||
// The connection survives a bad_request and can still serve a good
|
||||
// request right after - same expectation `table_explain`'s malformed
|
||||
// test above holds for its own opcode.
|
||||
const updated = try client.tableUpdate("users", &sets, "");
|
||||
try std.testing.expectEqual(@as(u64, 1), updated);
|
||||
}
|
||||
|
||||
test "server rejects a malformed table_update frame (bad set fields) with bad_request without crashing" {
|
||||
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);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
const stream = try std.net.tcpConnectToAddress(server.boundAddress());
|
||||
defer stream.close();
|
||||
|
||||
// A well-formed table name ("users"), then a "set fields" byte string
|
||||
// that decodeTableUpdate happily hands back as opaque bytes but
|
||||
// Row.decode rejects (a field count of 0xFFFFFFFF with nothing behind
|
||||
// it) - the decode failure `handleTableUpdate` itself must catch,
|
||||
// unlike `decodeTableUpdate`'s own malformed-frame check above it.
|
||||
var payload = std.ArrayList(u8).init(allocator);
|
||||
defer payload.deinit();
|
||||
var lenBuf: [4]u8 = undefined;
|
||||
std.mem.writeInt(u32, &lenBuf, 5, .little);
|
||||
try payload.appendSlice(&lenBuf);
|
||||
try payload.appendSlice("users");
|
||||
std.mem.writeInt(u32, &lenBuf, 4, .little);
|
||||
try payload.appendSlice(&lenBuf);
|
||||
std.mem.writeInt(u32, &lenBuf, 0xFFFFFFFF, .little);
|
||||
try payload.appendSlice(&lenBuf);
|
||||
|
||||
try protocol.writeFrame(stream.writer(), @intFromEnum(protocol.Op.table_update), payload.items);
|
||||
var frame = try protocol.readFrame(allocator, stream.reader());
|
||||
defer frame.deinit();
|
||||
try std.testing.expectEqual(@intFromEnum(protocol.Status.bad_request), frame.tag);
|
||||
|
||||
// The connection must still be usable afterward.
|
||||
try protocol.writeFrame(stream.writer(), @intFromEnum(protocol.Op.ping), "");
|
||||
var pingFrame = try protocol.readFrame(allocator, stream.reader());
|
||||
defer pingFrame.deinit();
|
||||
try std.testing.expectEqual(@intFromEnum(protocol.Status.ok), pingFrame.tag);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user