...
A findAndModify with an equality query on _id, an update, a projection, and no explicit collation will hit an invariant when run on a collection with a non-simple default collation, due to a mismatch between the collator set on the ExpressionContext and the collator parsed by CanonicalQuery from the QueryRequest. When running findAndModify under the conditions outlined above: We parse the update request here. If the request has no explicit collation, the ExpCtx's collator remains nullptr. We attempt to parse the query at this point, but if it is a simple equality on _id then we do not create a CanonicalQuery. This deferred canonicalization is intended to let us subsequently check whether the query is eligible for the IDHACK fast-path. We then call getExecutorUpdate. We obtain the collection-default collation and set it on the ParsedUpdate. Because the ParsedUpdate has an ExpressionContext but still has no CanonicalQuery, the collator will only be set on the ExpressionContext. We then check whether we can use IDHACK. But because we have a projection, we fail this check and fall through to here, at which point we attempt to canonicalize the query. When constructing the QueryRequest for canonicalization, ParsedUpdate sets the (empty) collation directly from the UpdateRequest. CanonicalQuery::canonicalize therefore ends up creating a nullptr collator based on the QueryRequest and comparing it against the collection-default collator set on the ExpressionContext, causing us to hit an invariant. This bug was introduced in SERVER-45406.
xgen-internal-githook commented on Tue, 31 Mar 2020 20:55:51 +0000: Author: {'name': 'Ian Boros', 'email': 'ian.boros@mongodb.com', 'username': 'puppyofkosh'} Message: SERVER-46863 prevent collator mismatch in findAndModify/update (cherry picked from commit f105232d6594336e35cfee8f7f6d4ef49182ce84) Branch: v4.4 https://github.com/mongodb/mongo/commit/68df73a1d0700577209becb31cc3447f955fe898 xgen-internal-githook commented on Tue, 31 Mar 2020 19:01:49 +0000: Author: {'name': 'Ian Boros', 'email': 'ian.boros@mongodb.com', 'username': 'puppyofkosh'} Message: SERVER-46863 prevent collator mismatch in findAndModify/update Branch: master https://github.com/mongodb/mongo/commit/f105232d6594336e35cfee8f7f6d4ef49182ce84 ian.boros commented on Fri, 20 Mar 2020 21:12:13 +0000: A few notes: This is not an issue in 4.2 because ParsedUpdates do not create their own ExpressionContext in that branch. They rely on CQ to create it for them, see here. This means that the path in CQ::canonicalize() which has this invariant is not taken. This does not appear to be an issue for the delete command either, though its execution machinery is constructed in a similar manner. This is because there's no need to set the collation on the ParsedDelete before canonicalizing the query, as is done with update. bernard.gorman commented on Fri, 13 Mar 2020 19:17:55 +0000: Note: this bug will also manifest if the findAndModify request has a hint instead of a projection (or has both).
function reproBF16515() { const coll = db.reproBF16515; coll.drop(); assert.commandWorked(db.createCollection("reproBF16515", {collation: { locale: 'en_US' }})); // Works coll.findAndModify({query: {_id: 1}, update: {}}); print("Succeeded: fAM with _id query"); // Works coll.findAndModify({query: {a: 1}, update: {}, fields: {a: 1}}); print("Succeeded: fAM with non-_id query + projection"); // Invariant print("About to run: fAM with _id query + projection"); coll.findAndModify({ query: { _id: 3 }, update: {}, fields: { a: 1 } }); print("Unreachable: fAM with _id query + projection"); }