diff options
Diffstat (limited to 'jstests/change_streams/collation.js')
-rw-r--r-- | jstests/change_streams/collation.js | 662 |
1 files changed, 325 insertions, 337 deletions
diff --git a/jstests/change_streams/collation.js b/jstests/change_streams/collation.js index e99f6064b60..3d50b564711 100644 --- a/jstests/change_streams/collation.js +++ b/jstests/change_streams/collation.js @@ -3,344 +3,332 @@ * default collation, and uses the simple collation if none is provided. */ (function() { - "use strict"; - - load("jstests/libs/collection_drop_recreate.js"); // For assert[Drop|Create]Collection. - load("jstests/libs/change_stream_util.js"); // For 'ChangeStreamTest' and - // 'runCommandChangeStreamPassthroughAware'. - - let cst = new ChangeStreamTest(db); - - const caseInsensitive = {locale: "en_US", strength: 2}; - - let caseInsensitiveCollection = "change_stream_case_insensitive"; - assertDropCollection(db, caseInsensitiveCollection); - - // Test that you can open a change stream before the collection exists, and it will use the - // simple collation. Tag this stream as 'doNotModifyInPassthroughs', since only individual - // collections have the concept of a default collation. - const simpleCollationStream = cst.startWatchingChanges({ - pipeline: [ - {$changeStream: {}}, - { - $match: - {$or: [{"fullDocument._id": "INSERT_ONE"}, {"fullDocument._id": "INSERT_TWO"}]} - }, - {$project: {docId: "$fullDocument._id"}} - ], - collection: caseInsensitiveCollection, - doNotModifyInPassthroughs: true - }); - - // Create the collection with a non-default collation. The stream should continue to use the - // simple collation. - caseInsensitiveCollection = - assertCreateCollection(db, caseInsensitiveCollection, {collation: caseInsensitive}); - assert.commandWorked( - caseInsensitiveCollection.insert([{_id: "insert_one"}, {_id: "INSERT_TWO"}])); - cst.assertNextChangesEqual( - {cursor: simpleCollationStream, expectedChanges: [{docId: "INSERT_TWO"}]}); - - const caseInsensitivePipeline = [ +"use strict"; + +load("jstests/libs/collection_drop_recreate.js"); // For assert[Drop|Create]Collection. +load("jstests/libs/change_stream_util.js"); // For 'ChangeStreamTest' and + // 'runCommandChangeStreamPassthroughAware'. + +let cst = new ChangeStreamTest(db); + +const caseInsensitive = { + locale: "en_US", + strength: 2 +}; + +let caseInsensitiveCollection = "change_stream_case_insensitive"; +assertDropCollection(db, caseInsensitiveCollection); + +// Test that you can open a change stream before the collection exists, and it will use the +// simple collation. Tag this stream as 'doNotModifyInPassthroughs', since only individual +// collections have the concept of a default collation. +const simpleCollationStream = cst.startWatchingChanges({ + pipeline: [ + {$changeStream: {}}, + {$match: {$or: [{"fullDocument._id": "INSERT_ONE"}, {"fullDocument._id": "INSERT_TWO"}]}}, + {$project: {docId: "$fullDocument._id"}} + ], + collection: caseInsensitiveCollection, + doNotModifyInPassthroughs: true +}); + +// Create the collection with a non-default collation. The stream should continue to use the +// simple collation. +caseInsensitiveCollection = + assertCreateCollection(db, caseInsensitiveCollection, {collation: caseInsensitive}); +assert.commandWorked(caseInsensitiveCollection.insert([{_id: "insert_one"}, {_id: "INSERT_TWO"}])); +cst.assertNextChangesEqual( + {cursor: simpleCollationStream, expectedChanges: [{docId: "INSERT_TWO"}]}); + +const caseInsensitivePipeline = [ + {$changeStream: {}}, + {$match: {"fullDocument.text": "abc"}}, + {$project: {docId: "$documentKey._id"}} +]; + +// Test that $changeStream will not implicitly adopt the default collation of the collection on +// which it is run. Tag this stream as 'doNotModifyInPassthroughs'; whole-db and cluster-wide +// streams do not have default collations. +const didNotInheritCollationStream = cst.startWatchingChanges({ + pipeline: caseInsensitivePipeline, + collection: caseInsensitiveCollection, + doNotModifyInPassthroughs: true +}); +// Test that a collation can be explicitly specified for the $changeStream. This does not need +// to be tagged 'doNotModifyInPassthroughs', since whole-db and cluster-wide changeStreams will +// use an explicit collation if present. +let explicitCaseInsensitiveStream = cst.startWatchingChanges({ + pipeline: caseInsensitivePipeline, + collection: caseInsensitiveCollection, + aggregateOptions: {collation: caseInsensitive} +}); + +assert.writeOK(caseInsensitiveCollection.insert({_id: 0, text: "aBc"})); +assert.writeOK(caseInsensitiveCollection.insert({_id: 1, text: "abc"})); + +// 'didNotInheritCollationStream' should not have inherited the collection's case-insensitive +// default collation, and should only see the second insert. 'explicitCaseInsensitiveStream' +// should see both inserts. +cst.assertNextChangesEqual({cursor: didNotInheritCollationStream, expectedChanges: [{docId: 1}]}); +cst.assertNextChangesEqual( + {cursor: explicitCaseInsensitiveStream, expectedChanges: [{docId: 0}, {docId: 1}]}); + +// Test that the collation does not apply to the scan over the oplog. +const similarNameCollection = assertDropAndRecreateCollection( + db, "cHaNgE_sTrEaM_cAsE_iNsEnSiTiVe", {collation: {locale: "en_US"}}); + +// We must recreate the explicitCaseInsensitiveStream and set 'doNotModifyInPassthroughs'. Whole +// db and cluster-wide streams use the simple collation while scanning the oplog, but they don't +// filter the oplog by collection name. The subsequent $match stage which we inject into the +// pipeline to filter for a specific collection will obey the pipeline's case-insensitive +// collation, meaning that 'cHaNgE_sTrEaM_cAsE_iNsEnSiTiVe' will match +// 'change_stream_case_insensitive'. +explicitCaseInsensitiveStream = cst.startWatchingChanges({ + pipeline: caseInsensitivePipeline, + collection: caseInsensitiveCollection, + aggregateOptions: {collation: caseInsensitive}, + doNotModifyInPassthroughs: true +}); + +assert.writeOK(similarNameCollection.insert({_id: 0, text: "aBc"})); +assert.writeOK(caseInsensitiveCollection.insert({_id: 2, text: "ABC"})); + +// The case-insensitive stream should not see the first insert (to the other collection), only +// the second. We do not expect to see the insert in 'didNotInheritCollationStream'. +cst.assertNextChangesEqual({cursor: explicitCaseInsensitiveStream, expectedChanges: [{docId: 2}]}); + +// Test that creating a collection without a collation does not invalidate any change streams +// that were opened before the collection existed. +(function() { +let noCollationCollection = "change_stream_no_collation"; +assertDropCollection(db, noCollationCollection); + +const streamCreatedBeforeNoCollationCollection = cst.startWatchingChanges({ + pipeline: [{$changeStream: {}}, {$project: {docId: "$documentKey._id"}}], + collection: noCollationCollection +}); + +noCollationCollection = assertCreateCollection(db, noCollationCollection); +assert.writeOK(noCollationCollection.insert({_id: 0})); + +cst.assertNextChangesEqual( + {cursor: streamCreatedBeforeNoCollationCollection, expectedChanges: [{docId: 0}]}); +}()); + +// Test that creating a collection and explicitly specifying the simple collation does not +// invalidate any change streams that were opened before the collection existed. +(function() { +let simpleCollationCollection = "change_stream_simple_collation"; +assertDropCollection(db, simpleCollationCollection); + +const streamCreatedBeforeSimpleCollationCollection = cst.startWatchingChanges({ + pipeline: [{$changeStream: {}}, {$project: {docId: "$documentKey._id"}}], + collection: simpleCollationCollection +}); + +simpleCollationCollection = + assertCreateCollection(db, simpleCollationCollection, {collation: {locale: "simple"}}); +assert.writeOK(simpleCollationCollection.insert({_id: 0})); + +cst.assertNextChangesEqual( + {cursor: streamCreatedBeforeSimpleCollationCollection, expectedChanges: [{docId: 0}]}); +}()); + +// Test that creating a change stream with a non-default collation, then creating a collection +// with the same collation will not invalidate the change stream. +(function() { +let frenchCollection = "change_stream_french_collation"; +assertDropCollection(db, frenchCollection); + +const frenchChangeStream = cst.startWatchingChanges({ + pipeline: [{$changeStream: {}}, {$project: {docId: "$documentKey._id"}}], + aggregateOptions: {collation: {locale: "fr"}}, + collection: frenchCollection +}); + +frenchCollection = assertCreateCollection(db, frenchCollection, {collation: {locale: "fr"}}); +assert.writeOK(frenchCollection.insert({_id: 0})); + +cst.assertNextChangesEqual({cursor: frenchChangeStream, expectedChanges: [{docId: 0}]}); +}()); + +// Test that creating a change stream with a non-default collation, then creating a collection +// with *a different* collation will not invalidate the change stream. +(function() { +let germanCollection = "change_stream_german_collation"; +assertDropCollection(db, germanCollection); + +const englishCaseInsensitiveStream = cst.startWatchingChanges({ + pipeline: [ + {$changeStream: {}}, + {$match: {"fullDocument.text": "abc"}}, + {$project: {docId: "$documentKey._id"}} + ], + aggregateOptions: {collation: caseInsensitive}, + collection: germanCollection +}); + +germanCollection = assertCreateCollection(db, germanCollection, {collation: {locale: "de"}}); +assert.writeOK(germanCollection.insert({_id: 0, text: "aBc"})); + +cst.assertNextChangesEqual({cursor: englishCaseInsensitiveStream, expectedChanges: [{docId: 0}]}); +}()); + +// Test that creating a change stream with a non-default collation against a collection that has +// a non-simple default collation will use the collation specified on the operation. +(function() { +const caseInsensitiveCollection = assertDropAndRecreateCollection( + db, "change_stream_case_insensitive", {collation: caseInsensitive}); + +const englishCaseSensitiveStream = cst.startWatchingChanges({ + pipeline: [ {$changeStream: {}}, {$match: {"fullDocument.text": "abc"}}, {$project: {docId: "$documentKey._id"}} - ]; - - // Test that $changeStream will not implicitly adopt the default collation of the collection on - // which it is run. Tag this stream as 'doNotModifyInPassthroughs'; whole-db and cluster-wide - // streams do not have default collations. - const didNotInheritCollationStream = cst.startWatchingChanges({ - pipeline: caseInsensitivePipeline, - collection: caseInsensitiveCollection, - doNotModifyInPassthroughs: true - }); - // Test that a collation can be explicitly specified for the $changeStream. This does not need - // to be tagged 'doNotModifyInPassthroughs', since whole-db and cluster-wide changeStreams will - // use an explicit collation if present. - let explicitCaseInsensitiveStream = cst.startWatchingChanges({ - pipeline: caseInsensitivePipeline, - collection: caseInsensitiveCollection, - aggregateOptions: {collation: caseInsensitive} - }); - - assert.writeOK(caseInsensitiveCollection.insert({_id: 0, text: "aBc"})); - assert.writeOK(caseInsensitiveCollection.insert({_id: 1, text: "abc"})); - - // 'didNotInheritCollationStream' should not have inherited the collection's case-insensitive - // default collation, and should only see the second insert. 'explicitCaseInsensitiveStream' - // should see both inserts. - cst.assertNextChangesEqual( - {cursor: didNotInheritCollationStream, expectedChanges: [{docId: 1}]}); - cst.assertNextChangesEqual( - {cursor: explicitCaseInsensitiveStream, expectedChanges: [{docId: 0}, {docId: 1}]}); - - // Test that the collation does not apply to the scan over the oplog. - const similarNameCollection = assertDropAndRecreateCollection( - db, "cHaNgE_sTrEaM_cAsE_iNsEnSiTiVe", {collation: {locale: "en_US"}}); - - // We must recreate the explicitCaseInsensitiveStream and set 'doNotModifyInPassthroughs'. Whole - // db and cluster-wide streams use the simple collation while scanning the oplog, but they don't - // filter the oplog by collection name. The subsequent $match stage which we inject into the - // pipeline to filter for a specific collection will obey the pipeline's case-insensitive - // collation, meaning that 'cHaNgE_sTrEaM_cAsE_iNsEnSiTiVe' will match - // 'change_stream_case_insensitive'. - explicitCaseInsensitiveStream = cst.startWatchingChanges({ - pipeline: caseInsensitivePipeline, - collection: caseInsensitiveCollection, - aggregateOptions: {collation: caseInsensitive}, - doNotModifyInPassthroughs: true - }); - - assert.writeOK(similarNameCollection.insert({_id: 0, text: "aBc"})); - assert.writeOK(caseInsensitiveCollection.insert({_id: 2, text: "ABC"})); - - // The case-insensitive stream should not see the first insert (to the other collection), only - // the second. We do not expect to see the insert in 'didNotInheritCollationStream'. - cst.assertNextChangesEqual( - {cursor: explicitCaseInsensitiveStream, expectedChanges: [{docId: 2}]}); - - // Test that creating a collection without a collation does not invalidate any change streams - // that were opened before the collection existed. - (function() { - let noCollationCollection = "change_stream_no_collation"; - assertDropCollection(db, noCollationCollection); - - const streamCreatedBeforeNoCollationCollection = cst.startWatchingChanges({ - pipeline: [{$changeStream: {}}, {$project: {docId: "$documentKey._id"}}], - collection: noCollationCollection - }); - - noCollationCollection = assertCreateCollection(db, noCollationCollection); - assert.writeOK(noCollationCollection.insert({_id: 0})); - - cst.assertNextChangesEqual( - {cursor: streamCreatedBeforeNoCollationCollection, expectedChanges: [{docId: 0}]}); - }()); - - // Test that creating a collection and explicitly specifying the simple collation does not - // invalidate any change streams that were opened before the collection existed. - (function() { - let simpleCollationCollection = "change_stream_simple_collation"; - assertDropCollection(db, simpleCollationCollection); - - const streamCreatedBeforeSimpleCollationCollection = cst.startWatchingChanges({ - pipeline: [{$changeStream: {}}, {$project: {docId: "$documentKey._id"}}], - collection: simpleCollationCollection - }); - - simpleCollationCollection = - assertCreateCollection(db, simpleCollationCollection, {collation: {locale: "simple"}}); - assert.writeOK(simpleCollationCollection.insert({_id: 0})); - - cst.assertNextChangesEqual( - {cursor: streamCreatedBeforeSimpleCollationCollection, expectedChanges: [{docId: 0}]}); - }()); - - // Test that creating a change stream with a non-default collation, then creating a collection - // with the same collation will not invalidate the change stream. - (function() { - let frenchCollection = "change_stream_french_collation"; - assertDropCollection(db, frenchCollection); - - const frenchChangeStream = cst.startWatchingChanges({ - pipeline: [{$changeStream: {}}, {$project: {docId: "$documentKey._id"}}], - aggregateOptions: {collation: {locale: "fr"}}, - collection: frenchCollection - }); - - frenchCollection = - assertCreateCollection(db, frenchCollection, {collation: {locale: "fr"}}); - assert.writeOK(frenchCollection.insert({_id: 0})); - - cst.assertNextChangesEqual({cursor: frenchChangeStream, expectedChanges: [{docId: 0}]}); - }()); - - // Test that creating a change stream with a non-default collation, then creating a collection - // with *a different* collation will not invalidate the change stream. - (function() { - let germanCollection = "change_stream_german_collation"; - assertDropCollection(db, germanCollection); - - const englishCaseInsensitiveStream = cst.startWatchingChanges({ - pipeline: [ - {$changeStream: {}}, - {$match: {"fullDocument.text": "abc"}}, - {$project: {docId: "$documentKey._id"}} - ], - aggregateOptions: {collation: caseInsensitive}, - collection: germanCollection - }); - - germanCollection = - assertCreateCollection(db, germanCollection, {collation: {locale: "de"}}); - assert.writeOK(germanCollection.insert({_id: 0, text: "aBc"})); - - cst.assertNextChangesEqual( - {cursor: englishCaseInsensitiveStream, expectedChanges: [{docId: 0}]}); - }()); - - // Test that creating a change stream with a non-default collation against a collection that has - // a non-simple default collation will use the collation specified on the operation. - (function() { - const caseInsensitiveCollection = assertDropAndRecreateCollection( - db, "change_stream_case_insensitive", {collation: caseInsensitive}); - - const englishCaseSensitiveStream = cst.startWatchingChanges({ - pipeline: [ - {$changeStream: {}}, - {$match: {"fullDocument.text": "abc"}}, - {$project: {docId: "$documentKey._id"}} - ], - aggregateOptions: {collation: {locale: "en_US"}}, - collection: caseInsensitiveCollection - }); - - assert.writeOK(caseInsensitiveCollection.insert({_id: 0, text: "aBc"})); - assert.writeOK(caseInsensitiveCollection.insert({_id: 1, text: "abc"})); - - cst.assertNextChangesEqual( - {cursor: englishCaseSensitiveStream, expectedChanges: [{docId: 1}]}); - }()); - - // Test that collation is supported by the shell helper. Test that creating a change stream with - // a non-default collation against a collection that has a simple default collation will use the - // collation specified on the operation. - (function() { - const noCollationCollection = - assertDropAndRecreateCollection(db, "change_stream_no_collation"); - - const cursor = noCollationCollection.watch( - [{$match: {"fullDocument.text": "abc"}}, {$project: {docId: "$documentKey._id"}}], - {collation: caseInsensitive}); - assert(!cursor.hasNext()); - assert.writeOK(noCollationCollection.insert({_id: 0, text: "aBc"})); - assert.writeOK(noCollationCollection.insert({_id: 1, text: "abc"})); - assert.soon(() => cursor.hasNext()); - assertChangeStreamEventEq(cursor.next(), {docId: 0}); - assert.soon(() => cursor.hasNext()); - assertChangeStreamEventEq(cursor.next(), {docId: 1}); - assert(!cursor.hasNext()); - }()); - - // Test that we can resume a change stream on a collection that has been dropped without - // requiring the user to explicitly specify the collation. - (function() { - const collName = "change_stream_case_insensitive"; - let caseInsensitiveCollection = - assertDropAndRecreateCollection(db, collName, {collation: caseInsensitive}); - - let changeStream = caseInsensitiveCollection.watch([{$match: {"fullDocument.text": "abc"}}], - {collation: caseInsensitive}); - - assert.writeOK(caseInsensitiveCollection.insert({_id: 0, text: "abc"})); - - assert.soon(() => changeStream.hasNext()); - const next = changeStream.next(); - assert.docEq(next.documentKey, {_id: 0}); - const resumeToken = next._id; - - // Insert a second document to see after resuming. - assert.writeOK(caseInsensitiveCollection.insert({_id: "dropped_coll", text: "ABC"})); - - // Drop the collection to invalidate the stream. - assertDropCollection(db, collName); - - // Test that a $changeStream is allowed to resume on the dropped collection with an explicit - // collation, even if it doesn't match the original collection's default collation. - changeStream = caseInsensitiveCollection.watch( - [{$match: {"fullDocument.text": "ABC"}}], - {resumeAfter: resumeToken, collation: {locale: "simple"}}); - - assert.soon(() => changeStream.hasNext()); - assert.docEq(changeStream.next().documentKey, {_id: "dropped_coll"}); - - // Test that a pipeline without an explicit collation is allowed to resume the change stream - // after the collection has been dropped, and it will use the simple collation. Do not - // modify this in the passthrough suite(s) since only individual collections have the - // concept of a default collation. - const doNotModifyInPassthroughs = true; - const cmdRes = assert.commandWorked(runCommandChangeStreamPassthroughAware( - db, - { - aggregate: collName, - pipeline: [{$changeStream: {resumeAfter: resumeToken}}], - cursor: {}, - }, - doNotModifyInPassthroughs)); - - changeStream = new DBCommandCursor(db, cmdRes); - assert.soon(() => changeStream.hasNext()); - assert.docEq(changeStream.next().documentKey, {_id: "dropped_coll"}); - }()); - - // Test that the default collation of a new version of the collection is not applied when - // resuming a change stream from before a collection drop. - (function() { - const collName = "change_stream_case_insensitive"; - let caseInsensitiveCollection = - assertDropAndRecreateCollection(db, collName, {collation: caseInsensitive}); - - let changeStream = caseInsensitiveCollection.watch([{$match: {"fullDocument.text": "abc"}}], - {collation: caseInsensitive}); - - assert.writeOK(caseInsensitiveCollection.insert({_id: 0, text: "abc"})); - - assert.soon(() => changeStream.hasNext()); - const next = changeStream.next(); - assert.docEq(next.documentKey, {_id: 0}); - const resumeToken = next._id; - - // Insert a second document to see after resuming. - assert.writeOK(caseInsensitiveCollection.insert({_id: "dropped_coll", text: "ABC"})); - - // Recreate the collection with a different collation. - caseInsensitiveCollection = assertDropAndRecreateCollection( - db, caseInsensitiveCollection.getName(), {collation: {locale: "simple"}}); - assert.writeOK(caseInsensitiveCollection.insert({_id: "new collection", text: "abc"})); - - // Verify that the stream sees the insert before the drop and then is exhausted. We won't - // see the invalidate because the pipeline has a $match stage after the $changeStream. - assert.soon(() => changeStream.hasNext()); - assert.docEq(changeStream.next().fullDocument, {_id: "dropped_coll", text: "ABC"}); - // Only single-collection streams will be exhausted from the drop. Use 'next()' instead of - // 'isExhausted()' to force a getMore since the previous getMore may not include the - // collection drop, which is more likely with sharded collections on slow machines. - if (!isChangeStreamPassthrough()) { - assert.throws(() => changeStream.next()); - } - - // Test that a pipeline with an explicit collation is allowed to resume from before the - // collection is dropped and recreated. - changeStream = - caseInsensitiveCollection.watch([{$match: {"fullDocument.text": "ABC"}}], - {resumeAfter: resumeToken, collation: {locale: "fr"}}); - - assert.soon(() => changeStream.hasNext()); - assert.docEq(changeStream.next().documentKey, {_id: "dropped_coll"}); - // Only single-collection streams will be exhausted from the drop. Use 'next()' instead of - // 'isExhausted()' to force a getMore since the previous getMore may not include the - // collection drop, which is more likely with sharded collections on slow machines. - if (!isChangeStreamPassthrough()) { - assert.throws(() => changeStream.next()); - } - - // Test that a pipeline without an explicit collation is allowed to resume, even though the - // collection has been recreated with the same default collation as it had previously. Do - // not modify this command in the passthrough suite(s) since only individual collections - // have the concept of a default collation. - const doNotModifyInPassthroughs = true; - const cmdRes = assert.commandWorked(runCommandChangeStreamPassthroughAware( - db, - { - aggregate: collName, - pipeline: [{$changeStream: {resumeAfter: resumeToken}}], - cursor: {} - }, - doNotModifyInPassthroughs)); - - changeStream = new DBCommandCursor(db, cmdRes); - assert.soon(() => changeStream.hasNext()); - assert.docEq(changeStream.next().documentKey, {_id: "dropped_coll"}); - }()); + ], + aggregateOptions: {collation: {locale: "en_US"}}, + collection: caseInsensitiveCollection +}); + +assert.writeOK(caseInsensitiveCollection.insert({_id: 0, text: "aBc"})); +assert.writeOK(caseInsensitiveCollection.insert({_id: 1, text: "abc"})); + +cst.assertNextChangesEqual({cursor: englishCaseSensitiveStream, expectedChanges: [{docId: 1}]}); +}()); + +// Test that collation is supported by the shell helper. Test that creating a change stream with +// a non-default collation against a collection that has a simple default collation will use the +// collation specified on the operation. +(function() { +const noCollationCollection = assertDropAndRecreateCollection(db, "change_stream_no_collation"); + +const cursor = noCollationCollection.watch( + [{$match: {"fullDocument.text": "abc"}}, {$project: {docId: "$documentKey._id"}}], + {collation: caseInsensitive}); +assert(!cursor.hasNext()); +assert.writeOK(noCollationCollection.insert({_id: 0, text: "aBc"})); +assert.writeOK(noCollationCollection.insert({_id: 1, text: "abc"})); +assert.soon(() => cursor.hasNext()); +assertChangeStreamEventEq(cursor.next(), {docId: 0}); +assert.soon(() => cursor.hasNext()); +assertChangeStreamEventEq(cursor.next(), {docId: 1}); +assert(!cursor.hasNext()); +}()); + +// Test that we can resume a change stream on a collection that has been dropped without +// requiring the user to explicitly specify the collation. +(function() { +const collName = "change_stream_case_insensitive"; +let caseInsensitiveCollection = + assertDropAndRecreateCollection(db, collName, {collation: caseInsensitive}); + +let changeStream = caseInsensitiveCollection.watch([{$match: {"fullDocument.text": "abc"}}], + {collation: caseInsensitive}); + +assert.writeOK(caseInsensitiveCollection.insert({_id: 0, text: "abc"})); + +assert.soon(() => changeStream.hasNext()); +const next = changeStream.next(); +assert.docEq(next.documentKey, {_id: 0}); +const resumeToken = next._id; + +// Insert a second document to see after resuming. +assert.writeOK(caseInsensitiveCollection.insert({_id: "dropped_coll", text: "ABC"})); + +// Drop the collection to invalidate the stream. +assertDropCollection(db, collName); + +// Test that a $changeStream is allowed to resume on the dropped collection with an explicit +// collation, even if it doesn't match the original collection's default collation. +changeStream = + caseInsensitiveCollection.watch([{$match: {"fullDocument.text": "ABC"}}], + {resumeAfter: resumeToken, collation: {locale: "simple"}}); + +assert.soon(() => changeStream.hasNext()); +assert.docEq(changeStream.next().documentKey, {_id: "dropped_coll"}); + +// Test that a pipeline without an explicit collation is allowed to resume the change stream +// after the collection has been dropped, and it will use the simple collation. Do not +// modify this in the passthrough suite(s) since only individual collections have the +// concept of a default collation. +const doNotModifyInPassthroughs = true; +const cmdRes = assert.commandWorked(runCommandChangeStreamPassthroughAware( + db, + { + aggregate: collName, + pipeline: [{$changeStream: {resumeAfter: resumeToken}}], + cursor: {}, + }, + doNotModifyInPassthroughs)); + +changeStream = new DBCommandCursor(db, cmdRes); +assert.soon(() => changeStream.hasNext()); +assert.docEq(changeStream.next().documentKey, {_id: "dropped_coll"}); +}()); + +// Test that the default collation of a new version of the collection is not applied when +// resuming a change stream from before a collection drop. +(function() { +const collName = "change_stream_case_insensitive"; +let caseInsensitiveCollection = + assertDropAndRecreateCollection(db, collName, {collation: caseInsensitive}); + +let changeStream = caseInsensitiveCollection.watch([{$match: {"fullDocument.text": "abc"}}], + {collation: caseInsensitive}); + +assert.writeOK(caseInsensitiveCollection.insert({_id: 0, text: "abc"})); + +assert.soon(() => changeStream.hasNext()); +const next = changeStream.next(); +assert.docEq(next.documentKey, {_id: 0}); +const resumeToken = next._id; + +// Insert a second document to see after resuming. +assert.writeOK(caseInsensitiveCollection.insert({_id: "dropped_coll", text: "ABC"})); + +// Recreate the collection with a different collation. +caseInsensitiveCollection = assertDropAndRecreateCollection( + db, caseInsensitiveCollection.getName(), {collation: {locale: "simple"}}); +assert.writeOK(caseInsensitiveCollection.insert({_id: "new collection", text: "abc"})); + +// Verify that the stream sees the insert before the drop and then is exhausted. We won't +// see the invalidate because the pipeline has a $match stage after the $changeStream. +assert.soon(() => changeStream.hasNext()); +assert.docEq(changeStream.next().fullDocument, {_id: "dropped_coll", text: "ABC"}); +// Only single-collection streams will be exhausted from the drop. Use 'next()' instead of +// 'isExhausted()' to force a getMore since the previous getMore may not include the +// collection drop, which is more likely with sharded collections on slow machines. +if (!isChangeStreamPassthrough()) { + assert.throws(() => changeStream.next()); +} + +// Test that a pipeline with an explicit collation is allowed to resume from before the +// collection is dropped and recreated. +changeStream = + caseInsensitiveCollection.watch([{$match: {"fullDocument.text": "ABC"}}], + {resumeAfter: resumeToken, collation: {locale: "fr"}}); + +assert.soon(() => changeStream.hasNext()); +assert.docEq(changeStream.next().documentKey, {_id: "dropped_coll"}); +// Only single-collection streams will be exhausted from the drop. Use 'next()' instead of +// 'isExhausted()' to force a getMore since the previous getMore may not include the +// collection drop, which is more likely with sharded collections on slow machines. +if (!isChangeStreamPassthrough()) { + assert.throws(() => changeStream.next()); +} + +// Test that a pipeline without an explicit collation is allowed to resume, even though the +// collection has been recreated with the same default collation as it had previously. Do +// not modify this command in the passthrough suite(s) since only individual collections +// have the concept of a default collation. +const doNotModifyInPassthroughs = true; +const cmdRes = assert.commandWorked(runCommandChangeStreamPassthroughAware( + db, + {aggregate: collName, pipeline: [{$changeStream: {resumeAfter: resumeToken}}], cursor: {}}, + doNotModifyInPassthroughs)); + +changeStream = new DBCommandCursor(db, cmdRes); +assert.soon(() => changeStream.hasNext()); +assert.docEq(changeStream.next().documentKey, {_id: "dropped_coll"}); +}()); })(); |