M1: doc-level free list, sessions, and a spec runner that no longer overstates #1
141
src/commands.zig
141
src/commands.zig
@@ -559,10 +559,108 @@ fn reject_bad_session_fields(msg: *wire.Message, reply: *wire.Reply, cmd: []cons
|
|||||||
return reject_txn_fields(msg, reply, cmd);
|
return reject_txn_fields(msg, reply, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_end_sessions(_: *Context, _: *wire.Message, reply: *wire.Reply) !void {
|
/// Still a no-op -- there is nothing to end -- but no longer a blind `ok`.
|
||||||
|
///
|
||||||
|
/// A driver sends this on close for every session it handed out, so it is the
|
||||||
|
/// one session command that arrives in normal operation. Validating an array
|
||||||
|
/// we then discard looks like ceremony; it is not. `ok: 1` to a malformed
|
||||||
|
/// `endSessions` is the same class of answer as `ok: 1` to a transactional
|
||||||
|
/// write: the client is told the server understood, and it did not. mongod's
|
||||||
|
/// field path is `endSessions.endSessionsFromClient`, an IDL artefact -- the
|
||||||
|
/// command's own field is `endSessions` and the parsed argument has a
|
||||||
|
/// different name -- and it is reproduced rather than tidied, because a name
|
||||||
|
/// that differs from the real server's is worse than an odd one.
|
||||||
|
fn cmd_end_sessions(_: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
|
||||||
|
const arena = reply.arena_alloc();
|
||||||
|
const sessions = switch (msg.body.get("endSessions").?) {
|
||||||
|
.array => |a| a,
|
||||||
|
else => |v| {
|
||||||
|
const text = try std.fmt.allocPrint(
|
||||||
|
arena,
|
||||||
|
"BSON field 'endSessions.endSessions' is the wrong type '{s}', expected type 'array'",
|
||||||
|
.{v.type_name()},
|
||||||
|
);
|
||||||
|
return reply.put_error(@intFromEnum(ErrorCode.type_mismatch), "TypeMismatch", text);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (sessions, 0..) |entry, i| {
|
||||||
|
if (try reject_bad_session_entry(entry, i, reply)) return;
|
||||||
|
}
|
||||||
try reply.put_ok();
|
try reply.put_ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One element of `endSessions`. Answers whether an error reply was written.
|
||||||
|
fn reject_bad_session_entry(entry: bson.Value, i: usize, reply: *wire.Reply) !bool {
|
||||||
|
const arena = reply.arena_alloc();
|
||||||
|
const prefix = "BSON field 'endSessions.endSessionsFromClient";
|
||||||
|
const doc = switch (entry) {
|
||||||
|
.doc => |d| d,
|
||||||
|
else => {
|
||||||
|
const text = try std.fmt.allocPrint(
|
||||||
|
arena,
|
||||||
|
"{s}.{d}' is the wrong type '{s}', expected type 'object'",
|
||||||
|
.{ prefix, i, entry.type_name() },
|
||||||
|
);
|
||||||
|
try reply.put_error(@intFromEnum(ErrorCode.type_mismatch), "TypeMismatch", text);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var seen_id = false;
|
||||||
|
for (doc) |pair| {
|
||||||
|
if (std.mem.eql(u8, pair.key, "uid")) continue;
|
||||||
|
if (!std.mem.eql(u8, pair.key, "id")) {
|
||||||
|
const text = try std.fmt.allocPrint(
|
||||||
|
arena,
|
||||||
|
"{s}.{s}' is an unknown field.",
|
||||||
|
.{ prefix, pair.key },
|
||||||
|
);
|
||||||
|
try reply.put_error(@intFromEnum(ErrorCode.idl_unknown_field), "IDLUnknownField", text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
seen_id = true;
|
||||||
|
const bin = switch (pair.value) {
|
||||||
|
.binary => |b| b,
|
||||||
|
else => {
|
||||||
|
const text = try std.fmt.allocPrint(
|
||||||
|
arena,
|
||||||
|
"{s}.id' is the wrong type '{s}', expected type 'binData'",
|
||||||
|
.{ prefix, pair.value.type_name() },
|
||||||
|
);
|
||||||
|
try reply.put_error(@intFromEnum(ErrorCode.type_mismatch), "TypeMismatch", text);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (bin.subtype != 4) {
|
||||||
|
const text = try std.fmt.allocPrint(
|
||||||
|
arena,
|
||||||
|
"{s}.id' is the wrong binData type '{s}', expected type 'UUID'",
|
||||||
|
.{ prefix, subtype_name(bin.subtype) },
|
||||||
|
);
|
||||||
|
try reply.put_error(@intFromEnum(ErrorCode.type_mismatch), "TypeMismatch", text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (bin.data.len != 16) {
|
||||||
|
try reply.put_error(
|
||||||
|
@intFromEnum(ErrorCode.invalid_uuid),
|
||||||
|
"InvalidUUID",
|
||||||
|
"uuid must be a 16-byte binary field with UUID (4) subtype",
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!seen_id) {
|
||||||
|
try reply.put_error(
|
||||||
|
@intFromEnum(ErrorCode.idl_failed_to_parse),
|
||||||
|
"IDLFailedToParse",
|
||||||
|
"BSON field 'endSessions.endSessionsFromClient.id' is missing but a required field",
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
fn cmd_connection_status(_: *Context, _: *wire.Message, reply: *wire.Reply) !void {
|
fn cmd_connection_status(_: *Context, _: *wire.Message, reply: *wire.Reply) !void {
|
||||||
const auth_info = try reply.arena_alloc().alloc(bson.Pair, 2);
|
const auth_info = try reply.arena_alloc().alloc(bson.Pair, 2);
|
||||||
auth_info[0] = .{ .key = "authenticatedUsers", .value = .{ .array = &.{} } };
|
auth_info[0] = .{ .key = "authenticatedUsers", .value = .{ .array = &.{} } };
|
||||||
@@ -3194,6 +3292,47 @@ test "a transactional write is refused rather than applied" {
|
|||||||
try testing.expectEqual(@as(i64, 0), try doc_count(&ctx, "txn"));
|
try testing.expectEqual(@as(i64, 0), try doc_count(&ctx, "txn"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "endSessions judges the array it is handed" {
|
||||||
|
// Still a no-op, and that is not what is being tested. The command arrives
|
||||||
|
// in normal operation -- a driver sends it on close for every session it
|
||||||
|
// handed out -- so a blind `ok` here tells a client the server understood
|
||||||
|
// something it never looked at. Codes measured against mongod 8.3.7.
|
||||||
|
var threaded: std.Io.Threaded = .init_single_threaded;
|
||||||
|
defer threaded.deinit();
|
||||||
|
const io = threaded.io();
|
||||||
|
var tdb = try TestDb.init(io);
|
||||||
|
defer tdb.deinit();
|
||||||
|
var ctx = tdb.ctx(io);
|
||||||
|
|
||||||
|
const uuid = [_]u8{0x6B} ** 16;
|
||||||
|
const good = bson.Value{ .binary = .{ .subtype = 4, .data = &uuid } };
|
||||||
|
const one = bson.Value{ .doc = &.{.{ .key = "id", .value = good }} };
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(?i32, null), try run_for_code(&ctx, "endSessions", .{ .array = &.{} }, &.{}));
|
||||||
|
try testing.expectEqual(@as(?i32, null), try run_for_code(&ctx, "endSessions", .{ .array = &.{one} }, &.{}));
|
||||||
|
// `uid` rides along once authentication is on, exactly as it does in lsid.
|
||||||
|
try testing.expectEqual(@as(?i32, null), try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .doc = &.{
|
||||||
|
.{ .key = "id", .value = good },
|
||||||
|
.{ .key = "uid", .value = .{ .binary = .{ .subtype = 0, .data = &[_]u8{0} ** 32 } } },
|
||||||
|
} }} }, &.{}));
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(i32, 14), (try run_for_code(&ctx, "endSessions", .{ .string = "nope" }, &.{})).?);
|
||||||
|
try testing.expectEqual(@as(i32, 14), (try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .int32 = 5 }} }, &.{})).?);
|
||||||
|
try testing.expectEqual(@as(i32, 14), (try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .doc = &.{
|
||||||
|
.{ .key = "id", .value = .{ .string = "nope" } },
|
||||||
|
} }} }, &.{})).?);
|
||||||
|
try testing.expectEqual(@as(i32, 207), (try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .doc = &.{
|
||||||
|
.{ .key = "id", .value = .{ .binary = .{ .subtype = 4, .data = uuid[0..15] } } },
|
||||||
|
} }} }, &.{})).?);
|
||||||
|
try testing.expectEqual(@as(i32, 40414), (try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .doc = &.{} }} }, &.{})).?);
|
||||||
|
try testing.expectEqual(@as(i32, 40415), (try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .doc = &.{
|
||||||
|
.{ .key = "id", .value = good },
|
||||||
|
.{ .key = "bogus", .value = .{ .int32 = 1 } },
|
||||||
|
} }} }, &.{})).?);
|
||||||
|
// A bad entry after a good one is still a bad entry.
|
||||||
|
try testing.expectEqual(@as(i32, 14), (try run_for_code(&ctx, "endSessions", .{ .array = &.{ one, .{ .int32 = 5 } } }, &.{})).?);
|
||||||
|
}
|
||||||
|
|
||||||
test "an unknown command is reported before its session id is judged" {
|
test "an unknown command is reported before its session id is judged" {
|
||||||
// Measured: mongod answers CommandNotFound to `{nosuchcmd: 1, lsid: 5}`,
|
// Measured: mongod answers CommandNotFound to `{nosuchcmd: 1, lsid: 5}`,
|
||||||
// so the lookup comes first and this is where the check belongs.
|
// so the lookup comes first and this is where the check belongs.
|
||||||
|
|||||||
Reference in New Issue
Block a user