From 548c882d47a569422898aa2c79aa8f0851c142e2 Mon Sep 17 00:00:00 2001 From: "A.Shakhmatov" Date: Sun, 9 Aug 2026 13:23:58 +0300 Subject: [PATCH] commands: the wire version says what the version string says `buildInfo` has always reported 4.4.0 and the handshake has always reported maxWireVersion 8, which is 4.2. A driver believes the wire version: it refused client-side to send `hint` on an unacknowledged delete or findAndModify (the error is "only supported on MongoDB 4.4+", raised without a round trip), and withheld `comment` from getMore, listCollections and listDatabases (lib/operations/get_more.js:43 and its neighbours). Both are things this engine handles -- the acknowledged hint suites pass, and getMore ignores fields it does not know -- so the effect was purely the number disagreeing with itself. The 8 was not arbitrary. The comment above it tied it to omitting `topologyVersion`, which is what keeps a driver off the streaming hello protocol we do not implement -- a real bug, once visible as Compass reconnecting every heartbeat. Checked before touching it, in the driver rather than from memory: `useStreamingProtocol` (lib/sdam/monitor.js:154) returns false whenever `topologyVersion` is absent and never looks at the wire version at all. The omission is the whole mechanism; the wire version was a second line of defence that never existed. The comment now says so. A test asserts the two agree, so they cannot drift apart again silently, which is the actual defect here -- not the value. Spec suites 173/118/196 -> 189/102/196: 16 cases fixed, none broken. Ten are the unacknowledged-hint cases the previous commit uncovered, six are `comment` forwarding. 166/166 unit tests in ReleaseFast and ReleaseSafe, 82/82 fuzz, e2e 49, e2e2 concurrent 2 + crash pair, e2e3 16, e2e4 17, e2e6 72, e2e7 86, crash-fuzz 60 cycles. --- src/commands.zig | 50 ++++++++++++++++++++++++++++++++++++---- tests/spec/scorecard.txt | 38 +++++++++--------------------- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/commands.zig b/src/commands.zig index 4f92d5b..3f7c05b 100644 --- a/src/commands.zig +++ b/src/commands.zig @@ -257,7 +257,13 @@ fn add_server_info(ctx: *Context, reply: *wire.Reply) !void { try reply.put("logicalSessionTimeoutMinutes", .{ .int32 = 30 }); try reply.put("connectionId", .{ .int32 = @intCast(ctx.connection_id) }); try reply.put("minWireVersion", .{ .int32 = 0 }); - try reply.put("maxWireVersion", .{ .int32 = 8 }); + // 9, because this server calls itself 4.4.0 in `buildInfo` and 4.4 is + // wire 9. Reporting 8 was reporting 4.2, and a driver believes the wire + // version over the string: it refused client-side to send `hint` on an + // unacknowledged delete or findAndModify (ten spec cases), and withheld + // `comment` from getMore, listCollections and listDatabases. Both are + // things this engine handles. + try reply.put("maxWireVersion", .{ .int32 = 9 }); try reply.put("readOnly", .{ .bool = false }); // Deliberately no `topologyVersion`. A driver treats its presence as @@ -269,8 +275,13 @@ fn add_server_info(ctx: *Context, reply: *wire.Reply) !void { // fails the heartbeat ("Server ended moreToCome unexpectedly"), drops // the connection and resets its pool — a connect/disconnect loop once // per heartbeat, which is what MongoDB Compass showed. Omitting the - // field keeps monitoring on the polling path, which we do implement, - // and matches maxWireVersion 8: streaming hello arrived in wire 9. + // field keeps monitoring on the polling path, which we do implement. + // + // This is the whole of the mechanism, and it does not depend on the wire + // version: `useStreamingProtocol` (driver lib/sdam/monitor.js:154) polls + // whenever `topologyVersion` is absent, whatever else the handshake said. + // Checked when maxWireVersion went to 9 above, since the old comment here + // leaned on 8 as a second line of defence that never existed. } fn cmd_hello(ctx: *Context, _: *wire.Message, reply: *wire.Reply) !void { @@ -2718,7 +2729,38 @@ test "ping and hello replies parse" { try testing.expectEqual(@as(f64, 1.0), ok.double); const primary = bson.get_pair(reply2.pairs.items, "isWritablePrimary").?; try testing.expect(primary.bool); - try testing.expectEqual(@as(i32, 8), bson.get_pair(reply2.pairs.items, "maxWireVersion").?.int32); + try testing.expectEqual(@as(i32, 9), bson.get_pair(reply2.pairs.items, "maxWireVersion").?.int32); +} + +test "the wire version agrees with the version the server calls itself" { + // These two are read by different parts of a driver -- the handshake picks + // features off the wire version, `runOnRequirements` in the spec suites + // reads the string -- and when they disagreed the driver believed the wire + // version and withheld commands the version string promised. + 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); + + var hello = wire.Reply.init(testing.allocator); + defer hello.deinit(); + var hello_msg = try parse_fake_msg("hello", .null, &.{}); + defer hello_msg.deinit(); + try dispatch(&ctx, &hello_msg, &hello); + + var info = wire.Reply.init(testing.allocator); + defer info.deinit(); + var info_msg = try parse_fake_msg("buildInfo", .null, &.{}); + defer info_msg.deinit(); + try dispatch(&ctx, &info_msg, &info); + + // The mapping is the server's own: 4.2 is wire 8, 4.4 is wire 9. + const wire_version = bson.get_pair(hello.pairs.items, "maxWireVersion").?.int32; + const version = bson.get_pair(info.pairs.items, "version").?.string; + const expected: i32 = if (std.mem.startsWith(u8, version, "4.4.")) 9 else if (std.mem.startsWith(u8, version, "4.2.")) 8 else -1; + try testing.expectEqual(expected, wire_version); } test "handshake does not advertise the streaming hello protocol" { diff --git a/tests/spec/scorecard.txt b/tests/spec/scorecard.txt index 0b17127..6d1e8a6 100644 --- a/tests/spec/scorecard.txt +++ b/tests/spec/scorecard.txt @@ -1,7 +1,7 @@ # MongoDB spec-test scorecard (PLAN D2) -- crud + aggregate, unified format # specs: mongodb/specifications @ 615e0f9 (pinned in tests/spec/fetch.sh) # driver: mongodb@7.5.0 (pinned in tests/e2e/package-lock.json) -# server: MultiforaDB reporting version 4.4.0, maxWireVersion 8 +# server: MultiforaDB reporting version 4.4.0, maxWireVersion 9 # reproduce: bash tests/spec/fetch.sh && node tests/spec/run.js --scorecard # # What SKIP means here, so the totals are not read as better than they are: @@ -18,7 +18,7 @@ # hasServerConnectionId, and maxTimeMS in an expected command (CSOT rewrites # it -- the only assertion this runner declines to make). -total 173 pass 118 fail 196 skip 175 files 0 errored +total 189 pass 102 fail 196 skip 175 files 0 errored # per-file: name pass fail skip aggregate-allowdiskuse.json 3 0 0 @@ -30,16 +30,16 @@ aggregate-out-readConcern.json 0 0 4 aggregate-out.json 0 2 0 aggregate-rawdata.json 1 0 1 aggregate-write-readPreference.json 0 0 4 -aggregate.json 4 1 2 +aggregate.json 5 0 2 bulkWrite-arrayFilters.json 0 3 0 bulkWrite-collation.json 0 2 0 bulkWrite-comment.json 2 0 1 bulkWrite-delete-hint-serverError.json 0 0 2 bulkWrite-delete-hint.json 2 0 0 -bulkWrite-deleteMany-hint-unacknowledged.json 0 2 2 +bulkWrite-deleteMany-hint-unacknowledged.json 2 0 2 bulkWrite-deleteMany-let.json 0 1 1 bulkWrite-deleteMany-rawdata.json 1 0 1 -bulkWrite-deleteOne-hint-unacknowledged.json 0 2 2 +bulkWrite-deleteOne-hint-unacknowledged.json 2 0 2 bulkWrite-deleteOne-let.json 0 1 1 bulkWrite-deleteOne-rawdata.json 1 0 1 bulkWrite-errorResponse.json 0 0 1 @@ -93,7 +93,7 @@ db-aggregate.json 0 2 0 deleteMany-collation.json 0 1 0 deleteMany-comment.json 2 0 1 deleteMany-hint-serverError.json 0 0 2 -deleteMany-hint-unacknowledged.json 0 2 2 +deleteMany-hint-unacknowledged.json 2 0 2 deleteMany-hint.json 2 0 0 deleteMany-let.json 0 1 1 deleteMany-rawdata.json 1 0 1 @@ -102,7 +102,7 @@ deleteOne-collation.json 0 1 0 deleteOne-comment.json 2 0 1 deleteOne-errorResponse.json 0 0 1 deleteOne-hint-serverError.json 0 0 2 -deleteOne-hint-unacknowledged.json 0 2 2 +deleteOne-hint-unacknowledged.json 2 0 2 deleteOne-hint.json 2 0 0 deleteOne-let.json 0 1 1 deleteOne-rawdata.json 1 0 1 @@ -118,7 +118,7 @@ estimatedDocumentCount.json 2 1 3 find-allowdiskuse-serverError.json 0 0 2 find-allowdiskuse.json 3 0 0 find-collation.json 0 1 0 -find-comment.json 0 3 2 +find-comment.json 1 2 2 find-let.json 0 1 1 find-rawdata.json 1 0 1 find.json 5 0 0 @@ -126,7 +126,7 @@ findOne.json 1 1 0 findOneAndDelete-collation.json 0 1 0 findOneAndDelete-comment.json 2 0 1 findOneAndDelete-hint-serverError.json 0 0 2 -findOneAndDelete-hint-unacknowledged.json 0 2 2 +findOneAndDelete-hint-unacknowledged.json 2 0 2 findOneAndDelete-hint.json 2 0 0 findOneAndDelete-let.json 0 1 1 findOneAndDelete-rawdata.json 1 0 1 @@ -135,7 +135,7 @@ findOneAndReplace-collation.json 0 1 0 findOneAndReplace-comment.json 2 0 1 findOneAndReplace-dots_and_dollars.json 2 1 1 findOneAndReplace-hint-serverError.json 0 0 2 -findOneAndReplace-hint-unacknowledged.json 0 2 2 +findOneAndReplace-hint-unacknowledged.json 2 0 2 findOneAndReplace-hint.json 2 0 0 findOneAndReplace-let.json 0 1 1 findOneAndReplace-rawdata.json 1 0 1 @@ -147,7 +147,7 @@ findOneAndUpdate-comment.json 0 2 1 findOneAndUpdate-dots_and_dollars.json 0 0 4 findOneAndUpdate-errorResponse.json 0 1 1 findOneAndUpdate-hint-serverError.json 0 0 2 -findOneAndUpdate-hint-unacknowledged.json 0 2 2 +findOneAndUpdate-hint-unacknowledged.json 2 0 2 findOneAndUpdate-hint.json 2 0 0 findOneAndUpdate-let.json 0 1 1 findOneAndUpdate-pipeline.json 0 1 0 @@ -215,7 +215,6 @@ aggregate-out.json FAIL Aggregate with $out and batch size of 0 MongoServerError aggregate-rawdata.json SKIP Aggregate with rawData option needs server >= 8.2.0 aggregate-write-readPreference.json SKIP * needs topology replicaset/sharded/load-balanced aggregate.json SKIP aggregate with a document comment - pre 4.4 needs server <= 4.2.99 -aggregate.json FAIL aggregate with comment sets comment on getMore events client0[1].command.comment: missing from actual aggregate.json SKIP aggregate with comment does not set comment on getMore - pre 4.4 needs server <= 4.3.99 bulkWrite-arrayFilters.json FAIL BulkWrite updateOne with arrayFilters outcome crud-tests.test[0].y: expected an array, got {"$[i]":{"b":2}} bulkWrite-arrayFilters.json FAIL BulkWrite updateMany with arrayFilters outcome crud-tests.test[0].y: expected an array, got {"$[i]":{"b":2}} @@ -226,15 +225,11 @@ bulkWrite-comment.json SKIP BulkWrite with comment - pre 4.4 needs server <= 4.2 bulkWrite-delete-hint-serverError.json SKIP * needs server <= 4.3.3 bulkWrite-deleteMany-hint-unacknowledged.json SKIP Unacknowledged deleteMany with hint string fails with client-side error on pre-4.4 server needs server <= 4.2.99 bulkWrite-deleteMany-hint-unacknowledged.json SKIP Unacknowledged deleteMany with hint document fails with client-side error on pre-4.4 server needs server <= 4.2.99 -bulkWrite-deleteMany-hint-unacknowledged.json FAIL Unacknowledged deleteMany with hint string on 4.4+ server MongoBulkWriteError: hint for the delete command is only supported on MongoDB 4.4+ -bulkWrite-deleteMany-hint-unacknowledged.json FAIL Unacknowledged deleteMany with hint document on 4.4+ server MongoBulkWriteError: hint for the delete command is only supported on MongoDB 4.4+ bulkWrite-deleteMany-let.json SKIP BulkWrite deleteMany with let option needs server >= 5.0 bulkWrite-deleteMany-let.json FAIL BulkWrite deleteMany with let option unsupported (server-side error) bulkWrite: expected an error, the operation succeeded bulkWrite-deleteMany-rawdata.json SKIP BulkWrite deleteMany with rawData option needs server >= 8.2.0 bulkWrite-deleteOne-hint-unacknowledged.json SKIP Unacknowledged deleteOne with hint string fails with client-side error on pre-4.4 server needs server <= 4.2.99 bulkWrite-deleteOne-hint-unacknowledged.json SKIP Unacknowledged deleteOne with hint document fails with client-side error on pre-4.4 server needs server <= 4.2.99 -bulkWrite-deleteOne-hint-unacknowledged.json FAIL Unacknowledged deleteOne with hint string on 4.4+ server MongoBulkWriteError: hint for the delete command is only supported on MongoDB 4.4+ -bulkWrite-deleteOne-hint-unacknowledged.json FAIL Unacknowledged deleteOne with hint document on 4.4+ server MongoBulkWriteError: hint for the delete command is only supported on MongoDB 4.4+ bulkWrite-deleteOne-let.json SKIP BulkWrite deleteOne with let option needs server >= 5.0 bulkWrite-deleteOne-let.json FAIL BulkWrite deleteOne with let option unsupported (server-side error) bulkWrite: expected an error, the operation succeeded bulkWrite-deleteOne-rawdata.json SKIP BulkWrite deleteOne with rawData option needs server >= 8.2.0 @@ -320,8 +315,6 @@ deleteMany-comment.json SKIP deleteMany with comment - pre 4.4 needs server <= 4 deleteMany-hint-serverError.json SKIP * needs server <= 4.3.3 deleteMany-hint-unacknowledged.json SKIP Unacknowledged deleteMany with hint string fails with client-side error on pre-4.4 server needs server <= 4.2.99 deleteMany-hint-unacknowledged.json SKIP Unacknowledged deleteMany with hint document fails with client-side error on pre-4.4 server needs server <= 4.2.99 -deleteMany-hint-unacknowledged.json FAIL Unacknowledged deleteMany with hint string on 4.4+ server MongoCompatibilityError: hint for the delete command is only supported on MongoDB 4.4+ -deleteMany-hint-unacknowledged.json FAIL Unacknowledged deleteMany with hint document on 4.4+ server MongoCompatibilityError: hint for the delete command is only supported on MongoDB 4.4+ deleteMany-let.json SKIP deleteMany with let option needs server >= 5.0 deleteMany-let.json FAIL deleteMany with let option unsupported (server-side error) deleteMany: expected an error, the operation succeeded deleteMany-rawdata.json SKIP deleteMany with rawData option needs server >= 8.2.0 @@ -331,8 +324,6 @@ deleteOne-errorResponse.json SKIP delete operations support errorResponse assert deleteOne-hint-serverError.json SKIP * needs server <= 4.3.3 deleteOne-hint-unacknowledged.json SKIP Unacknowledged deleteOne with hint string fails with client-side error on pre-4.4 server needs server <= 4.2.99 deleteOne-hint-unacknowledged.json SKIP Unacknowledged deleteOne with hint document fails with client-side error on pre-4.4 server needs server <= 4.2.99 -deleteOne-hint-unacknowledged.json FAIL Unacknowledged deleteOne with hint string on 4.4+ server MongoCompatibilityError: hint for the delete command is only supported on MongoDB 4.4+ -deleteOne-hint-unacknowledged.json FAIL Unacknowledged deleteOne with hint document on 4.4+ server MongoCompatibilityError: hint for the delete command is only supported on MongoDB 4.4+ deleteOne-let.json SKIP deleteOne with let option needs server >= 5.0 deleteOne-let.json FAIL deleteOne with let option unsupported (server-side error) deleteOne: expected an error, the operation succeeded deleteOne-rawdata.json SKIP deleteOne with rawData option needs server >= 8.2.0 @@ -356,7 +347,6 @@ find-collation.json FAIL Find with a collation find: expected 1 elements, got 0 find-comment.json FAIL find with string comment find[0]: unexpected extra keys ["x"] find-comment.json FAIL find with document comment find[0]: unexpected extra keys ["x"] find-comment.json SKIP find with document comment - pre 4.4 needs server <= 4.2.99 -find-comment.json FAIL find with comment sets comment on getMore events client0[1].command.comment: missing from actual find-comment.json SKIP find with comment does not set comment on getMore - pre 4.4 needs server <= 4.3.99 find-let.json SKIP Find with let option needs server >= 5.0 find-let.json FAIL Find with let option unsupported (server-side error) find: expected an error, the operation succeeded @@ -367,8 +357,6 @@ findOneAndDelete-comment.json SKIP findOneAndDelete with comment - pre 4.4 needs findOneAndDelete-hint-serverError.json SKIP * needs server <= 4.3.3 findOneAndDelete-hint-unacknowledged.json SKIP Unacknowledged findOneAndDelete with hint string fails with client-side error on pre-4.4 server needs server <= 4.2.99 findOneAndDelete-hint-unacknowledged.json SKIP Unacknowledged findOneAndDelete with hint document fails with client-side error on pre-4.4 server needs server <= 4.2.99 -findOneAndDelete-hint-unacknowledged.json FAIL Unacknowledged findOneAndDelete with hint string on 4.4+ server MongoCompatibilityError: hint for the findAndModify command is only supported on MongoDB 4.4+ -findOneAndDelete-hint-unacknowledged.json FAIL Unacknowledged findOneAndDelete with hint document on 4.4+ server MongoCompatibilityError: hint for the findAndModify command is only supported on MongoDB 4.4+ findOneAndDelete-let.json SKIP findOneAndDelete with let option needs server >= 5.0 findOneAndDelete-let.json FAIL findOneAndDelete with let option unsupported (server-side error) findOneAndDelete: expected an error, the operation succeeded findOneAndDelete-rawdata.json SKIP findOneAndDelete with rawData option needs server >= 8.2.0 @@ -379,8 +367,6 @@ findOneAndReplace-dots_and_dollars.json FAIL Replacing document with dollar-pref findOneAndReplace-hint-serverError.json SKIP * needs server <= 4.3.0 findOneAndReplace-hint-unacknowledged.json SKIP Unacknowledged findOneAndReplace with hint string fails with client-side error on pre-4.4 server needs server <= 4.2.99 findOneAndReplace-hint-unacknowledged.json SKIP Unacknowledged findOneAndReplace with hint document fails with client-side error on pre-4.4 server needs server <= 4.2.99 -findOneAndReplace-hint-unacknowledged.json FAIL Unacknowledged findOneAndReplace with hint string on 4.4+ server MongoCompatibilityError: hint for the findAndModify command is only supported on MongoDB 4.4+ -findOneAndReplace-hint-unacknowledged.json FAIL Unacknowledged findOneAndReplace with hint document on 4.4+ server MongoCompatibilityError: hint for the findAndModify command is only supported on MongoDB 4.4+ findOneAndReplace-let.json SKIP findOneAndReplace with let option needs server >= 5.0 findOneAndReplace-let.json FAIL findOneAndReplace with let option unsupported (server-side error) findOneAndReplace: expected an error, the operation succeeded findOneAndReplace-rawdata.json SKIP findOneAndReplace with rawData option needs server >= 8.2.0 @@ -404,8 +390,6 @@ findOneAndUpdate-errorResponse.json SKIP findOneAndUpdate document validation er findOneAndUpdate-hint-serverError.json SKIP * needs server <= 4.3.0 findOneAndUpdate-hint-unacknowledged.json SKIP Unacknowledged findOneAndUpdate with hint string fails with client-side error on pre-4.4 server needs server <= 4.2.99 findOneAndUpdate-hint-unacknowledged.json SKIP Unacknowledged findOneAndUpdate with hint document fails with client-side error on pre-4.4 server needs server <= 4.2.99 -findOneAndUpdate-hint-unacknowledged.json FAIL Unacknowledged findOneAndUpdate with hint string on 4.4+ server MongoCompatibilityError: hint for the findAndModify command is only supported on MongoDB 4.4+ -findOneAndUpdate-hint-unacknowledged.json FAIL Unacknowledged findOneAndUpdate with hint document on 4.4+ server MongoCompatibilityError: hint for the findAndModify command is only supported on MongoDB 4.4+ findOneAndUpdate-let.json SKIP findOneAndUpdate with let option needs server >= 5.0 findOneAndUpdate-let.json FAIL findOneAndUpdate with let option unsupported (server-side error) findOneAndUpdate: expected an error, the operation succeeded findOneAndUpdate-pipeline.json FAIL FindOneAndUpdate using pipelines MongoServerError: update must be a document