diff options
Diffstat (limited to 'jstests/core')
139 files changed, 826 insertions, 7648 deletions
diff --git a/jstests/core/aggregation_accepts_write_concern.js b/jstests/core/aggregation_accepts_write_concern.js index 8117c296e03..2c764414a1d 100644 --- a/jstests/core/aggregation_accepts_write_concern.js +++ b/jstests/core/aggregation_accepts_write_concern.js @@ -1,11 +1,7 @@ /** * Confirms that the aggregate command accepts writeConcern regardless of whether the pipeline * writes or is read-only. - * @tags: [ - * assumes_write_concern_unchanged, - * does_not_support_stepdowns, - * references_foreign_collection - * ] + * @tags: [assumes_write_concern_unchanged, does_not_support_stepdowns] */ (function() { "use strict"; diff --git a/jstests/core/api_version_new_50_language_features.js b/jstests/core/api_version_new_50_language_features.js index 8d73124b2eb..71ea01d53f2 100644 --- a/jstests/core/api_version_new_50_language_features.js +++ b/jstests/core/api_version_new_50_language_features.js @@ -12,7 +12,6 @@ load("jstests/libs/api_version_helpers.js"); // For 'APIVersionHelpers'. const collName = "api_version_new_50_language_features"; -const viewName = collName + "_view"; const coll = db[collName]; coll.drop(); assert.commandWorked(coll.insert({a: 1, date: new ISODate()})); @@ -26,12 +25,53 @@ const stablePipelines = [ [{$set: {x: {$setField: {input: "$$ROOT", field: "x", value: "foo"}}}}], ]; +function assertAggregateFailsWithAPIStrict(pipeline, errorCodes) { + assert.commandFailedWithCode(db.runCommand({ + aggregate: collName, + pipeline: pipeline, + cursor: {}, + apiStrict: true, + apiVersion: "1" + }), + errorCodes, + pipeline); +} + +function assertAggregateSucceedsWithAPIStrict(pipeline) { + assert.commandWorked(db.runCommand( + {aggregate: collName, pipeline: pipeline, cursor: {}, apiStrict: true, apiVersion: "1"})); +} + +function assertViewFailsWithAPIStrict(pipeline) { + assert.commandFailedWithCode(db.runCommand({ + create: 'new_50_feature_view', + viewOn: collName, + pipeline: pipeline, + apiStrict: true, + apiVersion: "1" + }), + ErrorCodes.APIStrictError, + pipeline); +} + +function assertViewSucceedsWithAPIStrict(pipeline) { + assert.commandWorked(db.runCommand({ + create: 'new_50_feature_view', + viewOn: collName, + pipeline: pipeline, + apiStrict: true, + apiVersion: "1" + })); + + assert.commandWorked(db.runCommand({drop: 'new_50_feature_view'})); +} + for (let pipeline of stablePipelines) { // Assert error thrown when running a pipeline with stages not in API Version 1. APIVersionHelpers.assertAggregateSucceedsWithAPIStrict(pipeline, collName); // Assert error thrown when creating a view on a pipeline with stages not in API Version 1. - APIVersionHelpers.assertViewSucceedsWithAPIStrict(pipeline, viewName, collName); + assertViewSucceedsWithAPIStrict(pipeline); // Assert error is not thrown when running without apiStrict=true. assert.commandWorked(db.runCommand({ @@ -50,14 +90,14 @@ const setWindowFieldsPipeline = [{ output: {runningCount: {$sum: 1, window: {documents: ["unbounded", "current"]}}} } }]; -APIVersionHelpers.assertAggregateSucceedsWithAPIStrict(setWindowFieldsPipeline, collName); +assertAggregateSucceedsWithAPIStrict(setWindowFieldsPipeline); APIVersionHelpers.assertAggregateSucceedsWithAPIStrict( setWindowFieldsPipeline, collName, [ErrorCodes.InvalidOptions, ErrorCodes.OperationNotSupportedInTransaction]); -APIVersionHelpers.assertViewSucceedsWithAPIStrict(setWindowFieldsPipeline, viewName, collName); +APIVersionHelpers.assertViewSucceedsWithAPIStrict(setWindowFieldsPipeline, collName); // Creating a collection with dotted paths is allowed with apiStrict:true. diff --git a/jstests/core/api_version_new_51_language_features.js b/jstests/core/api_version_new_51_language_features.js index 48e73d7e052..5d923e3bd2f 100644 --- a/jstests/core/api_version_new_51_language_features.js +++ b/jstests/core/api_version_new_51_language_features.js @@ -12,7 +12,6 @@ load("jstests/libs/api_version_helpers.js"); // For 'APIVersionHelpers'. const collName = "api_version_new_51_language_features"; -const viewName = collName + "_view"; const coll = db[collName]; coll.drop(); assert.commandWorked(coll.insert({a: 1, date: new ISODate()})); @@ -27,7 +26,7 @@ for (let pipeline of stablePipelines) { APIVersionHelpers.assertAggregateSucceedsWithAPIStrict(pipeline, collName); // Assert error thrown when creating a view on a pipeline with stages not in API Version 1. - APIVersionHelpers.assertViewSucceedsWithAPIStrict(pipeline, viewName, collName); + APIVersionHelpers.assertViewSucceedsWithAPIStrict(pipeline, collName); // Assert error is not thrown when running without apiStrict=true. assert.commandWorked(db.runCommand({ diff --git a/jstests/core/api_version_new_52_language_features.js b/jstests/core/api_version_new_52_language_features.js index e50f2f96160..3fc21934853 100644 --- a/jstests/core/api_version_new_52_language_features.js +++ b/jstests/core/api_version_new_52_language_features.js @@ -11,21 +11,7 @@ "use strict"; load("jstests/libs/api_version_helpers.js"); // For 'APIVersionHelpers'. -function isFeatureFlagEnabled(featureFlag) { - const featureFlagParam = db.adminCommand({getParameter: 1, [featureFlag]: 1}); - return featureFlagParam.hasOwnProperty(featureFlag) && featureFlagParam[featureFlag]["value"]; -} - -// Since parallel test suite ignores feature flags in some scenarios we need explicitly check if the -// required flag is enabled. -if (!isFeatureFlagEnabled('featureFlagExactTopNAccumulator')) { - jsTestLog( - "Skipping the test because required feature flag 'featureFlagExactTopNAccumulator' is disabled"); - return; -} - const collName = "api_version_new_52_language_features"; -const viewName = collName + "_view"; const coll = db[collName]; coll.drop(); assert.commandWorked(coll.insert({a: 1, arr: [2, 1, 4]})); @@ -97,7 +83,7 @@ for (const pipeline of stablePipelines) { APIVersionHelpers.assertAggregateSucceedsWithAPIStrict(pipeline, collName); // Assert creating a view on a pipeline with stages in API Version 1 succeeds. - APIVersionHelpers.assertViewSucceedsWithAPIStrict(pipeline, viewName, collName); + APIVersionHelpers.assertViewSucceedsWithAPIStrict(pipeline, collName); // Assert error is not thrown when running without apiStrict=true. assert.commandWorked(db.runCommand({ diff --git a/jstests/core/api_version_test_expression.js b/jstests/core/api_version_test_expression.js index 3252ebda83e..41bbd9c0402 100644 --- a/jstests/core/api_version_test_expression.js +++ b/jstests/core/api_version_test_expression.js @@ -8,7 +8,6 @@ * assumes_unsharded_collection, * uses_api_parameters, * no_selinux, - * references_foreign_collection, * ] */ diff --git a/jstests/core/arrayfind8.js b/jstests/core/arrayfind8.js index ee74aae33ef..87a3a8d701a 100644 --- a/jstests/core/arrayfind8.js +++ b/jstests/core/arrayfind8.js @@ -81,6 +81,7 @@ function checkQuery(subQuery, bothMatch, elemMatch, nonElemMatch, additionalCons insertValueIfNotNull(bothMatch); insertValueIfNotNull(elemMatch); insertValueIfNotNull(nonElemMatch); + checkMatch(bothMatch, elemMatch, nonElemMatch, standardQuery, elemMatchQuery, 'unindexed'); // Check matching and index bounds for a single key index. diff --git a/jstests/core/awaitdata_getmore_cmd.js b/jstests/core/awaitdata_getmore_cmd.js index f2c766ca29b..8ad2634d27e 100644 --- a/jstests/core/awaitdata_getmore_cmd.js +++ b/jstests/core/awaitdata_getmore_cmd.js @@ -171,7 +171,7 @@ const topology = DiscoverTopology.findConnectedNodes(db.getMongo()); if (topology.type !== Topology.kStandalone) { const readConcern = assert.commandWorked(db.adminCommand({getDefaultRWConcern: 1})).defaultReadConcern; - if (readConcern.level == "majority" || TestData.defaultReadConcernLevel === "majority") { + if (readConcern.level == "majority") { return; } } @@ -214,8 +214,7 @@ assert.soon(() => db.await_data.findOne({_id: "signal parent shell"}) !== null); // Now issue a getMore which will match the parallel shell's currentOp filter, signalling it to // write a non-matching document into the collection. Confirm that we do not receive this // document and that we subsequently time out. -cmdRes = db.runCommand( - {getMore: cmdRes.cursor.id, collection: collName, maxTimeMS: ReplSetTest.kDefaultTimeoutMS}); +cmdRes = db.runCommand({getMore: cmdRes.cursor.id, collection: collName, maxTimeMS: 4000}); assert.commandWorked(cmdRes); jsTestLog("Waiting insertion shell to terminate..."); assert.eq(insertshell(), 0); diff --git a/jstests/core/bittest.js b/jstests/core/bittest.js index a0d81b64eef..a4a7272ae28 100644 --- a/jstests/core/bittest.js +++ b/jstests/core/bittest.js @@ -169,13 +169,6 @@ assert.commandWorked(coll.insert({a: 1.1})); assert.commandWorked(coll.insert({a: -1.1})); assert.commandWorked(coll.insert({a: -Infinity})); assert.commandWorked(coll.insert({a: +Infinity})); -assert.commandWorked(coll.insert({a: NumberDecimal("Infinity")})); -assert.commandWorked(coll.insert({a: NumberDecimal("-Infinity")})); -assert.commandWorked(coll.insert({a: NumberDecimal("NaN")})); -assert.commandWorked(coll.insert({a: NumberDecimal(Number.MAX_SAFE_LONG + 1)})); -assert.commandWorked(coll.insert({a: NumberDecimal(Number.MIN_SAFE_LONG - 1)})); -assert.commandWorked(coll.insert({a: NumberDecimal(1.1)})); -assert.commandWorked(coll.insert({a: NumberDecimal(-1.1)})); assert.commandWorked(coll.createIndex({a: 1})); assertQueryCorrect({a: {$bitsAllSet: 0}}, 5); diff --git a/jstests/core/bypass_doc_validation.js b/jstests/core/bypass_doc_validation.js index 1fd32db658b..d290508835a 100644 --- a/jstests/core/bypass_doc_validation.js +++ b/jstests/core/bypass_doc_validation.js @@ -6,7 +6,6 @@ // uses_map_reduce_with_temp_collections, // # Tenant migrations don't support applyOps. // tenant_migration_incompatible, -// references_foreign_collection, // ] /** diff --git a/jstests/core/bypass_empty_ts_replacement.js b/jstests/core/bypass_empty_ts_replacement.js deleted file mode 100644 index 9bd74f3f0ca..00000000000 --- a/jstests/core/bypass_empty_ts_replacement.js +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Tests the "bypassEmptyTsReplacement" option. - * - * @tags: [ - * requires_fcv_60, - * ] - */ -(function() { -"use strict"; - -const coll = db.jstests_bypass_empty_ts_replacement; -const collName = coll.getName(); -const emptyTs = Timestamp(0, 0); - -coll.drop(); - -function doInsert(docs, bypassEmptyTsReplacement) { - let cmdRes = db.runCommand( - {insert: collName, documents: docs, bypassEmptyTsReplacement: bypassEmptyTsReplacement}); - assert.commandWorked(cmdRes); -} - -function doUpdate(filter, update, bypassEmptyTsReplacement) { - let cmdRes = db.runCommand({ - update: collName, - updates: [{q: filter, u: update}], - bypassEmptyTsReplacement: bypassEmptyTsReplacement - }); - assert.commandWorked(cmdRes); -} - -function doUpdateWithUpsert(filter, update, bypassEmptyTsReplacement) { - let cmdRes = db.runCommand({ - update: collName, - updates: [{q: filter, u: update, upsert: true}], - bypassEmptyTsReplacement: bypassEmptyTsReplacement - }); - assert.commandWorked(cmdRes); -} - -function doFindAndModify(filter, update, bypassEmptyTsReplacement) { - let cmdRes = db.runCommand({ - findAndModify: collName, - query: filter, - update: update, - bypassEmptyTsReplacement: bypassEmptyTsReplacement - }); - assert.commandWorked(cmdRes); -} - -function doFindAndModifyWithUpsert(filter, update, bypassEmptyTsReplacement) { - let cmdRes = db.runCommand({ - findAndModify: collName, - query: filter, - update: update, - upsert: true, - bypassEmptyTsReplacement: bypassEmptyTsReplacement - }); - assert.commandWorked(cmdRes); -} - -let numCallsToRunTests = 0; - -function runTests(bypassEmptyTsReplacement) { - ++numCallsToRunTests; - - let first = (numCallsToRunTests == 1); - let startId = numCallsToRunTests * 100; - - function getId(i) { - return startId + i; - } - - // The insert/update/findAndModify commands below will be run with the bypassEmptyTsReplacement - // parameter set to true or false (depending whether the 'bypassEmptyTsReplacement' variable is - // true or false). - - // Insert several documents. - doInsert([{_id: getId(1), a: emptyTs}], bypassEmptyTsReplacement); - doInsert([{_id: getId(2), a: 1}], bypassEmptyTsReplacement); - doInsert([{_id: getId(3), a: 2}], bypassEmptyTsReplacement); - doInsert([{_id: getId(6), a: 3}], bypassEmptyTsReplacement); - doInsert([{_id: getId(7), a: 4}], bypassEmptyTsReplacement); - doInsert([{_id: getId(8), a: 5}], bypassEmptyTsReplacement); - doInsert([{_id: getId(9), a: 6}], bypassEmptyTsReplacement); - doInsert([{_id: getId(10), a: 7}], bypassEmptyTsReplacement); - doInsert([{_id: getId(11), a: 8}], bypassEmptyTsReplacement); - - // Use a replacement-style update to update the doc with _id=getId(2). - // - // When bypassEmptyTsReplacement=true, this is an example of how the special "empty timestamp" - // behavior can be suppressed when doing a replacement-style update. - doUpdate({_id: getId(2)}, {a: emptyTs}, bypassEmptyTsReplacement); - - // Use a replacement-style findAndModify to update the doc with _id=getId(3). - doFindAndModify({_id: getId(3)}, {a: emptyTs}, bypassEmptyTsReplacement); - - // Do a replacement-style update to add a new document with _id=getId(4). - doUpdateWithUpsert({_id: getId(4)}, {a: emptyTs}, bypassEmptyTsReplacement); - - // Do a replacement-style findAndModify to add a new document with _id=getId(5). - doFindAndModifyWithUpsert({_id: getId(5)}, {a: emptyTs}, bypassEmptyTsReplacement); - - // Do an update-operator-style update to update the doc with _id=getId(6). - doUpdate({_id: getId(6)}, {$set: {a: emptyTs}}, bypassEmptyTsReplacement); - - // Do an update-operator-style findAndModify to update the doc with _id=getId(7). - doFindAndModify({_id: getId(7)}, {$set: {a: emptyTs}}, bypassEmptyTsReplacement); - - // Do a pipeline-style update to update the doc with _id=getId(8). - doUpdate({_id: getId(8)}, [{$addFields: {a: emptyTs}}], bypassEmptyTsReplacement); - - // Do a pipeline-style findAndModify to update the doc with _id=getId(9). - doFindAndModify({_id: getId(9)}, [{$addFields: {a: emptyTs}}], bypassEmptyTsReplacement); - - // Do a pipeline-style update with $internalApplyOplogUpdate to update the doc with - // _id=getId(10). - doUpdate({_id: getId(10)}, - [{$_internalApplyOplogUpdate: {oplogUpdate: {$v: 2, diff: {i: {a: emptyTs}}}}}], - bypassEmptyTsReplacement); - - // Do a pipeline-style findAndModify with $internalApplyOplogUpdate to update the doc with - // _id=getId(11). - doFindAndModify({_id: getId(11)}, - [{$_internalApplyOplogUpdate: {oplogUpdate: {$v: 2, diff: {i: {a: emptyTs}}}}}], - bypassEmptyTsReplacement); - - // Do an update-operator-style update to add a new document with _id=getId(12). - doUpdateWithUpsert({_id: getId(12)}, {$set: {a: emptyTs}}, bypassEmptyTsReplacement); - - // Do an update-operator-style findAndModify to add a new document with _id=getId(13). - doFindAndModifyWithUpsert({_id: getId(13)}, {$set: {a: emptyTs}}, bypassEmptyTsReplacement); - - // Do a pipeline-style update to add a new document with _id=getId(14). - doUpdateWithUpsert({_id: getId(14)}, [{$addFields: {a: emptyTs}}], bypassEmptyTsReplacement); - - // Do a pipeline-style findAndModify to add a new document with _id=getId(15). - doFindAndModifyWithUpsert( - {_id: getId(15)}, [{$addFields: {a: emptyTs}}], bypassEmptyTsReplacement); - - // Do a pipline-style update with $internalApplyOplogUpdate to add a new document with - // _id=getId(16). - doUpdateWithUpsert( - {_id: getId(16)}, - [{$_internalApplyOplogUpdate: {oplogUpdate: {$v: 2, diff: {i: {a: emptyTs}}}}}], - bypassEmptyTsReplacement); - - // Do pipeline-style findAndModify with $internalApplyOplogUpdate to add a new document - // with _id=getId(17). - doFindAndModifyWithUpsert( - {_id: getId(17)}, - [{$_internalApplyOplogUpdate: {oplogUpdate: {$v: 2, diff: {i: {a: emptyTs}}}}}], - bypassEmptyTsReplacement); - - // Verify that all the insert, update, and findAndModify commands behaved the way we expected. - // If 'bypassEmptyTsReplacement' is true, then we expect field "a" will be equal to the empty - // timestamp for all 17 documents. If 'bypassEmptyTsReplacement' is false, then we expect - // field "a" will be equal to the current timestamp for the first 5 documents and it will - // be equal to the empty timestamp for the other 12 documents. - for (let i = 1; i <= 17; ++i) { - let result = coll.findOne({_id: getId(i)}); - - if (bypassEmptyTsReplacement || i >= 6) { - assert.eq(tojson(result.a), tojson(emptyTs), "_id=" + getId(i)); - } else { - assert.neq(tojson(result.a), tojson(emptyTs), "_id=" + getId(i)); - } - } - - if (first) { - // If this is the first time that runTests() is invoked, insert a document with - // _id=Timestamp(0,0), and then verify that the document we just inserted can be - // retrieved using the filter "{_id: Timestamp(0,0)}". - doInsert([{_id: emptyTs, a: 9}], bypassEmptyTsReplacement); - - let result = coll.findOne({_id: emptyTs}); - assert.eq(tojson(result._id), tojson(emptyTs), "_id=" + tojson(emptyTs)); - assert.eq(tojson(result.a), tojson(9), "_id=" + tojson(emptyTs)); - } - - // Do a replacement-style update on the document with _id=Timestamp(0,0). - doUpdate({_id: emptyTs}, {_id: emptyTs, a: emptyTs}, bypassEmptyTsReplacement); - - // Verify the document we just updated can still be retrieved using "{_id: Timestamp(0,0)}". - let result = coll.findOne({_id: emptyTs}); - assert.eq(tojson(result._id), tojson(emptyTs), "_id=" + tojson(emptyTs)); - - if (bypassEmptyTsReplacement) { - // Verify that an empty timestamp value was stored in field "a". - assert.eq(tojson(result.a), tojson(emptyTs), "_id=" + tojson(emptyTs)); - } else { - // Verify that field "a" was set to the current timestamp. - assert.neq(tojson(result.a), tojson(emptyTs), "_id=" + tojson(emptyTs)); - } -} - -let bypassEmptyTsReplacement = true; -runTests(bypassEmptyTsReplacement); - -bypassEmptyTsReplacement = false; -runTests(bypassEmptyTsReplacement); -}()); diff --git a/jstests/core/bypass_empty_ts_replacement_timeseries.js b/jstests/core/bypass_empty_ts_replacement_timeseries.js deleted file mode 100644 index 69a5bcf281c..00000000000 --- a/jstests/core/bypass_empty_ts_replacement_timeseries.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Tests inserts, updates, and upserts on a timeseries collection with "bypassEmptyTsReplacement" - * set to true. - * - * @tags: [ - * assumes_no_implicit_collection_creation_after_drop, - * does_not_support_transactions, - * requires_fcv_60, - * requires_multi_updates, - * requires_non_retryable_writes, - * requires_timeseries, - * ] - */ -(function() { -"use strict"; - -const coll = db.jstests_bypass_empty_ts_replacement_timeseries; -const collName = coll.getName(); -const emptyTs = Timestamp(0, 0); - -coll.drop(); - -assert.commandWorked(db.createCollection(collName, {timeseries: {timeField: "t", metaField: "m"}})); - -function doInsert(docs, bypassEmptyTsReplacement) { - let cmdRes = db.runCommand( - {insert: collName, documents: docs, bypassEmptyTsReplacement: bypassEmptyTsReplacement}); - assert.commandWorked(cmdRes); -} - -function doUpdate(filter, update, bypassEmptyTsReplacement) { - let cmdRes = db.runCommand({ - update: collName, - updates: [{q: filter, u: update, multi: true}], - bypassEmptyTsReplacement: bypassEmptyTsReplacement - }); - assert.commandWorked(cmdRes); -} - -let numCallsToRunTests = 0; - -function runTests(bypassEmptyTsReplacement) { - ++numCallsToRunTests; - - let startId = numCallsToRunTests * 100; - - function getId(i) { - return startId + i; - } - - // Insert two documents. - doInsert([{_id: getId(1), t: new Date(100), m: emptyTs, a: emptyTs}], bypassEmptyTsReplacement); - doInsert([{_id: getId(2), t: new Date(200), m: getId(2), a: emptyTs}], - bypassEmptyTsReplacement); - - // Do a replacement-style update containing an empty timestamp value. - doUpdate({m: getId(2)}, {$set: {m: emptyTs}}, bypassEmptyTsReplacement); - - // Verify that the commands above didn't mutate any of the empty timestamp values in the - // collection. - for (let i = 1; i <= 2; ++i) { - let result = coll.findOne({_id: getId(i)}); - assert.eq(tojson(result.m), tojson(emptyTs), "_id=" + getId(i)); - assert.eq(tojson(result.a), tojson(emptyTs), "_id=" + getId(i)); - } -} - -let bypassEmptyTsReplacement = true; -runTests(bypassEmptyTsReplacement); - -bypassEmptyTsReplacement = false; -runTests(bypassEmptyTsReplacement); -}()); diff --git a/jstests/core/capped_large_docs.js b/jstests/core/capped_large_docs.js index 310562fbb6c..f32a3e33389 100644 --- a/jstests/core/capped_large_docs.js +++ b/jstests/core/capped_large_docs.js @@ -2,10 +2,6 @@ * Tests inserting large documents into a capped collection. * * @tags: [ - * # Suites with stepdowns will result in replication rollback, and we do not update tracked - * # collection size for replication rollback. Thus if this occurs during the inserts, we can - * # end up with fewer documents in the capped collection than we would otherwise expect. - * does_not_support_stepdowns, * requires_capped, * requires_collstats, * requires_fastcount, diff --git a/jstests/core/check_shard_index.js b/jstests/core/check_shard_index.js index 23f69c6b757..61e84c038b1 100644 --- a/jstests/core/check_shard_index.js +++ b/jstests/core/check_shard_index.js @@ -21,12 +21,12 @@ f.createIndex({x: 1, y: 1}); assert.eq(0, f.count(), "1. initial count should be zero"); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandWorked(res, "1a " + tojson(res)); +assert.eq(true, res.ok, "1a"); f.save({x: 1, y: 1}); assert.eq(1, f.count(), "1. count after initial insert should be 1"); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandWorked(res, "1b " + tojson(res)); +assert.eq(true, res.ok, "1b"); // ------------------------- // Case 2: entry with null values would make an index suitable @@ -40,16 +40,16 @@ f.save({x: 1, y: 1}); f.save({x: null, y: 1}); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandWorked(res, "2a " + tojson(res)); +assert.eq(true, res.ok, "2a " + tojson(res)); f.save({y: 2}); assert.eq(3, f.count(), "2. count after initial insert should be 3"); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandWorked(res, "2b " + tojson(res)); +assert.eq(true, res.ok, "2b " + tojson(res)); // Check _id index res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {_id: 1}}); -assert.commandWorked(res, "2c " + tojson(res)); +assert.eq(true, res.ok, "2c " + tojson(res)); assert(res.idskip, "2d " + tojson(res)); // ------------------------- @@ -65,7 +65,7 @@ f.save({x: [1, 2], y: 2}); assert.eq(2, f.count(), "3. count after initial insert should be 2"); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res, "3a " + tojson(res)); +assert.eq(false, res.ok, "3a " + tojson(res)); f.remove({y: 2}); f.dropIndex({x: 1, y: 1}); @@ -73,13 +73,13 @@ f.createIndex({x: 1, y: 1}); assert.eq(1, f.count(), "3. count after removing array value should be 1"); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandWorked(res, "3b " + tojson(res)); +assert.eq(true, res.ok, "3b " + tojson(res)); f.save({x: 2, y: [1, 2]}); assert.eq(2, f.count(), "3. count after adding array value should be 2"); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res, "3c " + tojson(res)); +assert.eq(false, res.ok, "3c " + tojson(res)); // ------------------------- // Case 4: Handles prefix shard key indexes. @@ -93,21 +93,21 @@ f.save({x: 1, y: 1, z: 1}); assert.eq(1, f.count(), "4. count after initial insert should be 1"); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1}}); -assert.commandWorked(res, "4a " + tojson(res)); +assert.eq(true, res.ok, "4a " + tojson(res)); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandWorked(res, "4b " + tojson(res)); +assert.eq(true, res.ok, "4b " + tojson(res)); f.save({x: [1, 2], y: 2, z: 2}); assert.eq(2, f.count(), "4. count after adding array value should be 2"); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1}}); -assert.commandFailed(res, "4c " + tojson(res)); +assert.eq(false, res.ok, "4c " + tojson(res)); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res, "4d " + tojson(res)); +assert.eq(false, res.ok, "4d " + tojson(res)); res = db.runCommand( {checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1, z: 1}}); -assert.commandFailed(res, "4e " + tojson(res)); +assert.eq(false, res.ok, "4e " + tojson(res)); f.remove({y: 2}); f.dropIndex({x: 1, y: 1, z: 1}); @@ -116,18 +116,18 @@ f.createIndex({x: 1, y: 1, z: 1}); assert.eq(1, f.count(), "4. count after removing array value should be 1"); res = db.runCommand( {checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1, z: 1}}); -assert.commandWorked(res, "4f " + tojson(res)); +assert.eq(true, res.ok, "4f " + tojson(res)); f.save({x: 3, y: [1, 2], z: 3}); assert.eq(2, f.count(), "4. count after adding array value on second key should be 2"); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1}}); -assert.commandFailed(res, "4g " + tojson(res)); +assert.eq(false, res.ok, "4g " + tojson(res)); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res, "4h " + tojson(res)); +assert.eq(false, res.ok, "4h " + tojson(res)); res = db.runCommand( {checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1, z: 1}}); -assert.commandFailed(res, "4i " + tojson(res)); +assert.eq(false, res.ok, "4i " + tojson(res)); f.remove({x: 3}); // Necessary so that the index is no longer marked as multikey @@ -137,82 +137,17 @@ f.createIndex({x: 1, y: 1, z: 1}); assert.eq(1, f.count(), "4. count after removing array value should be 1 again"); res = db.runCommand( {checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1, z: 1}}); -assert.commandWorked(res, "4e " + tojson(res)); +assert.eq(true, res.ok, "4e " + tojson(res)); f.save({x: 4, y: 4, z: [1, 2]}); assert.eq(2, f.count(), "4. count after adding array value on third key should be 2"); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1}}); -assert.commandFailed(res, "4c " + tojson(res)); +assert.eq(false, res.ok, "4c " + tojson(res)); res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res, "4d " + tojson(res)); +assert.eq(false, res.ok, "4d " + tojson(res)); res = db.runCommand( {checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1, z: 1}}); -assert.commandFailed(res, "4e " + tojson(res)); - -// ------------------------- -// Test error messages of checkShardingIndex failing: - -// Shard key is not a prefix of index key: -f.drop(); -f.createIndex({x: 1}); -res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {y: 1}}); -assert.commandFailed(res); -assert(res.errmsg.includes("Shard key is not a prefix of index key.")); - -// Index key is partial: -f.drop(); -f.createIndex({x: 1, y: 1}, {partialFilterExpression: {y: {$gt: 0}}}); -res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res); -assert(res.errmsg.includes("Index key is partial.")); - -// Index key is sparse: -f.drop(); -f.createIndex({x: 1, y: 1}, {sparse: true}); -res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res); -assert(res.errmsg.includes("Index key is sparse.")); - -// Index key is multikey: -f.drop(); -f.createIndex({x: 1, y: 1}); -f.save({y: [1, 2, 3, 4, 5]}); -res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res); -assert(res.errmsg.includes("Index key is multikey.")); - -// Index key has a non-simple collation: -f.drop(); -f.createIndex({x: 1, y: 1}, {collation: {locale: "en"}}); -res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res); -assert(res.errmsg.includes("Index has a non-simple collation.")); - -// Index key is sparse and index has non-simple collation: -f.drop(); -f.createIndex({x: 1, y: 1}, {sparse: true, collation: {locale: "en"}}); -res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res); -assert(res.errmsg.includes("Index key is sparse.") && - res.errmsg.includes("Index has a non-simple collation.")); - -// Multiple incompatible indexes: Index key is multikey and is partial: -f.drop(); -f.createIndex({x: 1, y: 1}, {name: "index_1_part", partialFilterExpression: {x: {$gt: 0}}}); -f.createIndex({x: 1, y: 1}, {name: "index_2"}); -f.save({y: [1, 2, 3, 4, 5]}); -res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res); -assert(res.errmsg.includes("Index key is multikey.") && - res.errmsg.includes("Index key is partial.")); - -// Multiple incompatible indexes: Index key is partial and sparse: -f.drop(); -f.createIndex({x: 1, y: 1}, {name: "index_1_part", partialFilterExpression: {x: {$gt: 0}}}); -f.createIndex({x: 1, y: 1}, {name: "index_2_sparse", sparse: true}); -res = db.runCommand({checkShardingIndex: "test.jstests_shardingindex", keyPattern: {x: 1, y: 1}}); -assert.commandFailed(res); -assert(res.errmsg.includes("Index key is partial.") && res.errmsg.includes("Index key is sparse.")); +assert.eq(false, res.ok, "4e " + tojson(res)); print("PASSED"); diff --git a/jstests/core/clustered_collection_collation.js b/jstests/core/clustered_collection_collation.js index 585425d6fac..d96afb7730d 100644 --- a/jstests/core/clustered_collection_collation.js +++ b/jstests/core/clustered_collection_collation.js @@ -35,9 +35,6 @@ const incompatibleCollation = { locale: "fr_CA", strength: 2 }; -const simpleCollation = { - locale: "simple", -}; assert.commandWorked(db.createCollection( collatedName, {clusteredIndex: {key: {_id: 1}, unique: true}, collation: defaultCollation})); @@ -144,7 +141,7 @@ const verifyNoTightBoundsAndFindsN = function(coll, expected, predicate, queryCo const max = res.queryPlanner.winningPlan.maxRecord; assert.neq(null, min, "No min bound"); assert.neq(null, max, "No max bound"); - assert(min !== max, "COLLSCAN bounds are equal"); + assert.neq(min, max, "COLLSCAN bounds are equal"); assert.eq(expected, coll.find(predicate).count(), "Didn't find the expected records"); }; @@ -153,77 +150,33 @@ const testBounds = function(coll, expected, defaultCollation) { verifyHasBoundsAndFindsN(coll, 1, {_id: 5}); verifyHasBoundsAndFindsN(coll, 1, {_id: {int: 5}}); verifyHasBoundsAndFindsN(coll, 1, {_id: {ints: [5, 10]}}); - verifyNoTightBoundsAndFindsN(coll, 2, {_id: {$in: [5, {ints: [5, 10]}]}}); // Test non string types with incompatible collations. verifyHasBoundsAndFindsN(coll, 1, {_id: 5}, incompatibleCollation); verifyHasBoundsAndFindsN(coll, 1, {_id: {int: 5}}, incompatibleCollation); verifyHasBoundsAndFindsN(coll, 1, {_id: {ints: [5, 10]}}, incompatibleCollation); - verifyNoTightBoundsAndFindsN( - coll, 2, {_id: {$in: [5, {ints: [5, 10]}]}}, incompatibleCollation); // Test strings respect the collation. verifyHasBoundsAndFindsN(coll, expected, {_id: "A"}); verifyHasBoundsAndFindsN(coll, expected, {_id: {str: "A"}}); verifyHasBoundsAndFindsN(coll, expected, {_id: {strs: ["A", "b"]}}); verifyHasBoundsAndFindsN(coll, expected, {_id: {strs: ["a", "B"]}}); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: ["A", 1]}}); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: ["A", "C"]}}); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: ["", {str: "A"}]}}); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: [{}, {strs: ["A", "b"]}]}}); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: [[], {strs: ["a", "B"]}]}}); // Test strings not in the _id field verifyNoBoundsAndFindsN(coll, expected, {data: ["A", "b"]}); verifyNoBoundsAndFindsN(coll, expected, {data: ["a", "B"]}); - // Test non compatible query collations don't generate exact bounds. This means, the bounds - // generated are with respect to the KeyString encoding of the data type of the query. For - // example, an _id: <string> query will be bounded by min and max values for type 'string', but - // not bounded by the exact value of <string>. + // Test non compatible query collations don't generate bounds verifyNoTightBoundsAndFindsN(coll, expected, {_id: "A"}, incompatibleCollation); verifyNoTightBoundsAndFindsN(coll, expected, {_id: {str: "A"}}, incompatibleCollation); verifyNoTightBoundsAndFindsN(coll, expected, {_id: {strs: ["A", "b"]}}, incompatibleCollation); verifyNoTightBoundsAndFindsN(coll, expected, {_id: {strs: ["a", "B"]}}, incompatibleCollation); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: ["A", 1]}}, incompatibleCollation); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: ["A", "C"]}}, incompatibleCollation); - verifyNoTightBoundsAndFindsN( - coll, expected, {_id: {$in: ["", {str: "A"}]}}, incompatibleCollation); - verifyNoTightBoundsAndFindsN( - coll, expected, {_id: {$in: [{}, {strs: ["A", "b"]}]}}, incompatibleCollation); - verifyNoTightBoundsAndFindsN( - coll, expected, {_id: {$in: [[], {strs: ["a", "B"]}]}}, incompatibleCollation); - - if (defaultCollation != undefined && defaultCollation.locale != simpleCollation.locale) { - // 'Simple' collations are treated differently than non-simple queries since they are the - // default 'locale' when a collation is not specified. Test that the 'simple' collation is - // not compatible when the clustered collection has a non-simple collation. - verifyNoTightBoundsAndFindsN(coll, expected, {_id: "A"}, simpleCollation); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {str: "A"}}, simpleCollation); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {strs: ["A", "b"]}}, simpleCollation); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {strs: ["a", "B"]}}, simpleCollation); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: ["A", 1]}}, simpleCollation); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: ["A", "C"]}}, simpleCollation); - verifyNoTightBoundsAndFindsN( - coll, expected, {_id: {$in: ["", {str: "A"}]}}, simpleCollation); - verifyNoTightBoundsAndFindsN( - coll, expected, {_id: {$in: [{}, {strs: ["A", "b"]}]}}, simpleCollation); - verifyNoTightBoundsAndFindsN( - coll, expected, {_id: {$in: [[], {strs: ["a", "B"]}]}}, simpleCollation); - } // Test compatible query collations generate bounds verifyHasBoundsAndFindsN(coll, expected, {_id: "A"}, defaultCollation); verifyHasBoundsAndFindsN(coll, expected, {_id: {str: "A"}}, defaultCollation); verifyHasBoundsAndFindsN(coll, expected, {_id: {strs: ["A", "b"]}}, defaultCollation); verifyHasBoundsAndFindsN(coll, expected, {_id: {strs: ["a", "B"]}}, defaultCollation); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: ["A", 1]}}, defaultCollation); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: ["A", "C"]}}, defaultCollation); - verifyNoTightBoundsAndFindsN(coll, expected, {_id: {$in: ["", {str: "A"}]}}, defaultCollation); - verifyNoTightBoundsAndFindsN( - coll, expected, {_id: {$in: [{}, {strs: ["A", "b"]}]}}, defaultCollation); - verifyNoTightBoundsAndFindsN( - coll, expected, {_id: {$in: [[], {strs: ["a", "B"]}]}}, defaultCollation); }; insertDocuments(collated); diff --git a/jstests/core/collation.js b/jstests/core/collation.js index 558d1af428c..6a7ff93c9c7 100644 --- a/jstests/core/collation.js +++ b/jstests/core/collation.js @@ -24,19 +24,18 @@ load("jstests/libs/sbe_explain_helpers.js"); // For engineSpecificAssertion. // For areAllCollectionsClustered. load("jstests/libs/clustered_collections/clustered_collection_util.js"); -let testDb = db.getSiblingDB("collation_js"); -var coll = testDb.collation; +var coll = db.collation; coll.drop(); var explainRes; var writeRes; var planStage; -var hello = testDb.runCommand("hello"); +var hello = db.runCommand("hello"); assert.commandWorked(hello); var isMongos = (hello.msg === "isdbgrid"); var isStandalone = !isMongos && !hello.hasOwnProperty('setName'); -var isClustered = ClusteredCollectionUtil.areAllCollectionsClustered(testDb); +var isClustered = ClusteredCollectionUtil.areAllCollectionsClustered(db); var assertIndexHasCollation = function(keyPattern, collation) { var indexSpecs = coll.getIndexes(); @@ -62,38 +61,35 @@ var getQueryCollation = function(explainRes) { }; // -// Test using testDb.createCollection() to make a collection with a default collation. +// Test using db.createCollection() to make a collection with a default collation. // // Attempting to create a collection with an invalid collation should fail. -assert.commandFailed(testDb.createCollection("collation", {collation: "not an object"})); -assert.commandFailed(testDb.createCollection("collation", {collation: {}})); -assert.commandFailed(testDb.createCollection("collation", {collation: {blah: 1}})); -assert.commandFailed(testDb.createCollection("collation", {collation: {locale: "en", blah: 1}})); -assert.commandFailed(testDb.createCollection("collation", {collation: {locale: "xx"}})); -assert.commandFailed( - testDb.createCollection("collation", {collation: {locale: "en", strength: 99}})); -assert.commandFailed( - testDb.createCollection("collation", {collation: {locale: "en", strength: 9.9}})); +assert.commandFailed(db.createCollection("collation", {collation: "not an object"})); +assert.commandFailed(db.createCollection("collation", {collation: {}})); +assert.commandFailed(db.createCollection("collation", {collation: {blah: 1}})); +assert.commandFailed(db.createCollection("collation", {collation: {locale: "en", blah: 1}})); +assert.commandFailed(db.createCollection("collation", {collation: {locale: "xx"}})); +assert.commandFailed(db.createCollection("collation", {collation: {locale: "en", strength: 99}})); +assert.commandFailed(db.createCollection("collation", {collation: {locale: "en", strength: 9.9}})); // Attempting to create a collection whose collation version does not match the collator version // produced by ICU should result in failure with a special error code. assert.commandFailedWithCode( - testDb.createCollection("collation", {collation: {locale: "en", version: "unknownVersion"}}), + db.createCollection("collation", {collation: {locale: "en", version: "unknownVersion"}}), ErrorCodes.IncompatibleCollationVersion); // Ensure we can create a collection with the "simple" collation as the collection default. -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "simple"}})); -var collectionInfos = testDb.getCollectionInfos({name: coll.getName()}); +assert.commandWorked(db.createCollection("collation", {collation: {locale: "simple"}})); +var collectionInfos = db.getCollectionInfos({name: "collation"}); assert.eq(collectionInfos.length, 1); assert(!collectionInfos[0].options.hasOwnProperty("collation")); +coll.drop(); // Ensure that we populate all collation-related fields when we create a collection with a valid // collation. -coll = testDb.collation_frCA; -coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); -var collectionInfos = testDb.getCollectionInfos({name: coll.getName()}); +assert.commandWorked(db.createCollection("collation", {collation: {locale: "fr_CA"}})); +var collectionInfos = db.getCollectionInfos({name: "collation"}); assert.eq(collectionInfos.length, 1); assert.eq(collectionInfos[0].options.collation, { locale: "fr_CA", @@ -180,8 +176,8 @@ if (isStandalone) { assertIndexHasCollation({c: 1}, {locale: "simple"}); } -coll = testDb.collation_index1; coll.drop(); + // // Creating an index with a collation. // @@ -259,7 +255,6 @@ assertIndexHasCollation({e: 1}, {locale: "simple"}); // Test that an index with a non-simple collation contains collator-generated comparison keys // rather than the verbatim indexed strings. -coll = testDb.collation_index2; coll.drop(); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "fr_CA"}})); assert.commandWorked(coll.createIndex({b: 1})); @@ -271,41 +266,37 @@ assert.eq("foo", coll.find().collation({locale: "fr_CA"}).hint({b: 1}).returnKey // Test that a query with a string comparison can use an index with a non-simple collation if it // has a matching collation. -coll = testDb.collation_index3; coll.drop(); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "fr_CA"}})); // Query has simple collation, but index has fr_CA collation. explainRes = coll.find({a: "foo"}).explain(); assert.commandWorked(explainRes); -assert(planHasStage(testDb, getWinningPlan(explainRes.queryPlanner), "COLLSCAN")); +assert(planHasStage(db, getWinningPlan(explainRes.queryPlanner), "COLLSCAN")); // Query has en_US collation, but index has fr_CA collation. explainRes = coll.find({a: "foo"}).collation({locale: "en_US"}).explain(); assert.commandWorked(explainRes); -assert(planHasStage(testDb, getWinningPlan(explainRes.queryPlanner), "COLLSCAN")); +assert(planHasStage(db, getWinningPlan(explainRes.queryPlanner), "COLLSCAN")); // Matching collations. explainRes = coll.find({a: "foo"}).collation({locale: "fr_CA"}).explain(); assert.commandWorked(explainRes); -assert(planHasStage(testDb, getWinningPlan(explainRes.queryPlanner), "IXSCAN")); +assert(planHasStage(db, getWinningPlan(explainRes.queryPlanner), "IXSCAN")); // Should not be possible to create a text index with an explicit non-simple collation. -coll = testDb.collation_index3; coll.drop(); assert.commandFailed(coll.createIndex({a: "text"}, {collation: {locale: "en"}})); // Text index builds which inherit a non-simple default collation should fail. -coll = testDb.collation_en1; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en"}})); assert.commandFailed(coll.createIndex({a: "text"})); // Text index build should succeed on a collection with a non-simple default collation if it // explicitly overrides the default with {locale: "simple"}. -coll = testDb.collation_en2; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en"}})); assert.commandWorked(coll.createIndex({a: "text"}, {collation: {locale: "simple"}})); // @@ -314,12 +305,10 @@ assert.commandWorked(coll.createIndex({a: "text"}, {collation: {locale: "simple" // Aggregation should return correct results when collation specified and collection does not // exist. -coll = testDb.collation_agg1; coll.drop(); assert.eq(0, coll.aggregate([], {collation: {locale: "fr"}}).itcount()); // Aggregation should return correct results when collation specified and collection does exist. -coll = testDb.collation_agg2; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "bar"})); @@ -330,39 +319,35 @@ assert.eq(1, // Aggregation should return correct results when no collation specified and collection has a // default collation. -coll = testDb.collation_agg3; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({str: "foo"})); assert.eq(1, coll.aggregate([{$match: {str: "FOO"}}]).itcount()); // Aggregation should return correct results when "simple" collation specified and collection // has a default collation. -coll = testDb.collation_agg4; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({str: "foo"})); assert.eq(0, coll.aggregate([{$match: {str: "FOO"}}], {collation: {locale: "simple"}}).itcount()); // Aggregation should select compatible index when no collation specified and collection has a // default collation. -coll = testDb.collation_agg5; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "en_US"}})); var explain = coll.explain("queryPlanner").aggregate([{$match: {a: "foo"}}]); -assert(isIxscan(testDb, getWinningPlan(explain.queryPlanner))); +assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); // Aggregation should not use index when no collation specified and collection default // collation is incompatible with index collation. -coll = testDb.collation_agg6; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "simple"}})); var explain = coll.explain("queryPlanner").aggregate([{$match: {a: "foo"}}]); -assert(isCollscan(testDb, getWinningPlan(explain.queryPlanner))); +assert(isCollscan(db, getWinningPlan(explain.queryPlanner))); // Explain of aggregation with collation should succeed. assert.commandWorked(coll.explain().aggregate([], {collation: {locale: "fr"}})); @@ -372,12 +357,10 @@ assert.commandWorked(coll.explain().aggregate([], {collation: {locale: "fr"}})); // // Count should return correct results when collation specified and collection does not exist. -coll = testDb.collation_count1; coll.drop(); assert.eq(0, coll.find({str: "FOO"}).collation({locale: "en_US"}).count()); // Count should return correct results when collation specified and collection does exist. -coll = testDb.collation_count1; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "bar"})); @@ -390,24 +373,21 @@ assert.eq(1, coll.count({str: "FOO"}, {collation: {locale: "en_US", strength: 2} // Count should return correct results when no collation specified and collection has a default // collation. -coll = testDb.collation_count3; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({str: "foo"})); assert.eq(1, coll.find({str: "FOO"}).count()); // Count should return correct results when "simple" collation specified and collection has a // default collation. -coll = testDb.collation_count4; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({str: "foo"})); assert.eq(0, coll.find({str: "FOO"}).collation({locale: "simple"}).count()); // Count should return correct results when collation specified and when run with explain. -coll = testDb.collation_count5; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "bar"})); @@ -426,7 +406,6 @@ assert.neq(null, planStage); assert.eq(1, planStage.advanced); // Explain of COUNT_SCAN stage should include index collation. -coll = testDb.collation_count6; coll.drop(); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "fr_CA"}})); explainRes = coll.explain("executionStats").find({a: 5}).count(); @@ -448,9 +427,8 @@ assert.eq(planStage.collation, { // Explain of COUNT_SCAN stage should include index collation when index collation is // inherited from collection default. -coll = testDb.collation_count7; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); assert.commandWorked(coll.createIndex({a: 1})); explainRes = coll.explain("executionStats").find({a: 5}).count(); assert.commandWorked(explainRes); @@ -470,14 +448,13 @@ assert.eq(planStage.collation, { }); // Should be able to use COUNT_SCAN for queries over strings. -coll = testDb.collation_count8; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); assert.commandWorked(coll.createIndex({a: 1})); explainRes = coll.explain("executionStats").find({a: "foo"}).count(); assert.commandWorked(explainRes); -assert(planHasStage(testDb, explainRes.executionStats.executionStages, "COUNT_SCAN")); -assert(!planHasStage(testDb, explainRes.executionStats.executionStages, "FETCH")); +assert(planHasStage(db, explainRes.executionStats.executionStages, "COUNT_SCAN")); +assert(!planHasStage(db, explainRes.executionStats.executionStages, "FETCH")); // // Collation tests for distinct. @@ -485,12 +462,10 @@ assert(!planHasStage(testDb, explainRes.executionStats.executionStages, "FETCH") // Distinct should return correct results when collation specified and collection does not // exist. -coll = testDb.collation_distinct1; coll.drop(); assert.eq(0, coll.distinct("str", {}, {collation: {locale: "en_US", strength: 2}}).length); // Distinct should return correct results when collation specified and no indexes exist. -coll = testDb.collation_distinct2; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "FOO"})); @@ -510,10 +485,9 @@ assert.eq(2, coll.distinct("str", {}, {collation: {locale: "en_US", strength: 3} // Distinct should return correct results when no collation specified and collection has a // default collation. -coll = testDb.collation_distinct3; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({str: "foo"})); assert.commandWorked(coll.insert({str: "FOO"})); assert.eq(1, coll.distinct("str").length); @@ -521,10 +495,9 @@ assert.eq(2, coll.distinct("_id", {str: "foo"}).length); // Distinct should return correct results when "simple" collation specified and collection has a // default collation. -coll = testDb.collation_distinct4; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({str: "foo"})); assert.commandWorked(coll.insert({str: "FOO"})); assert.eq(2, coll.distinct("str", {}, {collation: {locale: "simple"}}).length); @@ -532,45 +505,42 @@ assert.eq(1, coll.distinct("_id", {str: "foo"}, {collation: {locale: "simple"}}) // Distinct should select compatible index when no collation specified and collection has a // default collation. -coll = testDb.collation_distinct5; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "en_US"}})); var explain = coll.explain("queryPlanner").distinct("a"); -assert(planHasStage(testDb, getWinningPlan(explain.queryPlanner), "DISTINCT_SCAN")); -assert(planHasStage(testDb, getWinningPlan(explain.queryPlanner), "FETCH")); +assert(planHasStage(db, getWinningPlan(explain.queryPlanner), "DISTINCT_SCAN")); +assert(planHasStage(db, getWinningPlan(explain.queryPlanner), "FETCH")); // Distinct scan on strings can be used over an index with a collation when the predicate has // exact bounds. explain = coll.explain("queryPlanner").distinct("a", {a: {$gt: "foo"}}); -assert(planHasStage(testDb, getWinningPlan(explain.queryPlanner), "DISTINCT_SCAN")); -assert(planHasStage(testDb, getWinningPlan(explain.queryPlanner), "FETCH")); -assert(!planHasStage(testDb, getWinningPlan(explain.queryPlanner), "PROJECTION_COVERED")); +assert(planHasStage(db, getWinningPlan(explain.queryPlanner), "DISTINCT_SCAN")); +assert(planHasStage(db, getWinningPlan(explain.queryPlanner), "FETCH")); +assert(!planHasStage(db, getWinningPlan(explain.queryPlanner), "PROJECTION_COVERED")); // Distinct scan cannot be used over an index with a collation when the predicate has inexact // bounds. explain = coll.explain("queryPlanner").distinct("a", {a: {$exists: true}}); -assert(planHasStage(testDb, getWinningPlan(explain.queryPlanner), "IXSCAN")); -assert(planHasStage(testDb, getWinningPlan(explain.queryPlanner), "FETCH")); -assert(!planHasStage(testDb, getWinningPlan(explain.queryPlanner), "DISTINCT_SCAN")); +assert(planHasStage(db, getWinningPlan(explain.queryPlanner), "IXSCAN")); +assert(planHasStage(db, getWinningPlan(explain.queryPlanner), "FETCH")); +assert(!planHasStage(db, getWinningPlan(explain.queryPlanner), "DISTINCT_SCAN")); // Distinct scan can be used without a fetch when predicate has exact non-string bounds. explain = coll.explain("queryPlanner").distinct("a", {a: {$gt: 3}}); -assert(planHasStage(testDb, getWinningPlan(explain.queryPlanner), "DISTINCT_SCAN")); -assert(planHasStage(testDb, getWinningPlan(explain.queryPlanner), "PROJECTION_COVERED")); -assert(!planHasStage(testDb, getWinningPlan(explain.queryPlanner), "FETCH")); +assert(planHasStage(db, getWinningPlan(explain.queryPlanner), "DISTINCT_SCAN")); +assert(planHasStage(db, getWinningPlan(explain.queryPlanner), "PROJECTION_COVERED")); +assert(!planHasStage(db, getWinningPlan(explain.queryPlanner), "FETCH")); // Distinct should not use index when no collation specified and collection default collation is // incompatible with index collation. -coll = testDb.collation_distinct6; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "simple"}})); var explain = coll.explain("queryPlanner").distinct("a"); -assert(isCollscan(testDb, getWinningPlan(explain.queryPlanner))); +assert(isCollscan(db, getWinningPlan(explain.queryPlanner))); // Explain of DISTINCT_SCAN stage should include index collation. -coll = testDb.collation_distinct7; coll.drop(); assert.commandWorked(coll.createIndex({str: 1}, {collation: {locale: "fr_CA"}})); explainRes = coll.explain("executionStats").distinct("str", {}, {collation: {locale: "fr_CA"}}); @@ -592,9 +562,8 @@ assert.eq(planStage.collation, { // Explain of DISTINCT_SCAN stage should include index collation when index collation is // inherited from collection default. -coll = testDb.collation_distinct8; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); assert.commandWorked(coll.createIndex({str: 1})); explainRes = coll.explain("executionStats").distinct("str"); assert.commandWorked(explainRes); @@ -619,12 +588,10 @@ assert.eq(planStage.collation, { // Find should return correct results when collation specified and collection does not // exist. -coll = testDb.collation_find1; coll.drop(); assert.eq(0, coll.find({_id: "FOO"}).collation({locale: "en_US"}).itcount()); // Find should return correct results when collation specified and filter is a match on _id. -coll = testDb.collation_find2; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "bar"})); @@ -668,7 +635,6 @@ assert.commandWorked(coll.dropIndexes()); // Queries that use a index with a non-matching collation should add a sort // stage if needed. -coll = testDb.collation_find3; coll.drop(); assert.commandWorked(coll.insert([{a: "A"}, {a: "B"}, {a: "b"}, {a: "a"}])); @@ -681,7 +647,6 @@ res = coll.find({a: {'$exists': true}}, {_id: 0}).collation({locale: "en_US", st assert.eq(res.toArray(), [{a: "a"}, {a: "A"}, {a: "b"}, {a: "B"}]); // Find should return correct results when collation specified and query contains $expr. -coll = testDb.collation_find4; coll.drop(); assert.commandWorked(coll.insert([{a: "A"}, {a: "B"}])); assert.eq( @@ -689,10 +654,9 @@ assert.eq( // Find should return correct results when no collation specified and collection has a default // collation. -coll = testDb.collation_find5; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({str: "foo"})); assert.commandWorked(coll.insert({str: "FOO"})); assert.commandWorked(coll.insert({str: "bar"})); @@ -704,28 +668,25 @@ assert.eq([{str: "bar"}, {str: "foo"}, {str: "FOO"}], // Find with idhack should return correct results when no collation specified and collection has // a default collation. -coll = testDb.collation_find6; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: "foo"})); assert.eq(1, coll.find({_id: "FOO"}).itcount()); // Find should return correct results for query containing $expr when no collation specified and // collection has a default collation. -coll = testDb.collation_find7; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert([{a: "A"}, {a: "B"}])); assert.eq(1, coll.find({$expr: {$eq: ["$a", "a"]}}).itcount()); // Find should return correct results when "simple" collation specified and collection has a // default collation. -coll = testDb.collation_find8; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({str: "foo"})); assert.commandWorked(coll.insert({str: "FOO"})); assert.commandWorked(coll.insert({str: "bar"})); @@ -736,30 +697,27 @@ assert.eq([{str: "FOO"}, {str: "bar"}, {str: "foo"}], // Find on _id should return correct results when query collation differs from collection // default collation. -coll = testDb.collation_find9; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 3}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 3}})); assert.commandWorked(coll.insert({_id: "foo"})); assert.commandWorked(coll.insert({_id: "FOO"})); assert.eq(2, coll.find({_id: "foo"}).collation({locale: "en_US", strength: 2}).itcount()); if (!isClustered) { // Find on _id should use idhack stage when query inherits collection default collation. - coll = testDb.collation_find10; coll.drop(); - assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); + assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); explainRes = coll.explain("executionStats").find({_id: "foo"}).finish(); assert.commandWorked(explainRes); let classicAssert = null !== getPlanStage(getWinningPlan(explainRes.queryPlanner), "IDHACK"); let sbeAssert = null !== getPlanStage(getWinningPlan(explainRes.queryPlanner), "IXSCAN"); - engineSpecificAssertion(classicAssert, sbeAssert, testDb, explainRes); + engineSpecificAssertion(classicAssert, sbeAssert, db, explainRes); // Find on _id should use idhack stage when explicitly given query collation matches // collection default. - coll = testDb.collation_find11; coll.drop(); - assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); + assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); explainRes = coll.explain("executionStats").find({_id: "foo"}).collation({locale: "en_US"}).finish(); assert.commandWorked(explainRes); @@ -769,9 +727,8 @@ if (!isClustered) { // Find on _id should not use idhack stage when query collation does not match collection // default. - coll = testDb.collation_find12; coll.drop(); - assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); + assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); explainRes = coll.explain("executionStats").find({_id: "foo"}).collation({locale: "fr_CA"}).finish(); assert.commandWorked(explainRes); @@ -783,42 +740,37 @@ if (!isClustered) { // Find should select compatible index when no collation specified and collection has a default // collation. -coll = testDb.collation_find13; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "en_US"}})); var explain = coll.find({a: "foo"}).explain("queryPlanner"); -assert(isIxscan(testDb, getWinningPlan(explain.queryPlanner))); +assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); // Find should select compatible index when no collation specified and collection default // collation is "simple". -coll = testDb.collation_find14; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "simple"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "simple"}})); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "simple"}})); var explain = coll.find({a: "foo"}).explain("queryPlanner"); -assert(isIxscan(testDb, getWinningPlan(explain.queryPlanner))); +assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); // Find should not use index when no collation specified, index collation is "simple", and // collection has a non-"simple" default collation. -coll = testDb.collation_find15; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "simple"}})); var explain = coll.find({a: "foo"}).explain("queryPlanner"); -assert(isCollscan(testDb, getWinningPlan(explain.queryPlanner))); +assert(isCollscan(db, getWinningPlan(explain.queryPlanner))); // Find should select compatible index when "simple" collation specified and collection has a // non-"simple" default collation. -coll = testDb.collation_find16; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); assert.commandWorked(coll.createIndex({a: 1}, {collation: {locale: "simple"}})); var explain = coll.find({a: "foo"}).collation({locale: "simple"}).explain("queryPlanner"); -assert(isIxscan(testDb, getWinningPlan(explain.queryPlanner))); +assert(isIxscan(db, getWinningPlan(explain.queryPlanner))); // Find should return correct results when collation specified and run with explain. -coll = testDb.collation_find17; coll.drop(); assert.commandWorked(coll.insert({str: "foo"})); explainRes = @@ -833,7 +785,6 @@ assert.commandWorked(explainRes); assert.eq(1, explainRes.executionStats.nReturned); // Explain of find should include query collation. -coll = testDb.collation_find18; coll.drop(); explainRes = coll.explain("executionStats").find({str: "foo"}).collation({locale: "fr_CA"}).finish(); @@ -852,9 +803,8 @@ assert.eq(getQueryCollation(explainRes), { }); // Explain of find should include query collation when inherited from collection default. -coll = testDb.collation_find19; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); explainRes = coll.explain("executionStats").find({str: "foo"}).finish(); assert.commandWorked(explainRes); assert.eq(getQueryCollation(explainRes), { @@ -871,7 +821,6 @@ assert.eq(getQueryCollation(explainRes), { }); // Explain of IXSCAN stage should include index collation. -coll = testDb.collation_find20; coll.drop(); assert.commandWorked(coll.createIndex({str: 1}, {collation: {locale: "fr_CA"}})); explainRes = @@ -894,9 +843,8 @@ assert.eq(planStage.collation, { // Explain of IXSCAN stage should include index collation when index collation is inherited from // collection default. -coll = testDb.collation_find21; coll.drop(); -assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); assert.commandWorked(coll.createIndex({str: 1})); explainRes = coll.explain("executionStats").find({str: "foo"}).finish(); assert.commandWorked(explainRes); @@ -921,7 +869,6 @@ assert.eq(planStage.collation, { // findAndModify should return correct results when collation specified and collection does not // exist. -coll = testDb.collation_findmodify1; coll.drop(); assert.eq( null, @@ -929,7 +876,6 @@ assert.eq( {query: {str: "bar"}, update: {$set: {str: "baz"}}, new: true, collation: {locale: "fr"}})); // Update-findAndModify should return correct results when collation specified. -coll = testDb.collation_findmodify2; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "bar"})); @@ -953,7 +899,6 @@ assert.neq(null, planStage); assert.eq(1, planStage.nWouldModify); // Delete-findAndModify should return correct results when collation specified. -coll = testDb.collation_findmodify3; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "bar"})); @@ -974,10 +919,9 @@ assert.eq(1, planStage.nWouldDelete); // findAndModify should return correct results when no collation specified and collection has a // default collation. -coll = testDb.collation_findmodify4; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.eq({_id: 1, str: "foo"}, coll.findAndModify({query: {str: "FOO"}, update: {$set: {x: 1}}})); @@ -998,10 +942,9 @@ assert.eq({_id: 1, str: "foo", x: 4}, coll.findAndModify({query: {str: "FOO"}, r // findAndModify should return correct results when "simple" collation specified and collection // has a default collation. -coll = testDb.collation_findmodify5; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.eq(null, coll.findAndModify( @@ -1014,7 +957,6 @@ assert.eq(null, // // mapReduce should return correct results when collation specified and no indexes exist. -coll = testDb.collation_mapreduce1; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "bar"})); @@ -1031,10 +973,9 @@ assert.eq(mapReduceOut.results.length, 1); // mapReduce should return correct results when no collation specified and collection has a // default collation. -coll = testDb.collation_mapreduce2; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); var mapReduceOut = coll.mapReduce( function() { @@ -1049,10 +990,9 @@ assert.eq(mapReduceOut.results.length, 1); // mapReduce should return correct results when "simple" collation specified and collection has // a default collation. -coll = testDb.collation_mapreduce3; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); var mapReduceOut = coll.mapReduce( function() { @@ -1070,12 +1010,10 @@ assert.eq(mapReduceOut.results.length, 0); // // Remove should succeed when collation specified and collection does not exist. -coll = testDb.collation_remove1; coll.drop(); assert.commandWorked(coll.remove({str: "foo"}, {justOne: true, collation: {locale: "fr"}})); // Remove should return correct results when collation specified. -coll = testDb.collation_remove2; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1084,7 +1022,6 @@ assert.commandWorked(writeRes); assert.eq(1, writeRes.nRemoved); // Explain of remove should return correct results when collation specified. -coll = testDb.collation_remove3; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1097,10 +1034,9 @@ assert.eq(1, planStage.nWouldDelete); // Remove should return correct results when no collation specified and collection has a default // collation. -coll = testDb.collation_remove4; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); writeRes = coll.remove({str: "FOO"}, {justOne: true}); assert.commandWorked(writeRes); @@ -1108,10 +1044,9 @@ assert.eq(1, writeRes.nRemoved); // Remove with idhack should return correct results when no collation specified and collection // has a default collation. -coll = testDb.collation_remove5; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: "foo"})); writeRes = coll.remove({_id: "FOO"}, {justOne: true}); assert.commandWorked(writeRes); @@ -1127,9 +1062,8 @@ assert.eq(1, writeRes.nRemoved); if (!isClustered) { // Remove on _id should use idhack stage when query inherits collection default collation. - coll = testDb.collation_remove6; coll.drop(); - assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); + assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); explainRes = coll.explain("executionStats").remove({_id: "foo"}); assert.commandWorked(explainRes); planStage = getPlanStage(explainRes.executionStats.executionStages, "IDHACK"); @@ -1138,10 +1072,9 @@ if (!isClustered) { // Remove should return correct results when "simple" collation specified and collection has // a default collation. -coll = testDb.collation_remove7; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); writeRes = coll.remove({str: "FOO"}, {justOne: true, collation: {locale: "simple"}}); assert.commandWorked(writeRes); @@ -1149,10 +1082,9 @@ assert.eq(0, writeRes.nRemoved); // Remove on _id should return correct results when "simple" collation specified and // collection has a default collation. -coll = testDb.collation_remove8; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: "foo"})); writeRes = coll.remove({_id: "FOO"}, {justOne: true, collation: {locale: "simple"}}); assert.commandWorked(writeRes); @@ -1161,9 +1093,8 @@ assert.eq(0, writeRes.nRemoved); if (!isClustered) { // Remove on _id should use idhack stage when explicit query collation matches collection // default. - coll = testDb.collation_remove9; coll.drop(); - assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); + assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); explainRes = coll.explain("executionStats").remove({_id: "foo"}, {collation: {locale: "en_US"}}); assert.commandWorked(explainRes); @@ -1172,9 +1103,8 @@ if (!isClustered) { // Remove on _id should not use idhack stage when query collation does not match collection // default. - coll = testDb.collation_remove10; coll.drop(); - assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); + assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); explainRes = coll.explain("executionStats").remove({_id: "foo"}, {collation: {locale: "fr_CA"}}); assert.commandWorked(explainRes); @@ -1187,13 +1117,11 @@ if (!isClustered) { // // Update should succeed when collation specified and collection does not exist. -coll = testDb.collation_update1; coll.drop(); assert.commandWorked( coll.update({str: "foo"}, {$set: {other: 99}}, {multi: true, collation: {locale: "fr"}})); // Update should return correct results when collation specified. -coll = testDb.collation_update2; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1202,7 +1130,6 @@ writeRes = coll.update( assert.eq(2, writeRes.nModified); // Explain of update should return correct results when collation specified. -coll = testDb.collation_update3; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1217,10 +1144,9 @@ assert.eq(2, planStage.nWouldModify); // Update should return correct results when no collation specified and collection has a default // collation. -coll = testDb.collation_update4; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); writeRes = coll.update({str: "FOO"}, {$set: {other: 99}}); assert.commandWorked(writeRes); @@ -1228,10 +1154,9 @@ assert.eq(1, writeRes.nMatched); // Update with idhack should return correct results when no collation specified and collection // has a default collation. -coll = testDb.collation_update5; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: "foo"})); writeRes = coll.update({_id: "FOO"}, {$set: {other: 99}}); assert.commandWorked(writeRes); @@ -1239,9 +1164,8 @@ assert.eq(1, writeRes.nMatched); if (!isClustered) { // Update on _id should use idhack stage when query inherits collection default collation. - coll = testDb.collation_update6; coll.drop(); - assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); + assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); explainRes = coll.explain("executionStats").update({_id: "foo"}, {$set: {other: 99}}); assert.commandWorked(explainRes); planStage = getPlanStage(explainRes.executionStats.executionStages, "IDHACK"); @@ -1250,10 +1174,9 @@ if (!isClustered) { // Update should return correct results when "simple" collation specified and collection has // a default collation. -coll = testDb.collation_update7; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); writeRes = coll.update({str: "FOO"}, {$set: {other: 99}}, {collation: {locale: "simple"}}); assert.commandWorked(writeRes); @@ -1261,10 +1184,9 @@ assert.eq(0, writeRes.nModified); // Update on _id should return correct results when "simple" collation specified and // collection has a default collation. -coll = testDb.collation_update8; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: "foo"})); writeRes = coll.update({_id: "FOO"}, {$set: {other: 99}}, {collation: {locale: "simple"}}); assert.commandWorked(writeRes); @@ -1273,9 +1195,8 @@ assert.eq(0, writeRes.nModified); if (!isClustered) { // Update on _id should use idhack stage when explicitly given query collation matches // collection default. - coll = testDb.collation_update9; coll.drop(); - assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); + assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); explainRes = coll.explain("executionStats").update({_id: "foo"}, {$set: {other: 99}}, { collation: {locale: "en_US"} }); @@ -1285,9 +1206,8 @@ if (!isClustered) { // Update on _id should not use idhack stage when query collation does not match collection // default. - coll = testDb.collation_update10; coll.drop(); - assert.commandWorked(testDb.createCollection(coll.getName(), {collation: {locale: "en_US"}})); + assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "en_US"}})); explainRes = coll.explain("executionStats").update({_id: "foo"}, {$set: {other: 99}}, { collation: {locale: "fr_CA"} }); @@ -1301,9 +1221,8 @@ if (!isClustered) { // // $geoNear should fail when collation is specified but the collection does not exist. -coll = testDb.collation_geonear1; coll.drop(); -assert.commandFailedWithCode(testDb.runCommand({ +assert.commandFailedWithCode(db.runCommand({ aggregate: coll.getName(), cursor: {}, pipeline: [{ @@ -1317,10 +1236,9 @@ assert.commandFailedWithCode(testDb.runCommand({ ErrorCodes.NamespaceNotFound); // $geoNear rejects the now-deprecated "collation" option. -coll = testDb.collation_geonear2; coll.drop(); assert.commandWorked(coll.insert({geo: {type: "Point", coordinates: [0, 0]}, str: "abc"})); -assert.commandFailedWithCode(testDb.runCommand({ +assert.commandFailedWithCode(db.runCommand({ aggregate: coll.getName(), cursor: {}, pipeline: [{ @@ -1373,20 +1291,18 @@ assert.eq(1, coll.aggregate([geoNearStage], {collation: {locale: "en_US", streng // $geoNear should return correct results when no collation specified and collection has a // default collation. -coll = testDb.collation_geonear3; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.createIndex({geo: "2dsphere"})); assert.commandWorked(coll.insert({geo: {type: "Point", coordinates: [0, 0]}, str: "abc"})); assert.eq(1, coll.aggregate([geoNearStage]).itcount()); // $geoNear should return correct results when "simple" collation specified and collection has // a default collation. -coll = testDb.collation_geonear4; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.createIndex({geo: "2dsphere"})); assert.commandWorked(coll.insert({geo: {type: "Point", coordinates: [0, 0]}, str: "abc"})); assert.eq(0, coll.aggregate([geoNearStage], {collation: {locale: "simple"}}).itcount()); @@ -1397,7 +1313,6 @@ assert.eq(0, coll.aggregate([geoNearStage], {collation: {locale: "simple"}}).itc // Find with $nearSphere should return correct results when collation specified and // collection does not exist. -coll = testDb.collation_nearsphere1; coll.drop(); assert.eq( 0, @@ -1407,7 +1322,6 @@ assert.eq( // Find with $nearSphere should return correct results when collation specified and string // predicate not indexed. -coll = testDb.collation_nearsphere2; coll.drop(); assert.commandWorked(coll.insert({geo: {type: "Point", coordinates: [0, 0]}, str: "abc"})); assert.commandWorked(coll.createIndex({geo: "2dsphere"})); @@ -1472,7 +1386,6 @@ assert.eq( var bulk; // update(). -coll = testDb.collation_bulkupdate; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1483,7 +1396,6 @@ assert.commandWorked(writeRes); assert.eq(2, writeRes.nModified); // updateOne(). -coll = testDb.collation_bulkupdateone; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1494,7 +1406,6 @@ assert.commandWorked(writeRes); assert.eq(1, writeRes.nModified); // replaceOne(). -coll = testDb.collation_bulkreplaceone1; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1505,7 +1416,6 @@ assert.commandWorked(writeRes); assert.eq(1, writeRes.nModified); // replaceOne() with upsert(). -coll = testDb.collation_bulkreplaceone2; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1524,7 +1434,6 @@ assert.eq(0, writeRes.nUpserted); assert.eq(1, writeRes.nModified); // removeOne(). -coll = testDb.collation_bulkremoveone; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1535,7 +1444,6 @@ assert.commandWorked(writeRes); assert.eq(1, writeRes.nRemoved); // remove(). -coll = testDb.collation_bulkremove; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1550,7 +1458,6 @@ assert.eq(2, writeRes.nRemoved); // // deleteOne(). -coll = testDb.collation_deleteone; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1558,7 +1465,6 @@ res = coll.deleteOne({str: "FOO"}, {collation: {locale: "en_US", strength: 2}}); assert.eq(1, res.deletedCount); // deleteMany(). -coll = testDb.collation_deletemany; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1566,7 +1472,6 @@ res = coll.deleteMany({str: "FOO"}, {collation: {locale: "en_US", strength: 2}}) assert.eq(2, res.deletedCount); // findOneAndDelete(). -coll = testDb.collation_findonedelete; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.eq({_id: 1, str: "foo"}, @@ -1574,7 +1479,6 @@ assert.eq({_id: 1, str: "foo"}, assert.eq(null, coll.findOne({_id: 1})); // findOneAndReplace(). -coll = testDb.collation_findonereplace; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.eq({_id: 1, str: "foo"}, @@ -1583,7 +1487,6 @@ assert.eq({_id: 1, str: "foo"}, assert.neq(null, coll.findOne({str: "bar"})); // findOneAndUpdate(). -coll = testDb.collation_findoneupdate; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.eq({_id: 1, str: "foo"}, @@ -1592,7 +1495,6 @@ assert.eq({_id: 1, str: "foo"}, assert.neq(null, coll.findOne({other: 99})); // replaceOne(). -coll = testDb.collation_replaceone; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1600,7 +1502,6 @@ res = coll.replaceOne({str: "FOO"}, {str: "bar"}, {collation: {locale: "en_US", assert.eq(1, res.modifiedCount); // updateOne(). -coll = testDb.collation_updateone; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1609,7 +1510,6 @@ res = assert.eq(1, res.modifiedCount); // updateMany(). -coll = testDb.collation_updatemany; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1618,7 +1518,6 @@ res = assert.eq(2, res.modifiedCount); // updateOne with bulkWrite(). -coll = testDb.collation_updateonebulkwrite; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1645,7 +1544,6 @@ for (let backwards of [undefined, null]) { } // updateMany with bulkWrite(). -coll = testDb.collation_updatemanybulkwrite; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1659,7 +1557,6 @@ res = coll.bulkWrite([{ assert.eq(2, res.matchedCount); // replaceOne with bulkWrite(). -coll = testDb.collation_replaceonebulkwrite; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1670,7 +1567,6 @@ res = coll.bulkWrite([{ assert.eq(1, res.matchedCount); // deleteOne with bulkWrite(). -coll = testDb.collation_deleteonebulkwrite; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1679,7 +1575,6 @@ res = coll.bulkWrite( assert.eq(1, res.deletedCount); // deleteMany with bulkWrite(). -coll = testDb.collation_deletemanybulkwrite; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "foo"})); @@ -1688,7 +1583,6 @@ res = coll.bulkWrite( assert.eq(2, res.deletedCount); // Two deleteOne ops with bulkWrite using different collations. -coll = testDb.collation_deleteone2collation; coll.drop(); assert.commandWorked(coll.insert({_id: 1, str: "foo"})); assert.commandWorked(coll.insert({_id: 2, str: "bar"})); @@ -1700,19 +1594,18 @@ assert.eq(2, res.deletedCount); // applyOps. if (!isMongos) { - coll = testDb.collation_applyops; coll.drop(); assert.commandWorked( - testDb.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}})); + db.createCollection("collation", {collation: {locale: "en_US", strength: 2}})); assert.commandWorked(coll.insert({_id: "foo", x: 5, str: "bar"})); // preCondition.q respects collection default collation. - assert.commandFailed(testDb.runCommand({ + assert.commandFailed(db.runCommand({ applyOps: [{op: "u", ns: coll.getFullName(), o2: {_id: "foo"}, o: {$set: {x: 6}}}], preCondition: [{ns: coll.getFullName(), q: {_id: "not foo"}, res: {str: "bar"}}] })); assert.eq(5, coll.findOne({_id: "foo"}).x); - assert.commandWorked(testDb.runCommand({ + assert.commandWorked(db.runCommand({ applyOps: [{op: "u", ns: coll.getFullName(), o2: {_id: "foo"}, o: {$set: {x: 6}}}], preCondition: [{ns: coll.getFullName(), q: {_id: "FOO"}, res: {str: "bar"}}] })); @@ -1740,16 +1633,15 @@ if (!isMongos) { // default collation of the corresponding collection. We skip running this command in a sharded // cluster because it isn't supported by mongos. if (!isMongos) { - const clonedColl = testDb.collation_cloned; + const clonedColl = db.collation_cloned; - coll = testDb.collation_orig; coll.drop(); clonedColl.drop(); // Create a collection with a non-simple default collation. assert.commandWorked( - testDb.runCommand({create: coll.getName(), collation: {locale: "en", strength: 2}})); - const originalCollectionInfos = testDb.getCollectionInfos({name: coll.getName()}); + db.runCommand({create: coll.getName(), collation: {locale: "en", strength: 2}})); + const originalCollectionInfos = db.getCollectionInfos({name: coll.getName()}); assert.eq(originalCollectionInfos.length, 1, tojson(originalCollectionInfos)); assert.commandWorked(coll.insert({_id: "FOO"})); @@ -1758,10 +1650,10 @@ if (!isMongos) { coll.find({_id: "foo"}).toArray(), "query should have performed a case-insensitive match"); - var cloneCollOutput = testDb.runCommand( + var cloneCollOutput = db.runCommand( {cloneCollectionAsCapped: coll.getName(), toCollection: clonedColl.getName(), size: 4096}); assert.commandWorked(cloneCollOutput); - const clonedCollectionInfos = testDb.getCollectionInfos({name: clonedColl.getName()}); + const clonedCollectionInfos = db.getCollectionInfos({name: clonedColl.getName()}); assert.eq(clonedCollectionInfos.length, 1, tojson(clonedCollectionInfos)); assert.eq(originalCollectionInfos[0].options.collation, clonedCollectionInfos[0].options.collation); @@ -1769,7 +1661,6 @@ if (!isMongos) { } // Test that the find command's min/max options respect the collation. -coll = testDb.collation_minmax1; coll.drop(); assert.commandWorked(coll.insert({str: "a"})); assert.commandWorked(coll.insert({str: "A"})); @@ -1830,7 +1721,6 @@ assert.eq(4, .itcount()); // Ensure results from index with min/max query are sorted to match requested collation. -coll = testDb.collation_minmax2; coll.drop(); assert.commandWorked(coll.createIndex({a: 1, b: 1})); assert.commandWorked( @@ -1865,7 +1755,7 @@ explainRes = coll.find({}, {_id: 0}) .sort({a: 1, b: 1}) .explain(); assert.commandWorked(explainRes); -assert(planHasStage(testDb, getWinningPlan(explainRes.queryPlanner), "SORT")); +assert(planHasStage(db, getWinningPlan(explainRes.queryPlanner), "SORT")); // This query should fail since min has a string as one of it's boundaries, and the // collation doesn't match that of the index. diff --git a/jstests/core/collection_uuid_coll_mod.js b/jstests/core/collection_uuid_coll_mod.js index 218a758c0b3..52da75bb345 100644 --- a/jstests/core/collection_uuid_coll_mod.js +++ b/jstests/core/collection_uuid_coll_mod.js @@ -57,7 +57,7 @@ assert.eq(res.actualCollection, null); // 5. The command fails when the provided UUID corresponds to a different collection, even if the // provided namespace does not exist. -assert.commandWorked(testDB.runCommand({drop: coll2.getName()})); +coll2.drop(); res = assert.commandFailedWithCode( testDB.runCommand({collMod: coll2.getName(), collectionUUID: uuid}), ErrorCodes.CollectionUUIDMismatch); @@ -65,15 +65,4 @@ assert.eq(res.db, testDB.getName()); assert.eq(res.collectionUUID, uuid); assert.eq(res.expectedCollection, coll2.getName()); assert.eq(res.actualCollection, coll.getName()); -assert(!testDB.getCollectionNames().includes(coll2.getName())); - -// 6. The command fails with CollectionUUIDMismatch even if the database does not exist. -const nonexistentDB = testDB.getSiblingDB(testDB.getName() + '_nonexistent'); -res = assert.commandFailedWithCode( - nonexistentDB.runCommand({collMod: 'nonexistent', collectionUUID: uuid}), - ErrorCodes.CollectionUUIDMismatch); -assert.eq(res.db, nonexistentDB.getName()); -assert.eq(res.collectionUUID, uuid); -assert.eq(res.expectedCollection, 'nonexistent'); -assert.eq(res.actualCollection, null); })(); diff --git a/jstests/core/collection_uuid_drop.js b/jstests/core/collection_uuid_drop.js index a3465795292..276f591afdf 100644 --- a/jstests/core/collection_uuid_drop.js +++ b/jstests/core/collection_uuid_drop.js @@ -58,24 +58,13 @@ assert.eq(res.actualCollection, null); // The command fails when the provided UUID corresponds to a different collection, even if the // provided namespace does not exist. -assert.commandWorked(testDB.runCommand({drop: coll2.getName()})); +coll2.drop(); res = assert.commandFailedWithCode(testDB.runCommand({drop: coll2.getName(), collectionUUID: uuid}), ErrorCodes.CollectionUUIDMismatch); assert.eq(res.db, testDB.getName()); assert.eq(res.collectionUUID, uuid); assert.eq(res.expectedCollection, coll2.getName()); assert.eq(res.actualCollection, coll.getName()); -assert(!testDB.getCollectionNames().includes(coll2.getName())); - -// The command fails with CollectionUUIDMismatch even if the database does not exist. -const nonexistentDB = testDB.getSiblingDB(testDB.getName() + '_nonexistent'); -res = assert.commandFailedWithCode( - nonexistentDB.runCommand({drop: 'nonexistent', collectionUUID: uuid}), - ErrorCodes.CollectionUUIDMismatch); -assert.eq(res.db, nonexistentDB.getName()); -assert.eq(res.collectionUUID, uuid); -assert.eq(res.expectedCollection, 'nonexistent'); -assert.eq(res.actualCollection, null); // The command fails when the provided UUID corresponds to a different collection, even if the // provided namespace is a view. diff --git a/jstests/core/collection_uuid_find.js b/jstests/core/collection_uuid_find.js index 241435c1350..f3ce69f0e34 100644 --- a/jstests/core/collection_uuid_find.js +++ b/jstests/core/collection_uuid_find.js @@ -56,25 +56,13 @@ assert.eq(res.actualCollection, null); // The command fails when the provided UUID corresponds to a different collection, even if the // provided namespace does not exist. -assert.commandWorkedOrFailedWithCode(testDB.runCommand({drop: coll2.getName()}), - ErrorCodes.NamespaceNotFound); +coll2.drop(); res = assert.commandFailedWithCode(testDB.runCommand({find: coll2.getName(), collectionUUID: uuid}), ErrorCodes.CollectionUUIDMismatch); assert.eq(res.db, testDB.getName()); assert.eq(res.collectionUUID, uuid); assert.eq(res.expectedCollection, coll2.getName()); assert.eq(res.actualCollection, coll.getName()); -assert(!testDB.getCollectionNames().includes(coll2.getName())); - -// The command fails with CollectionUUIDMismatch even if the database does not exist. -const nonexistentDB = testDB.getSiblingDB(testDB.getName() + '_nonexistent'); -res = assert.commandFailedWithCode( - nonexistentDB.runCommand({find: 'nonexistent', collectionUUID: uuid}), - ErrorCodes.CollectionUUIDMismatch); -assert.eq(res.db, nonexistentDB.getName()); -assert.eq(res.collectionUUID, uuid); -assert.eq(res.expectedCollection, 'nonexistent'); -assert.eq(res.actualCollection, null); // The command fails when the provided UUID corresponds to a different collection, even if the // provided namespace is a view. diff --git a/jstests/core/collection_uuid_index_commands.js b/jstests/core/collection_uuid_index_commands.js index 85c9f0a9124..a323859144c 100644 --- a/jstests/core/collection_uuid_index_commands.js +++ b/jstests/core/collection_uuid_index_commands.js @@ -4,7 +4,6 @@ * @tags: [ * requires_fcv_60, * tenant_migration_incompatible, - * requires_non_retryable_commands, * ] */ (function() { @@ -21,6 +20,12 @@ const validateErrorResponse = function( // In sharded cluster scenario, the inner raw shards reply should contain the error info, // along with the outer reply obj. for (let [_, shardReply] of Object.entries(res.raw)) { + if (shardReply.code === ErrorCodes.HostUnreachable) { + // We can hit this error on some suites that kills the primary node on shards. + // Skipping is safe as this is rare and most probably happens on one shard. + continue; + } + assert.eq(shardReply.code, ErrorCodes.CollectionUUIDMismatch); assert.eq(shardReply.db, db); assert.eq(shardReply.collectionUUID, collectionUUID); @@ -77,19 +82,10 @@ const testCommand = function(cmd, cmdObj) { jsTestLog("The command '" + cmd + "' fails when the provided UUID corresponds to a different collection, even if the " + "provided namespace does not exist."); - assert.commandWorked(testDB.runCommand({drop: coll2.getName()})); + coll2.drop(); res = assert.commandFailedWithCode(testDB.runCommand(cmdObj), ErrorCodes.CollectionUUIDMismatch); validateErrorResponse(res, testDB.getName(), uuid, coll2.getName(), coll.getName()); - assert(!testDB.getCollectionNames().includes(coll2.getName())); - - jsTestLog("The command '" + cmd + - "' fails with CollectionUUIDMismatch even if the database does not exist."); - const nonexistentDB = testDB.getSiblingDB(testDB.getName() + '_nonexistent'); - cmdObj[cmd] = 'nonexistent'; - res = assert.commandFailedWithCode(nonexistentDB.runCommand(cmdObj), - ErrorCodes.CollectionUUIDMismatch); - validateErrorResponse(res, nonexistentDB.getName(), uuid, 'nonexistent', null); jsTestLog("Only collections in the same database are specified by actualCollection."); const otherDB = testDB.getSiblingDB(testDB.getName() + '_2'); diff --git a/jstests/core/collection_uuid_rename_collection.js b/jstests/core/collection_uuid_rename_collection.js index bc294fd7aab..3dc99fe98ab 100644 --- a/jstests/core/collection_uuid_rename_collection.js +++ b/jstests/core/collection_uuid_rename_collection.js @@ -162,7 +162,7 @@ assert.eq(res.actualCollection, null); // The command fails when the provided UUID corresponds to a different collection, even if the // provided source namespace does not exist. -assert.commandWorked(testDB.runCommand({drop: coll2.getName()})); +coll2.drop(); res = assert.commandFailedWithCode(testDB.adminCommand({ renameCollection: coll2.getFullName(), to: coll3.getFullName(), @@ -174,21 +174,6 @@ assert.eq(res.db, testDB.getName()); assert.eq(res.collectionUUID, uuid(coll)); assert.eq(res.expectedCollection, coll2.getName()); assert.eq(res.actualCollection, coll.getName()); -assert(!testDB.getCollectionNames().includes(coll2.getName())); - -// The command fails with CollectionUUIDMismatch even if the database does not exist. -const nonexistentDB = testDB.getSiblingDB(testDB.getName() + '_nonexistent'); -res = assert.commandFailedWithCode(testDB.adminCommand({ - renameCollection: nonexistentDB.getName() + '.nonexistent', - to: nonexistentDB.getName() + '.nonexistent_2', - dropTarget: true, - collectionUUID: uuid(coll), -}), - ErrorCodes.CollectionUUIDMismatch); -assert.eq(res.db, nonexistentDB.getName()); -assert.eq(res.collectionUUID, uuid(coll)); -assert.eq(res.expectedCollection, 'nonexistent'); -assert.eq(res.actualCollection, null); // The collectionUUID parameter cannot be provided when renaming a collection between databases. const otherDBColl = db.getSiblingDB(jsTestName() + '_2').coll; diff --git a/jstests/core/collection_uuid_write_commands.js b/jstests/core/collection_uuid_write_commands.js index 080ce1772d5..03bd0b09ae7 100644 --- a/jstests/core/collection_uuid_write_commands.js +++ b/jstests/core/collection_uuid_write_commands.js @@ -57,20 +57,10 @@ var testCommand = function(cmd, cmdObj) { jsTestLog("The command '" + cmd + "' fails when the provided UUID corresponds to a different collection, even if the " + "provided namespace does not exist."); - assert.commandWorkedOrFailedWithCode(testDB.runCommand({drop: coll2.getName()}), - ErrorCodes.NamespaceNotFound); + coll2.drop(); res = assert.commandFailedWithCode(testDB.runCommand(cmdObj), ErrorCodes.CollectionUUIDMismatch); validateErrorResponse(res, testDB.getName(), uuid, coll2.getName(), coll.getName()); - assert(!testDB.getCollectionNames().includes(coll2.getName())); - - jsTestLog("The command '" + cmd + - "' fails with CollectionUUIDMismatch even if the database does not exist."); - const nonexistentDB = testDB.getSiblingDB(testDB.getName() + '_nonexistent'); - cmdObj[cmd] = 'nonexistent'; - res = assert.commandFailedWithCode(nonexistentDB.runCommand(cmdObj), - ErrorCodes.CollectionUUIDMismatch); - validateErrorResponse(res, nonexistentDB.getName(), uuid, 'nonexistent', null); jsTestLog("Only collections in the same database are specified by actualCollection."); const otherDB = testDB.getSiblingDB(testDB.getName() + '_2'); @@ -85,7 +75,5 @@ var testCommand = function(cmd, cmdObj) { testCommand("insert", {insert: "", documents: [{inserted: true}]}); testCommand("update", {update: "", updates: [{q: {_id: 0}, u: {$set: {updated: true}}}]}); -testCommand("update", - {update: "", updates: [{q: {_id: 0}, u: {$set: {updated: true}}, upsert: true}]}); testCommand("delete", {delete: "", deletes: [{q: {_id: 0}, limit: 1}]}); })(); diff --git a/jstests/core/collmod.js b/jstests/core/collmod.js index 3362650a77a..4362fc110a4 100644 --- a/jstests/core/collmod.js +++ b/jstests/core/collmod.js @@ -131,17 +131,3 @@ assert.commandFailed( // Fails with an unknown key pattern. assert.commandFailed(db.runCommand( {collmod: coll, index: {keyPattern: {doesnotexist: 1}, expireAfterSeconds: 100}})); - -// Exclude from multiversion suites as SERVER-84531 will not be backported to older versions -const isMultiversion = jsTestOptions().mixedBinVersions || jsTestOptions().shardMixedBinVersions || - jsTestOptions().useRandomBinVersionsWithinReplicaSet; -if (!isMultiversion) { - // The timeseriesBucketsMayHaveMixedSchemaData option can only be used on time-series - // collections. - assert.commandFailedWithCode( - db.runCommand({collMod: coll, timeseriesBucketsMayHaveMixedSchemaData: true}), - ErrorCodes.InvalidOptions); - assert.commandFailedWithCode( - db.runCommand({collMod: coll, timeseriesBucketsMayHaveMixedSchemaData: false}), - ErrorCodes.InvalidOptions); -} diff --git a/jstests/core/collmod_convert_index_uniqueness.js b/jstests/core/collmod_convert_index_uniqueness.js index 559a5fed82c..fc6588ae70c 100644 --- a/jstests/core/collmod_convert_index_uniqueness.js +++ b/jstests/core/collmod_convert_index_uniqueness.js @@ -5,7 +5,7 @@ * # Cannot implicitly shard accessed collections because of collection existing when none * # expected. * assumes_no_implicit_collection_creation_after_drop, # common tag in collMod tests. - * requires_fcv_60, + * requires_fcv_52, * requires_non_retryable_commands, # common tag in collMod tests. * # TODO(SERVER-61181): Fix validation errors under ephemeralForTest. * incompatible_with_eft, @@ -19,9 +19,12 @@ (function() { 'use strict'; -load("jstests/libs/feature_flag_util.js"); +const collModIndexUniqueEnabled = assert + .commandWorked(db.getMongo().adminCommand( + {getParameter: 1, featureFlagCollModIndexUnique: 1})) + .featureFlagCollModIndexUnique.value; -if (!FeatureFlagUtil.isEnabled(db, "CollModIndexUnique")) { +if (!collModIndexUniqueEnabled) { jsTestLog('Skipping test because the collMod unique index feature flag is disabled.'); return; } diff --git a/jstests/core/collmod_convert_to_ttl.js b/jstests/core/collmod_convert_to_ttl.js index c4c8b7d2bbd..ab617d3eeb7 100644 --- a/jstests/core/collmod_convert_to_ttl.js +++ b/jstests/core/collmod_convert_to_ttl.js @@ -39,6 +39,13 @@ assert.commandFailedWithCode( db.runCommand({"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": -1}}), ErrorCodes.InvalidOptions); +// Tries to modify with an 'expireAfterSeconds' value too large. +assert.commandFailedWithCode(db.runCommand({ + "collMod": collName, + "index": {"keyPattern": {a: 1}, "expireAfterSeconds": 10000000000000} +}), + ErrorCodes.InvalidOptions); + // Successfully converts to a TTL index. assert.commandWorked(db.runCommand( {"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": 100}})); diff --git a/jstests/core/collmod_convert_to_unique_apply_ops.js b/jstests/core/collmod_convert_to_unique_apply_ops.js index eb574081840..3e47f117512 100644 --- a/jstests/core/collmod_convert_to_unique_apply_ops.js +++ b/jstests/core/collmod_convert_to_unique_apply_ops.js @@ -23,9 +23,12 @@ (function() { 'use strict'; -load("jstests/libs/feature_flag_util.js"); +const collModIndexUniqueEnabled = assert + .commandWorked(db.getMongo().adminCommand( + {getParameter: 1, featureFlagCollModIndexUnique: 1})) + .featureFlagCollModIndexUnique.value; -if (!FeatureFlagUtil.isEnabled(db, "CollModIndexUnique")) { +if (!collModIndexUniqueEnabled) { jsTestLog('Skipping test because the collMod unique index feature flag is disabled.'); return; } diff --git a/jstests/core/collmod_convert_to_unique_violations.js b/jstests/core/collmod_convert_to_unique_violations.js index 84e29d106c8..52b369e7063 100644 --- a/jstests/core/collmod_convert_to_unique_violations.js +++ b/jstests/core/collmod_convert_to_unique_violations.js @@ -20,10 +20,14 @@ (function() { 'use strict'; -load("jstests/libs/feature_flag_util.js"); load("jstests/libs/fixture_helpers.js"); // For 'isMongos' -if (!FeatureFlagUtil.isEnabled(db, "CollModIndexUnique")) { +const collModIndexUniqueEnabled = assert + .commandWorked(db.getMongo().adminCommand( + {getParameter: 1, featureFlagCollModIndexUnique: 1})) + .featureFlagCollModIndexUnique.value; + +if (!collModIndexUniqueEnabled) { jsTestLog('Skipping test because the collMod unique index feature flag is disabled.'); return; } @@ -100,4 +104,4 @@ assertFailedWithViolations({a: 1, b: 1}, [{ids: [4, 9]}, {ids: [6, 7, 8]}]); assert.commandWorked(coll.insert({_id: "10", a: 101, b: 4})); assertFailedWithViolations({a: 1, b: 1}, [{ids: [4, 9, "10"]}, {ids: [6, 7, 8]}]); -})(); +})();
\ No newline at end of file diff --git a/jstests/core/command_let_variables.js b/jstests/core/command_let_variables.js index 72d6558f6ce..1e4286dbc19 100644 --- a/jstests/core/command_let_variables.js +++ b/jstests/core/command_let_variables.js @@ -6,15 +6,13 @@ (function() { "use strict"; -load("jstests/libs/analyze_plan.js"); load("jstests/libs/fixture_helpers.js"); // For 'isMongos' and 'isSharded'. const testDB = db.getSiblingDB("command_let_variables"); const coll = testDB.command_let_variables; -coll.drop(); +const targetColl = testDB.command_let_variables_target; -const isMongos = FixtureHelpers.isMongos(testDB); -const isCollSharded = FixtureHelpers.isSharded(coll); +assert.commandWorked(testDB.dropDatabase()); const testDocs = [ { @@ -84,20 +82,7 @@ expectedResults = [ assert.eq(coll.aggregate(pipeline, {let : {target_trend: "weak decline"}}).toArray(), expectedResults); -// Test that running explain on the agg command works as expected. -let explain = assert.commandWorked(testDB.runCommand({ - explain: - {aggregate: coll.getName(), pipeline, let : {target_trend: "weak decline"}, cursor: {}}, - verbosity: "executionStats" -})); -if (!isMongos) { - assert(explain.hasOwnProperty("stages"), explain); - assert.neq(explain.stages.length, 0, explain); - let lastStage = explain.stages[explain.stages.length - 1]; - assert.eq(lastStage.nReturned, 2, explain); -} - -if (!isMongos) { +if (!FixtureHelpers.isMongos(testDB)) { // Test that if runtimeConstants and let are both specified, both will coexist. // Runtime constants are not allowed on mongos passthroughs. let constants = { @@ -277,20 +262,6 @@ expectedResults = { assert.eq(result.length, 1); assert.eq(expectedResults, result[0]); -// Test that let parameters work as expected when the find is run as an explain. -explain = assert.commandWorked(testDB.runCommand({ - explain: { - find: coll.getName(), - let : {target_species: "Song Thrush (Turdus philomelos)"}, - filter: {$expr: {$eq: ["$Species", "$$target_species"]}}, - projection: {_id: 0} - }, - verbosity: "executionStats" -})); -if (!isMongos) { - assert.eq(explain.executionStats.nReturned, 1, explain); -} - // Delete tests with let params will delete a record, assert that a point-wise find yields an empty // result, and then restore the collection state for further tests down the line. We can't exercise // a multi-delete here (limit: 0) because of failures in sharded txn passthrough tests. @@ -306,24 +277,8 @@ result = assert .cursor.firstBatch; assert.eq(result.length, 0); -assert.commandWorked(coll.insert({_id: 4, Species: "bird_to_remove"})); - -// Test that explain of a delete command works as expected with 'let' parameters. -explain = assert.commandWorked(testDB.runCommand({ - explain: { - delete: coll.getName(), - let : {target_species: "bird_to_remove"}, - deletes: - [{q: {$and: [{_id: 4}, {$expr: {$eq: ["$Species", "$$target_species"]}}]}, limit: 1}] - }, - verbosity: "executionStats" -})); -if (!isMongos) { - let deleteStage = getPlanStage(explain.executionStats.executionStages, "DELETE"); - assert.eq(deleteStage.nWouldDelete, 1, explain); -} - // Test that the .remove() shell helper supports let parameters. +assert.commandWorked(coll.insert({_id: 4, Species: "bird_to_remove"})); result = assert.commandWorked( coll.remove({$and: [{_id: 4}, {$expr: {$eq: ["$Species", "$$target_species"]}}]}, {justOne: true, let : {target_species: "bird_to_remove"}})); @@ -365,67 +320,43 @@ assert.commandFailedWithCode( {aggregate: coll.getName(), pipeline: [], cursor: {}, let : {REMOVE: "failure"}}), ErrorCodes.FailedToParse); -// Test that let variables can be used within views. Skip in sharded collection passthroughs, since -// dropping the view namespace implicitly recreates the namespace as a sharded collection. -if (!isCollSharded) { - const viewName = "core-viewColl"; - testDB[viewName].drop(); - assert.commandWorked(testDB.runCommand({ - create: viewName, - viewOn: coll.getName(), - pipeline: [{$match: {Species: "Song Thrush (Turdus philomelos)"}}] - })); - assert.commandWorked(testDB.runCommand({ - aggregate: viewName, - pipeline: [{$addFields: {var : "$$variable"}}], - let : {variable: "Song Thrush"}, - cursor: {} - })); -} - -assert.commandWorked(coll.insert({_id: 5, Species: "spy_bird"})); - -// Test that explain of findAndModify works correctly with let parameters. -explain = assert.commandWorked(testDB.runCommand({ - explain: { - findAndModify: coll.getName(), - let : {target_species: "spy_bird"}, - // Querying on _id field for sharded collection passthroughs. - query: {$and: [{_id: 5}, {$expr: {$eq: ["$Species", "$$target_species"]}}]}, - update: {Species: "questionable_bird"}, - new: true - }, - verbosity: "executionStats" +// Test that let variables can be used within views. +assert.commandWorked(testDB.runCommand({ + create: "core-viewColl", + viewOn: coll.getName(), + pipeline: [{$match: {Species: "Song Thrush (Turdus philomelos)"}}] +})); +assert.commandWorked(testDB.runCommand({ + aggregate: "core-viewColl", + pipeline: [{$addFields: {var : "$$variable"}}], + let : {variable: "Song Thrush"}, + cursor: {} })); -if (!isMongos) { - let updateStage = getPlanStage(explain.executionStats.executionStages, "UPDATE"); - assert.eq(updateStage.nMatched, 1, explain); - assert.eq(updateStage.nWouldModify, 1, explain); -} // Test that findAndModify works correctly with let parameter arguments. -result = assert.commandWorked(testDB.runCommand({ +assert.commandWorked(coll.insert({_id: 5, Species: "spy_bird"})); +result = testDB.runCommand({ findAndModify: coll.getName(), let : {target_species: "spy_bird"}, // Querying on _id field for sharded collection passthroughs. query: {$and: [{_id: 5}, {$expr: {$eq: ["$Species", "$$target_species"]}}]}, update: {Species: "questionable_bird"}, new: true -})); +}); expectedResults = { _id: 5, Species: "questionable_bird" }; assert.eq(expectedResults, result.value, result); -result = assert.commandWorked(testDB.runCommand({ +result = testDB.runCommand({ findAndModify: coll.getName(), let : {species_name: "not_a_bird", realSpecies: "dino"}, // Querying on _id field for sharded collection passthroughs. query: {$and: [{_id: 5}, {$expr: {$eq: ["$Species", "questionable_bird"]}}]}, update: [{$project: {Species: "$$species_name"}}, {$addFields: {suspect: "$$realSpecies"}}], new: true -})); +}); expectedResults = { _id: 5, Species: "not_a_bird", @@ -433,31 +364,12 @@ expectedResults = { }; assert.eq(expectedResults, result.value, result); -// Test that explain of update works correctly with let parameters. -explain = assert.commandWorked(testDB.runCommand({ - explain: { - update: coll.getName(), - updates: [{ - q: {_id: 3, $expr: {$eq: ["$Species", "$$target_species"]}}, - u: [{$set: {Species: "$$new_name"}}], - }], - let : {target_species: "Chaffinch (Fringilla coelebs)", new_name: "Chaffinch"} - }, - verbosity: "executionStats" -})); -if (!isMongos) { - let updateStage = getPlanStage(explain.executionStats.executionStages, "UPDATE"); - assert.eq(updateStage.nMatched, 1, explain); - assert.eq(updateStage.nWouldModify, 1, explain); -} - // Test that update respects different parameters in both the query and update part. result = assert.commandWorked(testDB.runCommand({ update: coll.getName(), - updates: [{ - q: {_id: 3, $expr: {$eq: ["$Species", "$$target_species"]}}, - u: [{$set: {Species: "$$new_name"}}], - }], + updates: [ + {q: {$expr: {$eq: ["$Species", "$$target_species"]}}, u: [{$set: {Species: "$$new_name"}}]} + ], let : {target_species: "Chaffinch (Fringilla coelebs)", new_name: "Chaffinch"} })); assert.eq(result.n, 1); @@ -475,8 +387,8 @@ assert.eq(result.cursor.firstBatch.length, 1); result = assert.commandWorked(testDB.runCommand({ update: coll.getName(), updates: [{ - q: {_id: 3, $expr: {$eq: ["$Species", "$$target_species"]}}, - u: [{$set: {Timestamp: "$$NOW"}}, {$set: {Species: "$$new_name"}}], + q: {$expr: {$eq: ["$Species", "$$target_species"]}}, + u: [{$set: {Timestamp: "$$NOW"}}, {$set: {Species: "$$new_name"}}] }], let : {target_species: "Chaffinch", new_name: "Pied Piper"} })); @@ -491,12 +403,6 @@ result = assert.commandWorked( testDB.runCommand({find: coll.getName(), filter: {$expr: {$eq: ["$Species", "Pied Piper"]}}})); assert.eq(result.cursor.firstBatch.length, 1, result); -// This forces a multi-statement transaction to commit if this test is running in one of the -// multi-statement transaction passthrough suites. We need to do this to ensure the updates above -// commit before running an update that will fail, as the failed update aborts the entire -// transaction and rolls back the updates above. -assert.commandWorked(testDB.runCommand({ping: 1})); - // Test that undefined let params in the update's query part fail gracefully. assert.commandFailedWithCode(testDB.runCommand({ update: coll.getName(), @@ -512,8 +418,8 @@ assert.commandFailedWithCode(testDB.runCommand({ assert.commandFailedWithCode(testDB.runCommand({ update: coll.getName(), updates: [{ - q: {_id: 3, $expr: {$eq: ["$Species", "Chaffinch (Fringilla coelebs)"]}}, - u: [{$set: {Species: "$$new_name"}}], + q: {$expr: {$eq: ["$Species", "Chaffinch (Fringilla coelebs)"]}}, + u: [{$set: {Species: "$$new_name"}}] }], let : {cat: "not_a_bird"} }), @@ -521,7 +427,7 @@ assert.commandFailedWithCode(testDB.runCommand({ // Test that the .update() shell helper supports let parameters. result = assert.commandWorked( - coll.update({_id: 3, $expr: {$eq: ["$Species", "$$target_species"]}}, + coll.update({$expr: {$eq: ["$Species", "$$target_species"]}}, [{$set: {Species: "$$new_name"}}], {let : {target_species: "Pied Piper", new_name: "Chaffinch"}})); assert.eq(result.nMatched, 1); @@ -613,10 +519,7 @@ assert.between(0, result, 1); } // Test that the expressions are evaluated once up front. -// -// TODO SERVER-75927: This does not work as expected when the collection is sharded. Once the bug -// is fixed, we should re-enable this test case when the collection is sharded. -if (!isCollSharded) { +{ const values = assert .commandWorked(testDB.runCommand({ find: coll.getName(), diff --git a/jstests/core/command_let_variables_expressions.js b/jstests/core/command_let_variables_expressions.js deleted file mode 100644 index c551c30be48..00000000000 --- a/jstests/core/command_let_variables_expressions.js +++ /dev/null @@ -1,208 +0,0 @@ -// Tests that commands like find, aggregate and update accept a 'let' parameter which defines -// variables for use in expressions within the command. Specifically covers cases where the -// parameter definitions contain expressions. -// @tags: [ -// requires_non_retryable_writes, -// does_not_support_transactions, -// tenant_migration_incompatible -// ] -(function() { -"use strict"; - -load('jstests/aggregation/extras/utils.js'); // For assertArrayEq(). - -const coll = db.getCollection('command_let_variables_expressions'); - -function setupColl() { - coll.drop(); - assert.commandWorked(coll.insert([ - {_id: 2, a: {}}, - {_id: 3, b: 1}, - {_id: 4, a: "$notAFieldPath"}, - ])); -} -setupColl(); - -const missingLetParam = { - $getField: {field: "c", input: {a: 1}} -}; -const literalLetParam = { - $literal: "$notAFieldPath" -}; - -// Run find commands with 'let' parameters containing expressions. -{ - // 'let' parameter expression that resolves to a missing value should not cause a query error. - let result = assert.commandWorked(db.runCommand({ - find: coll.getName(), - filter: {$expr: {$eq: ["$a", "$$c"]}}, - let : {c: missingLetParam}, - })); - assertArrayEq({ - actual: result.cursor.firstBatch, - expected: [{_id: 3, b: 1}], - }); - - // 'let' parameter expression that includes $literal and a $-prefixed string should not be - // misinterpreted as a field path. - result = assert.commandWorked(db.runCommand({ - find: coll.getName(), - filter: {$expr: {$eq: ["$a", "$$c"]}}, - let : {c: literalLetParam}, - })); - assertArrayEq({ - actual: result.cursor.firstBatch, - expected: [{_id: 4, a: "$notAFieldPath"}], - }); -} - -// Run aggregate commands with 'let' parameters containing expressions. -{ - let result = - coll.aggregate([{$match: {$expr: {$eq: ["$a", "$$c"]}}}], {let : {c: missingLetParam}}) - .toArray(); - assertArrayEq({ - actual: result, - expected: [{_id: 3, b: 1}], - }); - - result = coll.aggregate([{$match: {$expr: {$eq: ["$a", "$$c"]}}}], {let : {c: literalLetParam}}) - .toArray(); - assertArrayEq({ - actual: result, - expected: [{_id: 4, a: "$notAFieldPath"}], - }); -} - -// Same thing but for update. -{ - assert.commandWorked(db.runCommand({ - update: coll.getName(), - updates: [{ - q: {$expr: {$eq: ["$a", "$$c"]}}, - u: [{$set: {c: "updated"}}], - multi: true, - }], - let : {c: missingLetParam} - })); - assertArrayEq({ - actual: coll.find().toArray(), - expected: [{_id: 2, a: {}}, {_id: 3, b: 1, c: "updated"}, {_id: 4, a: "$notAFieldPath"}], - }); - - // Undo changes. - setupColl(); - - assert.commandWorked(db.runCommand({ - update: coll.getName(), - updates: [{ - q: {$expr: {$eq: ["$a", "$$c"]}}, - u: [{$set: {c: "updated"}}], - multi: true, - }], - let : {c: literalLetParam} - })); - assertArrayEq({ - actual: coll.find().toArray(), - expected: [ - {_id: 2, a: {}}, - {_id: 3, b: 1}, - {_id: 4, a: "$notAFieldPath", c: "updated"}, - ], - }); -} - -// Run findAndModify commands with 'let' parameters containing expressions. -{ - setupColl(); - - assert.commandWorked(db.runCommand({ - findAndModify: coll.getName(), - query: {_id: 3, $expr: {$eq: ["$a", "$$c"]}}, - update: [{$set: {c: "updated"}}], - let : {c: missingLetParam} - })); - assertArrayEq({ - actual: coll.find().toArray(), - expected: [{_id: 2, a: {}}, {_id: 3, b: 1, c: "updated"}, {_id: 4, a: "$notAFieldPath"}], - }); - - // Undo changes. - setupColl(); - - assert.commandWorked(db.runCommand({ - findAndModify: coll.getName(), - query: {_id: 4, $expr: {$eq: ["$a", "$$c"]}}, - update: [{$set: {c: "updated"}}], - let : {c: literalLetParam} - })); - assertArrayEq({ - actual: coll.find().toArray(), - expected: [ - {_id: 2, a: {}}, - {_id: 3, b: 1}, - {_id: 4, a: "$notAFieldPath", c: "updated"}, - ], - }); -} - -// Run delete commands with 'let' parameters containing expressions. -{ - setupColl(); - - assert.commandWorked(db.runCommand({ - delete: coll.getName(), - deletes: [{ - q: {$expr: {$eq: ["$a", "$$c"]}}, - limit: 0, // multi - }], - let : {c: missingLetParam} - })); - assertArrayEq({ - actual: coll.find().toArray(), - expected: [{_id: 2, a: {}}, {_id: 4, a: "$notAFieldPath"}], - }); - - // Undo changes. - setupColl(); - - assert.commandWorked(db.runCommand({ - delete: coll.getName(), - deletes: [{ - q: {$expr: {$eq: ["$a", "$$c"]}}, - limit: 0, // multi - }], - let : {c: literalLetParam} - })); - assertArrayEq({ - actual: coll.find().toArray(), - expected: [{_id: 2, a: {}}, {_id: 3, b: 1}], - }); -} - -// Run a $lookup with let/pipeline syntax. In sharded environments, this will require serializing -// 'let' variables to send to the shards. One of the documents in the collection is missing the -// local field ("$a"), which will cause a missing 'let' variable to be serialized. -{ - setupColl(); - - const result = coll.aggregate([ - { - $lookup: { - from: coll.getName(), - as: "res", - let: {local_a: "$a"}, - pipeline: [{$match: {$expr: {$eq: ["$$local_a", "$a"]},}}, {$project: {_id: 1}}] - } - } - ]).toArray(); - assertArrayEq({ - actual: result, - expected: [ - {_id: 2, a: {}, res: [{_id: 2}]}, - {_id: 3, b: 1, res: [{_id: 3}]}, - {_id: 4, a: "$notAFieldPath", res: [{_id: 4}]}, - ], - }); -} -}()); diff --git a/jstests/core/commands_namespace_parsing.js b/jstests/core/commands_namespace_parsing.js index c6745daefce..6016eaa4fee 100644 --- a/jstests/core/commands_namespace_parsing.js +++ b/jstests/core/commands_namespace_parsing.js @@ -10,7 +10,6 @@ // uses_testing_only_commands, // uses_map_reduce_with_temp_collections, // no_selinux, -// uses_compact, // ] // This file tests that commands namespace parsing rejects embedded null bytes. diff --git a/jstests/core/compact_keeps_indexes.js b/jstests/core/compact_keeps_indexes.js index ad1c5825306..f4f59165272 100644 --- a/jstests/core/compact_keeps_indexes.js +++ b/jstests/core/compact_keeps_indexes.js @@ -4,7 +4,6 @@ // @tags: [ // # compact command is not available on embedded // incompatible_with_embedded, -// uses_compact, // uses_multiple_connections, // uses_parallel_shell, // ] diff --git a/jstests/core/connection_string_validation.js b/jstests/core/connection_string_validation.js index 6236e445a77..302a42d61dc 100644 --- a/jstests/core/connection_string_validation.js +++ b/jstests/core/connection_string_validation.js @@ -1,7 +1,6 @@ // Test validation of connection strings passed to the JavaScript "connect()" function. // @tags: [ // uses_multiple_connections, -// docker_incompatible, // ] // Related to SERVER-8030. diff --git a/jstests/core/cover_null_queries.js b/jstests/core/cover_null_queries.js index 1ff45ea7b78..50bf0b58b07 100644 --- a/jstests/core/cover_null_queries.js +++ b/jstests/core/cover_null_queries.js @@ -3,9 +3,7 @@ * @tags: [ * assumes_unsharded_collection, * requires_non_retryable_writes, - * requires_fcv_60, - * # This test could produce unexpected explain output if additional indexes are created. - * assumes_no_implicit_index_creation, + * requires_fcv_52 * ] */ (function() { @@ -13,7 +11,6 @@ load("jstests/aggregation/extras/utils.js"); // For arrayEq(). load("jstests/libs/analyze_plan.js"); // For getAggPlanStages() and getPlanStages(). -load("jstests/libs/clustered_collections/clustered_collection_util.js"); const coll = db.cover_null_queries; coll.drop(); @@ -60,22 +57,12 @@ function validateStages({cmdObj, expectedStages, isAgg}) { */ function validateFindCmdOutputAndPlan({filter, projection, expectedStages, expectedOutput}) { const cmdObj = {find: coll.getName(), filter: filter, projection: projection}; - - // Compare index output with expected output. if (expectedOutput) { const res = assert.commandWorked(coll.runCommand(cmdObj)); const ouputArray = new DBCommandCursor(coll.getDB(), res).toArray(); assert(arrayEq(expectedOutput, ouputArray), ouputArray); } - - // Validate explain. validateStages({cmdObj, expectedStages}); - - // Verify that we get the same output as we expect without an index. - const noIndexCmdObj = Object.assign(cmdObj, {hint: {$natural: 1}}); - const resNoIndex = assert.commandWorked(coll.runCommand(noIndexCmdObj)); - const noIndexOutArr = new DBCommandCursor(coll.getDB(), resNoIndex).toArray(); - assert(arrayEq(expectedOutput, noIndexOutArr), noIndexOutArr); } /** @@ -84,18 +71,10 @@ function validateFindCmdOutputAndPlan({filter, projection, expectedStages, expec * are present in the plan returned. */ function validateSimpleCountCmdOutputAndPlan({filter, expectedStages, expectedCount}) { - // Compare index output with expected output. const cmdObj = {count: coll.getName(), query: filter}; const res = assert.commandWorked(coll.runCommand(cmdObj)); assert.eq(res.n, expectedCount); - - // Validate explain. validateStages({cmdObj, expectedStages}); - - // Verify that we get the same output with and without an index. - const noIndexCmdObj = Object.assign(cmdObj, {hint: {$natural: 1}}); - const resNoIndex = assert.commandWorked(coll.runCommand(noIndexCmdObj)); - assert.eq(resNoIndex.n, expectedCount); } /** @@ -109,42 +88,11 @@ function validateCountAggCmdOutputAndPlan({filter, expectedStages, expectedCount pipeline: pipeline || [{$match: filter}, {$count: "count"}], cursor: {}, }; - - // Compare index output with expected output. const cmdRes = assert.commandWorked(coll.runCommand(cmdObj)); const countRes = cmdRes.cursor.firstBatch; assert.eq(countRes.length, 1, cmdRes); assert.eq(countRes[0].count, expectedCount, countRes); - - // Validate explain. validateStages({cmdObj, expectedStages, isAgg: true}); - - // Verify that we get the same output as we expect without an index. - const noIndexCmdObj = Object.assign(cmdObj, {hint: {$natural: 1}}); - const resNoIndex = assert.commandWorked(coll.runCommand(noIndexCmdObj)); - const countResNoIndex = resNoIndex.cursor.firstBatch; - assert.eq(countResNoIndex.length, 1, cmdRes); - assert.eq(countResNoIndex[0].count, expectedCount, countRes); -} - -/** - * Same as above, but uses a $group count. - */ -function validateGroupCountAggCmdOutputAndPlan({filter, expectedStages, expectedCount}) { - validateCountAggCmdOutputAndPlan({ - expectedStages, - expectedCount, - pipeline: [{$match: filter}, {$group: {_id: 0, count: {$count: {}}}}] - }); -} - -function getExpectedStagesIndexScanAndFetch(extraStages) { - const clustered = ClusteredCollectionUtil.areAllCollectionsClustered(db.getMongo()); - const result = clustered ? {"CLUSTERED_IXSCAN": 1} : {"FETCH": 1, "IXSCAN": 1}; - for (const stage in extraStages) { - result[stage] = extraStages[stage]; - } - return result; } assert.commandWorked(coll.createIndex({a: 1, _id: 1})); @@ -204,7 +152,7 @@ validateFindCmdOutputAndPlan({ expectedStages: {"IXSCAN": 1, "FETCH": 0, "PROJECTION_COVERED": 1}, }); -// We can cover a $in with null and an empty array predicate. +// Same as above, but special case for null and empty array predicate. validateFindCmdOutputAndPlan({ filter: {a: {$in: [null, []]}}, projection: {_id: 1}, @@ -217,21 +165,6 @@ validateSimpleCountCmdOutputAndPlan({ expectedStages: {"IXSCAN": 1, "FETCH": 0}, }); -// We cannot cover a $in with null and an array predicate. -// TODO SERVER-71058: It should be possible to cover this case and the more general case of matching -// an array on a non-multikey index. -validateFindCmdOutputAndPlan({ - filter: {a: {$in: [null, ["a"]]}}, - projection: {_id: 1}, - expectedOutput: [{_id: 3}, {_id: 4}, {_id: 6}, {_id: 7}], - expectedStages: {"IXSCAN": 1, "FETCH": 1, "PROJECTION_SIMPLE": 1}, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {a: {$in: [null, ["a"]]}}, - expectedCount: 4, - expectedStages: {"IXSCAN": 1, "FETCH": 1, "COUNT": 1}, -}); - // Verify that a more complex projection that only relies on the _id field does not need a FETCH. validateFindCmdOutputAndPlan({ filter: {a: null}, @@ -339,18 +272,18 @@ validateFindCmdOutputAndPlan({ validateSimpleCountCmdOutputAndPlan({ filter: {a: null, _id: 3}, expectedCount: 1, - expectedStages: getExpectedStagesIndexScanAndFetch({"OR": 0, "COUNT_SCAN": 0}), + expectedStages: {"FETCH": 1, "IXSCAN": 1, "OR": 0, "COUNT_SCAN": 0} }); validateCountAggCmdOutputAndPlan({ filter: {a: null, _id: 3}, expectedCount: 1, - expectedStages: getExpectedStagesIndexScanAndFetch({"OR": 0, "COUNT_SCAN": 0}), + expectedStages: {"FETCH": 1, "IXSCAN": 1, "OR": 0, "COUNT_SCAN": 0}, }); validateFindCmdOutputAndPlan({ filter: {a: null, _id: 3}, projection: {_id: 1}, expectedOutput: [{_id: 3}], - expectedStages: getExpectedStagesIndexScanAndFetch({"PROJECTION_SIMPLE": 1}), + expectedStages: {"IXSCAN": 1, "FETCH": 1, "PROJECTION_SIMPLE": 1}, }); // Verify that if the index is multikey and the query searches for null and empty array values, then @@ -758,265 +691,4 @@ validateCountAggCmdOutputAndPlan({ expectedCount: 4, expectedStages: {"OR": 1, "COUNT_SCAN": 2, "IXSCAN": 0, "FETCH": 0}, }); - -// Validate that we can use the optimization when we have regex without array elements in a $in or -// $or. See SERVER-70436 for more details. -coll.drop(); - -assert.commandWorked(coll.insertMany([ - {_id: 1, a: '123456'}, - {_id: 2, a: '1234567'}, - {_id: 3, a: ' 12345678'}, - {_id: 4, a: '444456'}, - {_id: 5, a: ''}, - {_id: 6, a: null}, - {_id: 7}, -])); - -assert.commandWorked(coll.createIndex({a: 1, _id: 1})); - -// TODO SERVER-70998: Can apply optimization in case without regex; however, we still can't use a -// COUNT_SCAN in this case. -validateFindCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: ""}]}, - projection: {_id: 1}, - expectedOutput: [{_id: 5}, {_id: 6}, {_id: 7}], - expectedStages: {"IXSCAN": 1, "FETCH": 0, "PROJECTION_COVERED": 1}, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: ""}]}, - expectedCount: 3, - expectedStages: {"COUNT": 1, "IXSCAN": 1, "FETCH": 0}, -}); -validateGroupCountAggCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: ""}]}, - expectedCount: 3, - expectedStages: {"IXSCAN": 1, "FETCH": 0}, -}); - -// Can still apply optimization when we have regex. -validateFindCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: {$regex: "^$"}}]}, - projection: {_id: 1}, - expectedOutput: [{_id: 5}, {_id: 6}, {_id: 7}], - expectedStages: {"IXSCAN": 1, "FETCH": 0, "PROJECTION_COVERED": 1}, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: {$regex: "^$"}}]}, - expectedCount: 3, - expectedStages: {"IXSCAN": 1, "FETCH": 0, "COUNT": 1}, -}); -validateGroupCountAggCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: {$regex: "^$"}}]}, - expectedCount: 3, - expectedStages: {"IXSCAN": 1, "FETCH": 0}, -}); - -// Now test case with a multikey index. We can't leverage the optimization here. -assert.commandWorked(coll.insert({_id: 8, a: [1, 2, 3]})); -assert.commandWorked(coll.insert({_id: 9, a: []})); - -validateFindCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: []}, {a: {$regex: "^$"}}]}, - projection: {_id: 1}, - expectedOutput: [{_id: 5}, {_id: 6}, {_id: 7}, {_id: 9}], - expectedStages: {"IXSCAN": 1, "FETCH": 1, "PROJECTION_SIMPLE": 1}, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: []}, {a: {$regex: "^$"}}]}, - expectedCount: 4, - expectedStages: {"COUNT": 1, "IXSCAN": 1, "FETCH": 1}, -}); -validateGroupCountAggCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: []}, {a: {$regex: "^$"}}]}, - expectedCount: 4, - expectedStages: {"IXSCAN": 1, "FETCH": 1}, -}); - -// We also shouldn't cover queries on multikey indexes where $in includes an array, as we will still -// need a filter after the IXSCAN to correctly return -validateFindCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: []}, {a: [2]}]}, - projection: {_id: 1}, - expectedOutput: [{_id: 6}, {_id: 7}, {_id: 9}], - expectedStages: {"IXSCAN": 1, "FETCH": 1}, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: []}, {a: [2]}]}, - expectedCount: 3, - expectedStages: {"IXSCAN": 1, "FETCH": 1}, -}); -validateGroupCountAggCmdOutputAndPlan({ - filter: {$or: [{a: null}, {a: []}, {a: [2]}]}, - expectedCount: 3, - expectedStages: {"IXSCAN": 1, "FETCH": 1}, -}); - -// Validate that when we have a dotted path, we return the correct results for null queries. -coll.drop(); -assert.commandWorked(coll.insertMany([ - {_id: 1, a: 1}, - {_id: 2, a: null}, - {_id: 3}, - {_id: 4, a: {b: 1}}, - {_id: 5, a: {b: null}}, - {_id: 6, a: {c: 1}}, -])); -assert.commandWorked(coll.createIndex({"a.b": 1, _id: 1})); - -validateFindCmdOutputAndPlan({ - filter: {"a.b": null}, - projection: {_id: 1}, - expectedOutput: [{_id: 1}, {_id: 2}, {_id: 3}, {_id: 5}, {_id: 6}], - expectedStages: {"IXSCAN": 1, "PROJECTION_COVERED": 1, "FETCH": 0}, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {"a.b": null}, - expectedCount: 5, - expectedStages: {"OR": 1, "COUNT_SCAN": 2, "IXSCAN": 0, "FETCH": 0}, -}); -validateGroupCountAggCmdOutputAndPlan({ - filter: {"a.b": null}, - expectedCount: 5, - expectedStages: {"OR": 1, "COUNT_SCAN": 2, "IXSCAN": 0, "FETCH": 0}, -}); - -validateFindCmdOutputAndPlan({ - filter: {a: {b: null}}, - projection: {_id: 1}, - expectedOutput: [{_id: 5}], - expectedStages: {"COLLSCAN": 1, "PROJECTION_SIMPLE": 1}, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {a: {b: null}}, - expectedCount: 1, - expectedStages: {"COLLSCAN": 1}, -}); -validateGroupCountAggCmdOutputAndPlan({ - filter: {a: {b: null}}, - expectedCount: 1, - expectedStages: {"COLLSCAN": 1}, -}); - -// Still need fetch if we don't have a sufficiently restrictive projection. -validateFindCmdOutputAndPlan({ - filter: {"a.b": null}, - projection: {_id: 1, a: 1}, - expectedOutput: [ - {_id: 1, a: 1}, - {_id: 2, a: null}, - {_id: 3}, - {_id: 5, a: {b: null}}, - {_id: 6, a: {c: 1}}, - ], - expectedStages: {"IXSCAN": 1, "FETCH": 1}, -}); - -// Make index multikey, and test case where field b is nested in an array. -assert.commandWorked(coll.insertMany([ - {_id: 7, a: [{b: null}]}, - {_id: 8, a: [{b: []}]}, - {_id: 9, a: [{b: [1, 2, 3]}]}, - {_id: 10, a: [{b: 123}]}, - {_id: 11, a: [{c: 123}]}, - {_id: 12, a: []}, - {_id: 13, a: [{}]}, - {_id: 14, a: [1, 2, 3]}, - {_id: 15, a: [{b: 1}, {c: 2}, {b: 3}]}, - {_id: 16, a: [null]}, -])); - -validateFindCmdOutputAndPlan({ - filter: {"a.b": null}, - projection: {_id: 1}, - expectedOutput: [ - {_id: 1}, - {_id: 2}, - {_id: 3}, - {_id: 5}, - {_id: 6}, - {_id: 7}, - {_id: 11}, - {_id: 13}, - {_id: 15} - ], - expectedStages: {"IXSCAN": 1, "PROJECTION_SIMPLE": 1, "FETCH": 1}, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {"a.b": null}, - expectedCount: 9, - expectedStages: {"COUNT": 1, "IXSCAN": 1, "FETCH": 1}, -}); -validateGroupCountAggCmdOutputAndPlan({ - filter: {"a.b": null}, - expectedCount: 9, - expectedStages: {"IXSCAN": 1, "FETCH": 1}, -}); - -validateFindCmdOutputAndPlan({ - filter: {a: {b: null}}, - projection: {_id: 1}, - expectedOutput: [{_id: 5}, {_id: 7}], - expectedStages: { - "COLLSCAN": 1, - "PROJECTION_SIMPLE": 1, - }, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {a: {b: null}}, - expectedCount: 2, - expectedStages: {"COLLSCAN": 1}, -}); -validateGroupCountAggCmdOutputAndPlan({ - filter: {a: {b: null}}, - expectedCount: 2, - expectedStages: {"COLLSCAN": 1}, -}); - -validateFindCmdOutputAndPlan({ - filter: {a: [{b: null}]}, - projection: {_id: 1}, - expectedOutput: [{_id: 7}], - expectedStages: {"COLLSCAN": 1, "PROJECTION_SIMPLE": 1}, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {a: [{b: null}]}, - expectedCount: 1, - expectedStages: {"COLLSCAN": 1}, -}); -validateGroupCountAggCmdOutputAndPlan({ - filter: {a: [{b: null}]}, - expectedCount: 1, - expectedStages: {"COLLSCAN": 1}, -}); - -// We still need a FETCH for composite paths, because both {a: [1,2,3]} and {"a.b": null} generate -// null index keys, but the former should not match the predicate below. -validateFindCmdOutputAndPlan({ - filter: {"a.b": {$in: [null, []]}}, - projection: {_id: 1}, - expectedOutput: [ - {_id: 1}, - {_id: 2}, - {_id: 3}, - {_id: 5}, - {_id: 6}, - {_id: 7}, - {_id: 8}, - {_id: 11}, - {_id: 13}, - {_id: 15} - ], - expectedStages: {"IXSCAN": 1, "PROJECTION_SIMPLE": 1, "FETCH": 1}, -}); -validateSimpleCountCmdOutputAndPlan({ - filter: {"a.b": {$in: [null, []]}}, - expectedCount: 10, - expectedStages: {"IXSCAN": 1, "FETCH": 1}, -}); -validateGroupCountAggCmdOutputAndPlan({ - filter: {"a.b": {$in: [null, []]}}, - expectedCount: 10, - expectedStages: {"IXSCAN": 1, "FETCH": 1}, -}); })(); diff --git a/jstests/core/create_indexes.js b/jstests/core/create_indexes.js index afa9db6a93b..bdb0561094a 100644 --- a/jstests/core/create_indexes.js +++ b/jstests/core/create_indexes.js @@ -178,10 +178,4 @@ var configDB = db.getSiblingDB('config'); res = configDB.runCommand({createIndexes: 'transactions', indexes: [{key: {star: 1}, name: 'star'}]}); assert.commandFailedWithCode(res, ErrorCodes.IllegalOperation); - -// Test that providing an empty list of index spec for config.transactions should also fail with -// IllegalOperation, rather than BadValue for a normal collection. -// This is consistent with server behavior prior to 6.0. -res = configDB.runCommand({createIndexes: 'transactions', indexes: []}); -assert.commandFailedWithCode(res, ErrorCodes.IllegalOperation); }()); diff --git a/jstests/core/currentop_cursors.js b/jstests/core/currentop_cursors.js index 483d2d3eb62..65baff3363f 100644 --- a/jstests/core/currentop_cursors.js +++ b/jstests/core/currentop_cursors.js @@ -9,7 +9,6 @@ * no_selinux, * # This test contains assertions for the hostname that operations run on. * tenant_migration_incompatible, - * docker_incompatible, * ] */ diff --git a/jstests/core/currentop_shell.js b/jstests/core/currentop_shell.js index 5dbbe958dc7..c96ef2507f1 100644 --- a/jstests/core/currentop_shell.js +++ b/jstests/core/currentop_shell.js @@ -11,8 +11,6 @@ * # currentOp results. * assumes_read_preference_unchanged, * no_selinux, - * # Uses $function operator. - * requires_scripting, * ] */ diff --git a/jstests/core/currentop_waiting_for_latch.js b/jstests/core/currentop_waiting_for_latch.js index 1efc4deebc5..c8a18d57d84 100644 --- a/jstests/core/currentop_waiting_for_latch.js +++ b/jstests/core/currentop_waiting_for_latch.js @@ -2,8 +2,7 @@ * Tests that a backtrace will appear in the $currentOp output if the backtrace option is * set to true and there is a latch timeout. * - * @tags: [assumes_read_concern_unchanged, assumes_read_preference_unchanged, no_selinux, - * requires_latch_analyzer, multiversion_incompatible] + * @tags: [assumes_read_concern_unchanged, assumes_read_preference_unchanged, no_selinux] */ (function() { "use strict"; diff --git a/jstests/core/doc_validation_with_now_variable.js b/jstests/core/doc_validation_with_now_variable.js deleted file mode 100644 index ccb00fffd41..00000000000 --- a/jstests/core/doc_validation_with_now_variable.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Tests that insertion with a $$NOW Validator works properly. - * - * @tags: [assumes_no_implicit_collection_creation_after_drop] - */ -(function() { -"use strict"; -const coll = db.coll_doc_validation_with_now_variable; -coll.drop(); -// assert.commandWorked(db.createCollection("coll_doc_validation_with_now_variable", -// {validator: {"$expr": {$gt: ["$ts", "$$NOW"]}}})); - -assert.commandWorked(db.createCollection("coll_doc_validation_with_now_variable", - {validator: {"$expr": {$lt: ["$ts", "$$NOW"]}}})); - -assert.commandWorked(coll.insert({"ts": new Date(1589617694938)})); - -const result = coll.insert({"ts": new Date(2708791380000)}); -assert.commandFailedWithCode(result, ErrorCodes.DocumentValidationFailure, tojson(result)); -})(); diff --git a/jstests/core/elemmatch_or_pushdown_paths.js b/jstests/core/elemmatch_or_pushdown_paths.js deleted file mode 100644 index 18e06063674..00000000000 --- a/jstests/core/elemmatch_or_pushdown_paths.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Test OR-pushdown fixes for elemMatch based on SERVER-74954. - */ -(function() { -"use strict"; - -load("jstests/aggregation/extras/utils.js"); // for "arrayEq". -load('jstests/libs/analyze_plan.js'); // for "planHasStage". - -const coll = db.jstests_elemmatch_or_pushdown_paths; - -coll.drop(); - -assert.commandWorked(coll.insert([ - {a: 1, b: [{c: 1}]}, - {a: 2, b: [{c: 1}]}, - {a: 3, b: [{c: 1}]}, - {a: 4, b: [{c: 1}]}, -])); -assert.commandWorked(coll.createIndex({"b.c": 1, a: 1})); - -// Test exact bounds. -assert(arrayEq(coll.find({ - $and: [ - {$or: [{a: {$lt: 2}}, {a: {$gt: 3}}]}, - {b: {$elemMatch: {c: {$eq: 1, $exists: true}}}} - ] - }, - {_id: 0}) - .hint({"b.c": 1, a: 1}) - .toArray(), - [ - {a: 1, b: [{c: 1}]}, - {a: 4, b: [{c: 1}]}, - ])); - -// Similar test, but use $mod instead of $exists. -const results = coll.find({ - $and: [ - {$or: [{a: {$lt: 2}}, {a: {$gt: 3}}]}, - {b: {$elemMatch: {c: {$eq: 1, $mod: [2, 1]}}}} - ] - }, - {_id: 0}) - .toArray(); - -assert(arrayEq(results, - [ - {a: 1, b: [{c: 1}]}, - {a: 4, b: [{c: 1}]}, - ]), - results); - -assert(coll.drop()); -assert.commandWorked(coll.insert([ - {a: 5, b: [{c: 5, d: 6, e: 7}]}, - {a: 5, b: [{c: 5, d: 6, e: 8}]}, - {a: 5, b: [{c: 5, d: 5, e: 7}]}, - {a: 4, b: [{c: 5, d: 6, e: 7}]}, -])); -assert.commandWorked(coll.createIndex({"b.d": 1, "b.c": 1})); -assert.commandWorked(coll.createIndex({"b.e": 1, "b.c": 1})); - -// Test OR within elemmatch. -assert(arrayEq( - coll.find({$and: [{a: 5}, {b: {$elemMatch: {$and: [{c: 5}, {$or: [{d: 6}, {e: 7}]}]}}}]}, - {_id: 0}) - .toArray(), - [ - {a: 5, b: [{c: 5, d: 6, e: 7}]}, - {a: 5, b: [{c: 5, d: 6, e: 8}]}, - {a: 5, b: [{c: 5, d: 5, e: 7}]}, - ])); -})(); diff --git a/jstests/core/empty_ts.js b/jstests/core/empty_ts.js deleted file mode 100644 index 926de0fd790..00000000000 --- a/jstests/core/empty_ts.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Test how inserts and updates behave with "Timestamp(0,0)" values. - * - * @tags: [ - * requires_fcv_60, - * ] - */ -(function() { -"use strict"; - -const coll = db.jstests_core_empty_ts; -const emptyTs = Timestamp(0, 0); - -coll.drop(); - -// Insert several documents. For the first document inserted (_id=101), the empty timestamp value -// in field "a" should get replaced with the current timestamp. -assert.commandWorked(coll.insert({_id: 101, a: emptyTs})); -assert.commandWorked(coll.insert({_id: 102, a: 1})); -assert.commandWorked(coll.insert({_id: 103, a: 2})); -assert.commandWorked(coll.insert({_id: 106, a: 3})); -assert.commandWorked(coll.insert({_id: 107, a: 4})); -assert.commandWorked(coll.insert({_id: 108, a: 5})); -assert.commandWorked(coll.insert({_id: 109, a: 6})); -assert.commandWorked(coll.insert({_id: 110, a: 7})); -assert.commandWorked(coll.insert({_id: 111, a: 8})); - -// Use a replacement-style update to update _id=102. This should result in field "a" being set to -// the current timestamp. -// -// This is an example of how the special "empty timestamp" behavior works with a replacement-style -// update. -assert.commandWorked(coll.update({_id: 102}, {a: emptyTs})); - -// Use a replacement-style findAndModify to update _id=103. This should result in field "a" being -// set to the current timestamp. -let findAndModifyResult = coll.findAndModify({query: {_id: 103}, update: {a: emptyTs}}); -assert.eq(findAndModifyResult, {_id: 103, a: 2}); - -// Do a replacement-style update to add a new document with _id=104. This should result in field "a" -// being set to the current timestamp. -assert.commandWorked(coll.update({_id: 104}, {a: emptyTs}, {upsert: true})); - -// Do a replacement-style findAndModify to add a new document with _id=105. This should result in -// field "a" being set to the current timestamp. -findAndModifyResult = coll.findAndModify({query: {_id: 105}, update: {a: emptyTs}, upsert: true}); -assert.eq(findAndModifyResult, null); - -// For the rest of the commands below, the empty timestamp values stored in field "a" should be -// preserved as-is. - -// Do an update-operator-style update to update _id=106. -assert.commandWorked(coll.update({_id: 106}, {$set: {a: emptyTs}})); - -// Do an update-operator-style findAndModify to update _id=107. -findAndModifyResult = coll.findAndModify({query: {_id: 107}, update: {$set: {a: emptyTs}}}); -assert.eq(findAndModifyResult, {_id: 107, a: 4}); - -// Do a pipeline-style update to update _id=108. -assert.commandWorked(coll.update({_id: 108}, [{$addFields: {a: emptyTs}}])); - -// Do a pipeline-style findAndModify to update _id=109. -findAndModifyResult = coll.findAndModify({query: {_id: 109}, update: [{$addFields: {a: emptyTs}}]}); -assert.eq(findAndModifyResult, {_id: 109, a: 6}); - -// Do a pipeline-style update with $internalApplyOplogUpdate to update _id=110. -assert.commandWorked(coll.update( - {_id: 110}, [{$_internalApplyOplogUpdate: {oplogUpdate: {$v: 2, diff: {i: {a: emptyTs}}}}}])); - -// Do a pipeline-style findAndModify with $internalApplyOplogUpdate to update _id=111. -findAndModifyResult = coll.findAndModify({ - query: {_id: 111}, - update: [{$_internalApplyOplogUpdate: {oplogUpdate: {$v: 2, diff: {i: {a: emptyTs}}}}}] -}); -assert.eq(findAndModifyResult, {_id: 111, a: 8}); - -// Do an update-operator-style update to add a new document with _id=112. -assert.commandWorked(coll.update({_id: 112}, {$set: {a: emptyTs}}, {upsert: true})); - -// Do an update-operator-style findAndModify to add a new document with _id=113. -findAndModifyResult = - coll.findAndModify({query: {_id: 113}, update: {$set: {a: emptyTs}}, upsert: true}); -assert.eq(findAndModifyResult, null); - -// Do a pipeline-style update to add a new document with _id=114. -assert.commandWorked(coll.update({_id: 114}, [{$addFields: {a: emptyTs}}], {upsert: true})); - -// Do a pipeline-style findAndModify to add a new document with _id=115. -findAndModifyResult = - coll.findAndModify({query: {_id: 115}, update: [{$addFields: {a: emptyTs}}], upsert: true}); -assert.eq(findAndModifyResult, null); - -// Do a pipline-style update with $internalApplyOplogUpdate to add a new document _id=116. -assert.commandWorked( - coll.update({_id: 116}, - [{$_internalApplyOplogUpdate: {oplogUpdate: {$v: 2, diff: {i: {a: emptyTs}}}}}], - {upsert: true})); - -// Do pipeline-style findAndModify with $internalApplyOplogUpdate to add a new document _id=117. -findAndModifyResult = coll.findAndModify({ - query: {_id: 117}, - update: [{$_internalApplyOplogUpdate: {oplogUpdate: {$v: 2, diff: {i: {a: emptyTs}}}}}], - upsert: true -}); -assert.eq(findAndModifyResult, null); - -// Verify that all the insert, update, and findAndModify commands behaved the way we expected. -for (let i = 101; i <= 117; ++i) { - let result = coll.findOne({_id: i}); - - if (i >= 106) { - assert.eq(tojson(result.a), tojson(emptyTs), "_id=" + i); - } else { - assert.neq(tojson(result.a), tojson(emptyTs), "_id=" + i); - } -} - -// Insert a document with _id=Timestamp(0,0). -assert.commandWorked(coll.insert({_id: emptyTs, a: 9})); - -// Verify the document we just inserted can be retrieved using the filter "{_id: Timestamp(0,0)}". -let result = coll.findOne({_id: emptyTs}); -assert.eq(tojson(result._id), tojson(emptyTs), "_id=" + tojson(emptyTs)); -assert.eq(tojson(result.a), tojson(9), "_id=" + tojson(emptyTs)); - -// Do a replacement-style update on the document. -assert.commandWorked(coll.update({_id: emptyTs}, {_id: emptyTs, a: emptyTs})); - -// Verify the document we just updated can still be retrieved using "{_id: Timestamp(0,0)}", and -// verify that field "a" was set to the current timestamp. -result = coll.findOne({_id: emptyTs}); -assert.eq(tojson(result._id), tojson(emptyTs), "_id=" + tojson(emptyTs)); -assert.neq(tojson(result.a), tojson(emptyTs), "_id=" + tojson(emptyTs)); -}()); diff --git a/jstests/core/exhaust.js b/jstests/core/exhaust.js index 202de9e4374..197b99fcaee 100644 --- a/jstests/core/exhaust.js +++ b/jstests/core/exhaust.js @@ -3,7 +3,6 @@ // # This test uses exhaust which does not use runCommand (required by the inject_tenant_prefix.js // # override). // tenant_migration_incompatible, -// no_selinux // ] (function() { diff --git a/jstests/core/explain5.js b/jstests/core/explain5.js index 534aaf9665b..871a5f10d76 100644 --- a/jstests/core/explain5.js +++ b/jstests/core/explain5.js @@ -2,7 +2,7 @@ // @tags: [ // assumes_balancer_off, // assumes_read_concern_local, -// operations_longer_than_stepdown_interval, # large bulk inserts +// operations_longer_than_stepdown_interval, // large bulk inserts // ] (function() { diff --git a/jstests/core/explain_agg_write_concern.js b/jstests/core/explain_agg_write_concern.js index ec246140abe..9ff556489fa 100644 --- a/jstests/core/explain_agg_write_concern.js +++ b/jstests/core/explain_agg_write_concern.js @@ -5,7 +5,6 @@ // assumes_unsharded_collection, // assumes_write_concern_unchanged, // does_not_support_stepdowns, -// references_foreign_collection, // requires_non_retryable_commands, // ] diff --git a/jstests/core/explain_skip.js b/jstests/core/explain_skip.js deleted file mode 100644 index b6e01d6a436..00000000000 --- a/jstests/core/explain_skip.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Tests that explain reports the skip stage statistics correctly. - * - * @tags: [ - * assumes_unsharded_collection, - * assumes_against_mongod_not_mongos, - * ] - */ -load("jstests/libs/analyze_plan.js"); // For getWinningPlan. - -var coll = db.explain_skip; -coll.drop(); - -assert.commandWorked(coll.insert({a: 1, b: 1})); -assert.commandWorked(coll.insert({a: 1, b: 2})); -assert.commandWorked(coll.insert({a: 1, b: 3})); -assert.commandWorked(coll.insert({a: 2, b: 1})); -assert.commandWorked(coll.insert({a: 2, b: 2})); -assert.commandWorked(coll.insert({a: 2, b: 3})); - -assert.commandWorked(coll.createIndex({a: 1})); -assert.commandWorked(coll.createIndex({b: 1})); - -var explain = coll.find({a: 1, b: 1}).sort({_id: 1}).skip(5).explain(); -var winningPlan = getWinningPlan(explain.queryPlanner); -var skipStage = getPlanStage(winningPlan, "SKIP"); -assert.neq(null, skipStage, explain); -assert.eq(5, skipStage.skipAmount, explain); diff --git a/jstests/core/expr_index_use.js b/jstests/core/expr_index_use.js index 488c9267e90..95a5699eb48 100644 --- a/jstests/core/expr_index_use.js +++ b/jstests/core/expr_index_use.js @@ -279,6 +279,11 @@ confirmExpectedExprExecution({$eq: ["$w", NaN]}, {nReturned: 1, expectedIndex: { confirmExpectedExprExecution({$eq: ["$w", undefined]}, {nReturned: 16}); confirmExpectedExprExecution({$eq: ["$w", "$$REMOVE"]}, {nReturned: 16}); +// Test that equality to null queries can use a sparse index. +assert.commandWorked(coll.dropIndex({w: "hashed"})); +assert.commandWorked(coll.createIndex({w: 1}, {sparse: true})); +confirmExpectedExprExecution({$eq: ["$w", null]}, {nReturned: 1, expectedIndex: {w: 1}}); + // Equality match against text index prefix is expected to fail. Equality predicates are // required against the prefix fields of a text index, but currently $eq inside $expr does not // qualify. diff --git a/jstests/core/field_name_validation.js b/jstests/core/field_name_validation.js index 42d0d5cf21d..7dd249f98b2 100644 --- a/jstests/core/field_name_validation.js +++ b/jstests/core/field_name_validation.js @@ -66,9 +66,6 @@ assert.commandWorked(coll.insert({_id: 2, $valid: 1, $db: 1})); assert.commandWorked(coll.insert({_id: 3, $valid: 1, $ref: 1})); assert.commandWorked(coll.insert({_id: 4, $valid: 1, $alsoValid: 1})); -// Valid, because _id.$gt is a field name, and not equivalent to {_id: {$gt: 4}} -assert.commandWorked(coll.insert({"_id.$gt": 4})); - // // Update command field name validation. // @@ -96,16 +93,9 @@ assert.writeErrorWithCode(coll.update({"a.b": 1}, {_id: {$invalid: 1}}, {upsert: ErrorCodes.DollarPrefixedFieldName); assert.writeErrorWithCode(coll.update({"a.b": 1}, {$set: {_id: {$invalid: 1}}}, {upsert: true}), ErrorCodes.DollarPrefixedFieldName); -assert.writeErrorWithCode(coll.update({"a.b": 1}, {$set: {"_id.$gt": 1}}, {upsert: true}), - ErrorCodes.DollarPrefixedFieldName); assert.writeErrorWithCode( coll.update({"a.b": 1}, {$setOnInsert: {_id: {$invalid: 1}}}, {upsert: true}), ErrorCodes.DollarPrefixedFieldName); -assert.writeErrorWithCode( - coll.update({"a.b": 1}, {$setOnInsert: {"_id.$invalid": 1}}, {upsert: true}), - ErrorCodes.DollarPrefixedFieldName); -assert.writeErrorWithCode(coll.update({"_id.$gt": 1}, {$set: {a: 1}}, {upsert: true}), - ErrorCodes.DollarPrefixedFieldName); // Replacement-style updates can contain nested $-prefixed fields. assert.commandWorked(coll.update({"a.b": 1}, {a: {$c: 1}})); diff --git a/jstests/core/find_with_resume_after_param.js b/jstests/core/find_with_resume_after_param.js deleted file mode 100644 index 26717d063a4..00000000000 --- a/jstests/core/find_with_resume_after_param.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Tests that the internal parameter "$_resumeAfter" validates the type of the 'recordId' for - * clustered and non clustered collections. - * @tags: [ - * # Queries on mongoS may not request or provide a resume token. - * assumes_against_mongod_not_mongos, - * requires_fcv_60, - * sbe_incompatible, - * ] - */ - -(function() { -"use strict"; - -load("jstests/libs/collection_drop_recreate.js"); // For assertDropCollection. -load("jstests/libs/sbe_util.js"); // For checkSBEEnabled. - -const clustered = db.clusteredColl; -const nonClustered = db.normalColl; -const clusteredName = clustered.getName(); -const nonClusteredName = nonClustered.getName(); - -assertDropCollection(db, clusteredName); -assertDropCollection(db, nonClusteredName); - -db.createCollection(clusteredName, {clusteredIndex: {key: {_id: 1}, unique: true}}); -db.createCollection(nonClusteredName); - -// Insert some documents. -const docs = [{_id: 1, a: 1}, {_id: 2, a: 2}, {_id: 3, a: 3}]; -assert.commandWorked(clustered.insertMany(docs)); -assert.commandWorked(nonClustered.insertMany(docs)); - -function validateFailedResumeAfter({collName, resumeAfterSpec, errorCode, explainFail}) { - const spec = { - find: collName, - filter: {}, - $_requestResumeToken: true, - $_resumeAfter: resumeAfterSpec, - hint: {$natural: 1} - }; - assert.commandFailedWithCode(db.runCommand(spec), errorCode); - // Run the same query under an explain. - if (explainFail) { - assert.commandFailedWithCode(db.runCommand({explain: spec}), errorCode); - } else { - assert.commandWorked(db.runCommand({explain: spec})); - } -} - -// Confirm $_resumeAfter will fail for clustered collections if the recordId is Long. -validateFailedResumeAfter({ - collName: clusteredName, - resumeAfterSpec: {'$recordId': NumberLong(2)}, - errorCode: 7738600, - explainFail: true -}); - -// Confirm $_resumeAfter will fail with 'KeyNotFound' if given a non existent recordId. -validateFailedResumeAfter({ - collName: clusteredName, - resumeAfterSpec: {'$recordId': BinData(5, '1234')}, - errorCode: ErrorCodes.KeyNotFound -}); - -// TODO SERVER-78103: Added test for $recordId:null - -// Confirm $_resumeAfter will fail for normal collections if it is of type BinData. -validateFailedResumeAfter({ - collName: nonClusteredName, - resumeAfterSpec: {'$recordId': BinData(5, '1234')}, - errorCode: 7738600, - explainFail: true -}); - -// Confirm $_resumeAfter token will fail with 'KeyNotFound' if given a non existent recordId. -validateFailedResumeAfter({ - collName: nonClusteredName, - resumeAfterSpec: {'$recordId': NumberLong(8)}, - errorCode: ErrorCodes.KeyNotFound -}); - -// Confirm $_resumeAfter token will work with 'null'. -assert.commandWorked(db.runCommand({ - find: nonClusteredName, - filter: {}, - $_requestResumeToken: true, - $_resumeAfter: {'$recordId': null}, - hint: {$natural: 1} -})); - -// Confirm $_resumeAfter will fail to parse if collection does not exist. -validateFailedResumeAfter({ - collName: "random", - resumeAfterSpec: {'$recordId': null, "anotherField": null}, - errorCode: ErrorCodes.BadValue, - explainFail: true -}); -validateFailedResumeAfter({ - collName: "random", - resumeAfterSpec: "string", - errorCode: ErrorCodes.TypeMismatch, - explainFail: true -}); -}()); diff --git a/jstests/core/fts_index.js b/jstests/core/fts_index.js index 0063440f39b..c78301509f1 100644 --- a/jstests/core/fts_index.js +++ b/jstests/core/fts_index.js @@ -78,17 +78,6 @@ assert.eq(0, }) .length); -// $-prefixed fields cannot be indexed. -coll = db.getCollection(collNamePrefix + collCount++); -coll.drop(); -assert.commandFailed(coll.createIndex({"a.$custom": "text"}, {name: indexName})); -assert.eq(0, - coll.getIndexes() - .filter(function(z) { - return z.name == indexName; - }) - .length); - // SERVER-19519 Spec fails if '_fts' is specified on a non-text index. coll = db.getCollection(collNamePrefix + collCount++); coll.drop(); diff --git a/jstests/core/fts_index3.js b/jstests/core/fts_index3.js index 62b9fe72e56..ac4730d0bd0 100644 --- a/jstests/core/fts_index3.js +++ b/jstests/core/fts_index3.js @@ -3,7 +3,6 @@ // key. // @tags: [ // assumes_unsharded_collection, -// requires_fcv_60, // ] // Test that updates to fields in a text-indexed document are correctly reflected in the text index. @@ -111,11 +110,6 @@ assert.commandWorked(coll.update({}, {$set: {"a.language": "en"}})); assert.eq(0, coll.find({$text: {$search: "testing", $language: "es"}}).itcount()); assert.eq(1, coll.find({$text: {$search: "testing", $language: "en"}}).itcount()); -// SERVER-78238: index with a dotted path should not index fields with a dot inside that make it -// look like a dotted path. -assert.commandWorked(coll.insert({"a.b": "ignored"})); -assert.eq(0, coll.find({$text: {$search: "ignored"}}).itcount()); - // 10) Same as #9, but with a wildcard text index. coll = db.getCollection(collNamePrefix + collCount++); coll.drop(); @@ -127,11 +121,6 @@ assert.commandWorked(coll.update({}, {$set: {"a.language": "en"}})); assert.eq(0, coll.find({$text: {$search: "testing", $language: "es"}}).itcount()); assert.eq(1, coll.find({$text: {$search: "testing", $language: "en"}}).itcount()); -// SERVER-78238: index with a wildcard should not index fields with a dot inside or starting with $. -assert.commandWorked(coll.insert({"a.b": "ignored"})); -assert.commandWorked(coll.insert({"$personal": "ignored"})); -assert.eq(0, coll.find({$text: {$search: "ignored"}}).itcount()); - // 11) Create a text index on a single field with a custom language override, insert a document, // update the language of the document (so as to change the stemming), and verify that $text with // the new language returns the document. diff --git a/jstests/core/fts_mix.js b/jstests/core/fts_mix.js index f8bf850fb29..5942a85ec2c 100644 --- a/jstests/core/fts_mix.js +++ b/jstests/core/fts_mix.js @@ -1,3 +1,4 @@ + (function() { load("jstests/libs/fts.js"); load("jstests/aggregation/extras/utils.js"); // For resultsEq. diff --git a/jstests/core/group_lookup_with_canonical_query_prefix.js b/jstests/core/group_lookup_with_canonical_query_prefix.js deleted file mode 100644 index 408c336f771..00000000000 --- a/jstests/core/group_lookup_with_canonical_query_prefix.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Tests that an aggregation pipeline with stages only allowed with 'trySbeEngine' runs correctly - * when wrapped with a $group, or $lookup. This makes the query use SBE. - * @tags: [ - * assumes_unsharded_collection, - * assumes_against_mongod_not_mongos, - * not_allowed_with_security_token, - * does_not_support_causal_consistency, - * # We modify the value of a query knob. setParameter is not persistent. - * does_not_support_stepdowns, - * tenant_migration_incompatible, - * # Explain for the aggregate command cannot run within a multi-document transaction. - * does_not_support_transactions, - * # Explain command does not support read concerns other than local. - * assumes_read_concern_local, - * assumes_read_concern_unchanged, - * requires_fcv_60, - * no_selinux, - * ] - */ -load("jstests/aggregation/extras/utils.js"); // for arrayEq. -load("jstests/libs/analyze_plan.js"); // for getEngine. - -function buildErrorString(found, expected) { - return "Expected:\n" + tojson(expected) + "\nGot:\n" + tojson(found); -} - -function makeExpectedDocs(lowerBound, upperBound, isProject = false) { - let expectedDocsGroup = []; - let expectedDocsLookup = []; - for (let i = lowerBound; i < upperBound; i++) { - expectedDocsGroup.push({_id: i}); - // We project on {x: 1} so 'y' will not be present in the result. - if (isProject) { - expectedDocsLookup.push({_id: i, x: i, xx: []}); - } else { - expectedDocsLookup.push({_id: i, x: i, y: i, xx: []}); - } - } - return [expectedDocsGroup, expectedDocsLookup]; -} - -function runAndVerifyQuery(coll, pipeline, [expectedDocsGroup, expectedDocsLookup]) { - // Run the query and explain. - pipeline.push({$group: {_id: "$x"}}); - let res = coll.aggregate(pipeline); - assert(arrayEq(res.toArray(), expectedDocsGroup), - buildErrorString(res.toArray(), expectedDocsGroup)); - let explain = assert.commandWorked(coll.explain().aggregate(pipeline)); - assert.eq(getEngine(explain), "sbe", tojson(explain)); - - pipeline.pop(); - pipeline.push({$lookup: {from: coll2Name, localField: "y", foreignField: "z", as: "xx"}}); - res = coll.aggregate(pipeline); - assert(arrayEq(res.toArray(), expectedDocsLookup), - buildErrorString(res.toArray(), expectedDocsLookup)); - explain = assert.commandWorked(coll.explain().aggregate(pipeline)); - assert.eq(getEngine(explain), "sbe", tojson(explain)); -} - -// Runs the query and verifies properties of the result and explain. If 'withIndex' is true, for -// some queries, we use a distinct scan, which is only in the classic engine. -function runQueries(coll, withIndex = false) { - // $limit queries. - runAndVerifyQuery(coll, [{$limit: 5}], makeExpectedDocs(0, 5)); - - // $skip queries. - runAndVerifyQuery(coll, [{$skip: 5}], makeExpectedDocs(5, 100)); - - // $limit + $skip queries. - runAndVerifyQuery(coll, [{$limit: 50}, {$skip: 10}], makeExpectedDocs(10, 50)); - runAndVerifyQuery(coll, [{$skip: 10}, {$limit: 5}], makeExpectedDocs(10, 15)); - - // $sort + $limit + $skip queries. - runAndVerifyQuery(coll, [{$sort: {x: 1}}, {$skip: 10}, {$limit: 20}], makeExpectedDocs(10, 30)); - runAndVerifyQuery(coll, [{$sort: {x: 1}}, {$limit: 40}, {$skip: 30}], makeExpectedDocs(30, 40)); - - // Mixed queries. - runAndVerifyQuery( - coll, - [{$match: {x: {$lte: 20}}}, {$sort: {x: 1}}, {$skip: 8}, {$limit: 5}, {$project: {x: 1}}], - makeExpectedDocs(8, 13, true /*isProject*/)); - - if (!withIndex) { - // $sort queries. - runAndVerifyQuery(coll, [{$sort: {x: 1}}], makeExpectedDocs(0, 100)); - runAndVerifyQuery(coll, [{$sort: {y: 1}}], makeExpectedDocs(0, 100)); - - // $project queries. - runAndVerifyQuery(coll, [{$project: {x: 1}}], makeExpectedDocs(0, 100, true /*isProject*/)); - - // $match queries. - runAndVerifyQuery(coll, [{$match: {x: 4}}], makeExpectedDocs(4, 5)); - } -} - -function testIndexed(coll) { - assert.commandWorked(coll.createIndex({x: 1})); - assert.commandWorked(coll.createIndex({y: 1})); - runQueries(coll, true /* withIndex */); -} - -const collName = jsTestName(); -const coll2Name = jsTestName() + "2"; -let coll = db.getCollection(collName); -let coll2 = db.getCollection(coll2Name); -coll.drop(); -coll2.drop(); - -let originalParamValue; -try { - originalParamValue = db.adminCommand({getParameter: 1, internalQueryForceClassicEngine: 1}); - assert.commandWorked( - db.adminCommand({setParameter: 1, internalQueryForceClassicEngine: false})); - - const docs = []; - for (let i = 0; i < 100; i++) { - docs.push({_id: i, x: i, y: i}); - } - - assert.commandWorked(coll.insertMany(docs)); - assert.commandWorked(coll2.insert({z: 100})); - runQueries(coll); - testIndexed(coll); -} finally { - assert.commandWorked(db.adminCommand({ - setParameter: 1, - internalQueryForceClassicEngine: originalParamValue.internalQueryForceClassicEngine - })); -} diff --git a/jstests/core/hostinfo.js b/jstests/core/hostinfo.js index c3a0f34994a..04f341a659d 100644 --- a/jstests/core/hostinfo.js +++ b/jstests/core/hostinfo.js @@ -35,12 +35,8 @@ if (hostinfo.os.type != "") { assert.neq(hostinfo.system.cpuAddrSize, "" || null || 0, "Missing CPU Address Size"); assert.neq(hostinfo.system.memSizeMB, "" || null, "Missing Memory Size"); assert.neq(hostinfo.system.numCores, "" || null || 0, "Missing Number of Cores"); - assert.neq( - hostinfo.system.numPhysicalCores, "" || null || 0, "Missing Number of Physical Cores"); - assert.neq(hostinfo.system.numCpuSockets, "" || null || 0, "Missing Number of CPU Sockets"); assert.neq(hostinfo.system.cpuArch, "" || null, "Missing CPU Architecture"); assert.neq(hostinfo.system.numaEnabled, "" || null, "Missing NUMA flag"); - assert.neq(hostinfo.system.numNumaNodes, "" || null || 0, "Missing Number of NUMA Nodes"); } var buildInfo = assert.commandWorked(db.runCommand({buildInfo: 1})); diff --git a/jstests/core/index/sparse_index_internal_expr.js b/jstests/core/index/sparse_index_internal_expr.js deleted file mode 100644 index 28bcb7db77f..00000000000 --- a/jstests/core/index/sparse_index_internal_expr.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Tests that a sparse index cannot be used to answer a $expr query unless the sparse index is - * explicitly hinted. If a sparse index is hinted to answer a $expr query, incomplete results could - * be returned. - * - * @tags: [ - * multiversion_incompatible, - * does_not_support_transactions, - * ] - */ - -(function() { -"use strict"; - -load("jstests/libs/analyze_plan.js"); - -const coll = db.sparse_index_internal_expr; -coll.drop(); - -coll.insert({a: 1}); - -const exprQuery = { - $expr: {$lt: ["$missing", "r"]} -}; - -// Run a query with $expr on a missing field. This query will use a COLLSCAN plan and return -// document '{a: 1}' because $expr expression does not apply type bracketing, specifically, the -// missing field is evaluated to 'null'. The expression returns "true" because 'null' < "r". -let res = coll.find(exprQuery, {_id: 0}).toArray(); - -assert.eq(res.length, 1); -assert.docEq(res[0], {a: 1}); - -// Tests that a non-sparse index {missing: 1} can be used to answer the $expr query. -assert.commandWorked(coll.createIndex({"missing": 1})); - -// Explain the query, and determine whether an indexed solution is available. -let ixScans = getPlanStages(getWinningPlan(coll.find(exprQuery).explain().queryPlanner), "IXSCAN"); - -// Verify that the winning plan uses the $** index with the expected bounds. -assert.gt(ixScans.length, 0, ixScans); -assert.eq("missing_1", ixScans[0].indexName, ixScans); - -// Run the same query. A complete result will be returned. -res = coll.find(exprQuery, {_id: 0}).toArray(); -assert.eq(res.length, 1); -assert.docEq(res[0], {a: 1}); - -// Drop the non-sparse index and create a sparse index with the same key pattern. -assert.commandWorked(coll.dropIndex("missing_1")); -assert.commandWorked(coll.createIndex({'missing': 1}, {'sparse': true})); - -// Run the same query to test that a COLLSCAN plan is used rather than an indexed plan. -const collScans = - getPlanStages(getWinningPlan(coll.find(exprQuery).explain().queryPlanner), "COLLSCAN"); - -// Verify that the winning plan uses the $** index with the expected bounds. -assert.gt(collScans.length, 0, collScans); - -// Test that a sparse index can be hinted to answer $expr query but incomplete results in returned, -// because the document is not indexed by the sparse index. -res = coll.find(exprQuery, {_id: 0}).hint("missing_1").toArray(); -assert.eq(res.length, 0); - -ixScans = getPlanStages( - getWinningPlan(coll.find(exprQuery).hint("missing_1").explain().queryPlanner), "IXSCAN"); - -assert.gt(ixScans.length, 0, ixScans); -assert.eq("missing_1", ixScans[0].indexName, ixScans); -assert.eq(true, ixScans[0].isSparse, ixScans); -}()); diff --git a/jstests/core/index_elemmatch1.js b/jstests/core/index_elemmatch1.js new file mode 100644 index 00000000000..6277ca9c42d --- /dev/null +++ b/jstests/core/index_elemmatch1.js @@ -0,0 +1,39 @@ +/** + * Tests find with $elemMatch when supporting indexes are in place. + * @tags: [ + * assumes_balancer_off, + * assumes_read_concern_local, + * ] + */ +(function() { +"use strict"; + +const coll = db.index_elemmatch1; +coll.drop(); + +let x = 0; +let y = 0; +const bulk = coll.initializeUnorderedBulkOp(); +for (let a = 0; a < 10; a++) { + for (let b = 0; b < 10; b++) { + bulk.insert({a: a, b: b % 10, arr: [{x: x++ % 10, y: y++ % 10}]}); + } +} +assert.commandWorked(bulk.execute()); + +assert.commandWorked(coll.createIndex({a: 1, b: 1})); +assert.commandWorked(coll.createIndex({"arr.x": 1, a: 1})); + +const query = { + a: 5, + b: {$in: [1, 3, 5]}, + arr: {$elemMatch: {x: 5, y: 5}} +}; + +const count = coll.find(query).itcount(); +assert.eq(count, 1); + +const explain = coll.find(query).hint({"arr.x": 1, a: 1}).explain("executionStats"); +assert.commandWorked(explain); +assert.eq(count, explain.executionStats.totalKeysExamined, explain); +})(); diff --git a/jstests/core/elemmatch_index.js b/jstests/core/index_elemmatch2.js index f0aaf35cdba..33988cb945e 100644 --- a/jstests/core/elemmatch_index.js +++ b/jstests/core/index_elemmatch2.js @@ -2,7 +2,6 @@ * Test that queries containing $elemMatch correctly use an index if each child expression is * compatible with the index. * @tags: [ - * assumes_balancer_off, * assumes_read_concern_local, * ] */ @@ -65,57 +64,4 @@ assertIndexResults(coll, {a: {$elemMatch: {$not: {$in: [/^a/]}}}}, false, 3); coll.dropIndexes(); assert.commandWorked(coll.createIndex({a: 1})); assertIndexResults(coll, {a: {$elemMatch: {$not: {$in: [/^a/]}}}}, false, 3); - -(function() { -assert(coll.drop()); -assert.commandWorked(coll.insert({a: [{b: {c: "x"}}]})); -assert.commandWorked(coll.createIndex({"a.b.c": 1})); - -// Tests $elemMatch with path components that are empty strings. The system should not attempt to -// use the index for these queries. -assertIndexResults(coll, {"": {$elemMatch: {"a.b.c": "x"}}}, false, 0); -assertIndexResults(coll, {"": {$all: [{$elemMatch: {"a.b.c": "x"}}]}}, false, 0); -assertIndexResults(coll, {a: {$elemMatch: {"": {$elemMatch: {"b.c": "x"}}}}}, false, 0); - -// Tests $elemMatch with supporting index and no path components that are empty strings. -assertIndexResults(coll, {a: {$elemMatch: {"b.c": "x"}}}, true, 1); -assertIndexResults(coll, {a: {$all: [{$elemMatch: {"b.c": "x"}}]}}, true, 1); - -// Tests that $elemMatch with path components that are empty strings are correctly handled in the -// plan enumerator in case if an index is used on another predicate of the query. -assertIndexResults(coll, {$and: [{"a.b.c": "x"}, {"": {$elemMatch: {"a.b.c": "x"}}}]}, true, 0); - -assertIndexResults(coll, {$and: [{"a.b.c": "x"}, {"a.b.c": {$elemMatch: {"": "x"}}}]}, true, 0); -})(); - -(function() { -const coll = db.index_elemmatch1; -coll.drop(); - -let x = 0; -let y = 0; -const bulk = coll.initializeUnorderedBulkOp(); -for (let a = 0; a < 10; a++) { - for (let b = 0; b < 10; b++) { - bulk.insert({a: a, b: b % 10, arr: [{x: x++ % 10, y: y++ % 10}]}); - } -} -assert.commandWorked(bulk.execute()); - -assert.commandWorked(coll.createIndex({a: 1, b: 1})); -assert.commandWorked(coll.createIndex({"arr.x": 1, a: 1})); - -const query = { - a: 5, - b: {$in: [1, 3, 5]}, - arr: {$elemMatch: {x: 5, y: 5}} -}; - -const count = coll.find(query).itcount(); -assert.eq(count, 1); - -const explain = coll.find(query).hint({"arr.x": 1, a: 1}).explain("executionStats"); -assert.commandWorked(explain); -assert.eq(count, explain.executionStats.totalKeysExamined, explain); -})(); })(); diff --git a/jstests/core/index_filter_commands.js b/jstests/core/index_filter_commands.js index 6e5f5c5726a..6c5ff8920c8 100644 --- a/jstests/core/index_filter_commands.js +++ b/jstests/core/index_filter_commands.js @@ -30,8 +30,7 @@ * assumes_read_preference_unchanged, * assumes_unsharded_collection, * does_not_support_stepdowns, - * requires_fcv_60, - * references_foreign_collection, + * requires_fcv_60 * ] */ diff --git a/jstests/core/index_partial_create_drop.js b/jstests/core/index_partial_create_drop.js index c0a095b30df..800ad6ab64e 100644 --- a/jstests/core/index_partial_create_drop.js +++ b/jstests/core/index_partial_create_drop.js @@ -13,7 +13,7 @@ // Test partial index creation and drops. -load("jstests/libs/feature_flag_util.js"); +load("jstests/core/timeseries/libs/timeseries.js"); (function() { "use strict"; @@ -46,7 +46,7 @@ assert.commandFailed( assert.commandFailed(coll.createIndex( {x: 1}, {partialFilterExpression: {$expr: {$eq: [{$trim: {input: "$x"}}, "hi"]}}})); -if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { +if (!TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { // Only top-level $and is permitted in a partial filter expression. assert.commandFailedWithCode(coll.createIndex({x: 1}, { partialFilterExpression: diff --git a/jstests/core/index_partial_read_ops.js b/jstests/core/index_partial_read_ops.js index 2071b2d129c..4f1229888f7 100644 --- a/jstests/core/index_partial_read_ops.js +++ b/jstests/core/index_partial_read_ops.js @@ -10,7 +10,8 @@ // Include helpers for analyzing explain output. load("jstests/libs/analyze_plan.js"); -load("jstests/libs/feature_flag_util.js"); + +load("jstests/core/timeseries/libs/timeseries.js"); (function() { "use strict"; @@ -132,7 +133,7 @@ const coll = db.index_partial_read_ops; assert(isCollscan(db, coll.explain().find({a: {$lt: 0}}).finish())); })(); -if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { +if (!TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { jsTest.log( "Skipping partialFilterExpression testing for $in, $or and non-top level $and as timeseriesMetricIndexesEnabled is false"); return; diff --git a/jstests/core/index_prepareUnique.js b/jstests/core/index_prepareUnique.js index 196e80a1864..97b3d0ef874 100644 --- a/jstests/core/index_prepareUnique.js +++ b/jstests/core/index_prepareUnique.js @@ -1,17 +1,21 @@ /** - * Tests that the createIndex command accepts a prepareUnique field and works accordingly. + * Tests that the createIndex command accepts a prepareUnique field and works accordingly + * then. * - * @tags: [assumes_no_implicit_collection_creation_after_drop] + * @tags: [requires_fcv_53] */ (function() { "use strict"; -load("jstests/libs/feature_flag_util.js"); - const coll = db.index_prepareUnique; coll.drop(); -if (!FeatureFlagUtil.isEnabled(db, "CollModIndexUnique")) { +const collModIndexUniqueEnabled = assert + .commandWorked(db.getMongo().adminCommand( + {getParameter: 1, featureFlagCollModIndexUnique: 1})) + .featureFlagCollModIndexUnique.value; + +if (!collModIndexUniqueEnabled) { jsTestLog('Skipping test because the collMod unique index feature flag is disabled.'); return; } @@ -44,4 +48,4 @@ indexesWithComments = coll.getIndexes().filter(function(doc) { return friendlyEqual(doc.prepareUnique, true); }); assert.eq(0, indexesWithComments.length); -})(); +})();
\ No newline at end of file diff --git a/jstests/core/index_stats.js b/jstests/core/index_stats.js index b0abd2e35a1..43df9a63ed1 100644 --- a/jstests/core/index_stats.js +++ b/jstests/core/index_stats.js @@ -12,25 +12,18 @@ // # Tenant migrations passthrough suites automatically retry operations on TenantMigrationAborted // # errors. // tenant_migration_incompatible, -// references_foreign_collection, // ] (function() { "use strict"; load("jstests/libs/analyze_plan.js"); -load("jstests/libs/sbe_util.js"); // For checkSBEEnabled. -load('jstests/libs/fixture_helpers.js'); // For 'FixtureHelpers' +load("jstests/libs/sbe_util.js"); // For checkSBEEnabled. var colName = "jstests_index_stats"; var col = db[colName]; col.drop(); -function checkSBEEnabledOnHostingDB(db, featureFlags = []) { - return checkSBEEnabled(FixtureHelpers.getPrimaryForNodeHostingDatabase(db).getDB(db.getName()), - featureFlags); -} - var getUsageCount = function(indexName, collection) { collection = collection || col; var cursor = collection.aggregate([{$indexStats: {}}]); @@ -241,7 +234,7 @@ assert.eq(2, ]) .itcount()); assert.eq(1, getUsageCount("_id_", col), "Expected aggregation to use _id index"); -if (!checkSBEEnabledOnHostingDB(db, ["featureFlagSBELookupPushdown"])) { +if (!checkSBEEnabled(db, ["featureFlagSBELookupPushdown"])) { assert.eq(2, getUsageCount("_id_", foreignCollection), "Expected each lookup to be tracked as an index use"); @@ -279,7 +272,7 @@ const pipeline = [ ]; assert.eq(2, col.aggregate(pipeline).itcount()); assert.eq(1, getUsageCount("_id_", col), "Expected aggregation to use _id index"); -if (!checkSBEEnabledOnHostingDB(db, ["featureFlagSBELookupPushdown"])) { +if (!checkSBEEnabled(db, ["featureFlagSBELookupPushdown"])) { assert.eq(2, getUsageCount("_id_", foreignCollection), "Expected each lookup to be tracked as an index use"); diff --git a/jstests/core/json_schema/json_schema.js b/jstests/core/json_schema/json_schema.js index a69db77c60d..c1e06344498 100644 --- a/jstests/core/json_schema/json_schema.js +++ b/jstests/core/json_schema/json_schema.js @@ -1,7 +1,7 @@ // listCollections tests expect that a collection is not implicitly created after a drop. // @tags: [ // assumes_no_implicit_collection_creation_after_drop, -// requires_non_retryable_commands +// requires_non_retryable_commands, // ] /** diff --git a/jstests/core/json_schema/misc_validation.js b/jstests/core/json_schema/misc_validation.js index 569d98be258..58e76fc0d68 100644 --- a/jstests/core/json_schema/misc_validation.js +++ b/jstests/core/json_schema/misc_validation.js @@ -19,7 +19,6 @@ * requires_replication, * # This test depends on hardcoded database name equality. * tenant_migration_incompatible, - * references_foreign_collection, * ] */ (function() { diff --git a/jstests/core/latch_analyzer.js b/jstests/core/latch_analyzer.js index 96c20d9c320..73aa652c6c1 100644 --- a/jstests/core/latch_analyzer.js +++ b/jstests/core/latch_analyzer.js @@ -1,7 +1,7 @@ /** * Verify that the LatchAnalyzer is working to expectations * - * @tags: [multiversion_incompatible, no_selinux, requires_latch_analyzer] + * @tags: [multiversion_incompatible, no_selinux] */ (function() { diff --git a/jstests/core/list_collections_name_only.js b/jstests/core/list_collections_name_only.js index bd7bb7e249d..0db5ac8770f 100644 --- a/jstests/core/list_collections_name_only.js +++ b/jstests/core/list_collections_name_only.js @@ -2,7 +2,7 @@ * Test nameOnly option of listCollections * * @tags: [ - * # We expect the collection to exist when running listCollections + * // We expect the collection to exist when running listCollections * does_not_support_stepdowns * ] */ diff --git a/jstests/core/map_reduce_subplanning.js b/jstests/core/map_reduce_subplanning.js deleted file mode 100644 index 35fed994ba8..00000000000 --- a/jstests/core/map_reduce_subplanning.js +++ /dev/null @@ -1,47 +0,0 @@ -// The test runs commands that are not allowed with security token: mapReduce. -// @tags: [ -// not_allowed_with_security_token, -// does_not_support_stepdowns, -// requires_fastcount, -// requires_getmore, -// requires_non_retryable_writes, -// # This test has statements that do not support non-local read concern. -// does_not_support_causal_consistency, -// # Uses mapReduce command. -// requires_scripting, -// ] - -load("jstests/aggregation/extras/utils.js"); // For resultsEq -(function() { -"use strict"; - -const coll = db.map_reduce_subplanning; -coll.drop(); -db.getCollection("mrOutput").drop(); - -coll.createIndex({a: 1, c: 1}); -coll.createIndex({b: 1, c: 1}); -coll.createIndex({a: 1}); -coll.createIndex({b: 1}); - -assert.commandWorked(coll.insert({a: 2})); -assert.commandWorked(coll.insert({b: 3})); -assert.commandWorked(coll.insert({b: 3})); -assert.commandWorked(coll.insert({a: 2, b: 3})); - -assert.commandWorked(coll.mapReduce( - function() { - if (!this.hasOwnProperty('a')) { - emit('a', 0); - } else { - emit('a', this.a); - } - }, - function(key, vals) { - return vals.reduce((a, b) => a + b, 0); - }, - {out: {merge: "mrOutput"}, query: {$or: [{a: 2}, {b: 3}]}})); - -assert(resultsEq([{"_id": "a", "value": 4}], db.getCollection("mrOutput").find().toArray()), - db.getCollection("mrOutput").find().toArray()); -})(); diff --git a/jstests/core/match_with_and_or_lockstep_enumeration.js b/jstests/core/match_with_and_or_lockstep_enumeration.js deleted file mode 100644 index 3c16f63d6ab..00000000000 --- a/jstests/core/match_with_and_or_lockstep_enumeration.js +++ /dev/null @@ -1,110 +0,0 @@ - -/** - * Test a particular nested $and/$or query. This test was designed to reproduce SERVER-83091, a bug - * in which this query could trigger an infinite loop during plan enumeration. - * - * @tags: [ - * # We need to set a parameter which is meaningful only on mongod. - * assumes_against_mongod_not_mongos, - * # We expect to be reading from the same node where we configured a parameter. - * assumes_read_preference_unchanged, - * # Runs the setParameter command, which cannot work in passthroughs that have stepdowns. - * does_not_support_stepdowns, - * ] - */ -(function() { -"use strict"; - -const coll = db.and_or_lockstep_coll; -coll.drop(); - -// We test running the $and/$or query with both lockstep $or enumeration enabled and with it -// disabled. SERVER-83091 only affected lockstep $or enumeration, which is disabled by default in -// this branch. -const paramName = "internalQueryEnumerationPreferLockstepOrEnumeration"; - -// At the time when SERVER-83091 was filed, match expression rewrites would rewrite the {b: 4} and -// {b: 5} to {b: {$in: [4, 5]}}, resulting in a nested $or predicate which looks like this: -// -// $and -// a $eq 1 -// $or -// b $in [ 4, 5 ] -// $or -// $and -// b $eq 2 -// c $eq 3 -// b $in [ 6, 7 ] -// -// The fact that this rewrite could result in an $or with a direct $or child was key to reproducing -// SERVER-83091. Note that SERVER-83602 improves the system such that it will not unnecessarily -// generate an $or with a direct $or child. -// -// This nested $or is never simplified later on. (In constrast, if the query is originally expressed -// with a nested $or it will get simplified; that's why the repro requires an $or containing some -// predicates that get rewritten to an $in.) The bug related to how lockstep enumeration dealt with -// nested $or nodes. A very particular query structure was also necessary, because to exercise the -// bug we needed to get into a state where the inner $or reached its limit on the number of plans it -// was willing to enumerate before the outer $or did. -const predicate = { - a: 1, - $or: [ - {b: 2, c: 3}, - {b: 4}, - {b: 5}, - { - b: {$in: [6, 7]}, - } - ], -}; - -const docs = [ - // No "a" field. - {x: 2}, - - // "a" does not match. - {a: 2, x: 2}, - - // No matching "b" or "c" value. - {a: 1, x: 2, b: 8}, - {a: 1, x: 2, b: 2, c: 9}, - - // Matching values. - {a: 1, x: 2, b: 2, c: 3}, - {a: 1, x: 2, b: 4}, - {a: 1, x: 2, b: 5}, - {a: 1, x: 2, b: 6}, - {a: 1, x: 2, b: 7}, -]; -const kNumMatchingValues = 5; -assert.commandWorked(coll.insert(docs)); - -const indexes = [ - {a: 1, b: 1}, - {a: 1, c: 1}, -]; -for (let index of indexes) { - assert.commandWorked(coll.createIndex(index)); -} - -function setLockstepOrParam(val) { - assert.commandWorked(db.adminCommand({setParameter: 1, [paramName]: val})); -} - -function runTest(lockstepOrEnabled) { - setLockstepOrParam(lockstepOrEnabled); - jsTestLog(`Lockstep $or enumeration enabled: ${lockstepOrEnabled}`); - assert.eq(coll.find(predicate).itcount(), kNumMatchingValues); -} - -// Get the default parameter value so it can be correctly reset if the test fails. Then run the test -// with lockstep $or plan enumeration enabled and with it disabled. -const origParamValue = - assert.commandWorked(db.adminCommand({getParameter: 1, [paramName]: 1}))[paramName]; -try { - runTest(true); - runTest(false); -} finally { - setLockstepOrParam(origParamValue); -} -}()); diff --git a/jstests/core/monotonic_date_operations.js b/jstests/core/monotonic_date_operations.js deleted file mode 100644 index 5e28967bc44..00000000000 --- a/jstests/core/monotonic_date_operations.js +++ /dev/null @@ -1,408 +0,0 @@ -/** - * Tests that date expressions remain monotonic despite timezone changes. - */ - -(function() { -"use strict"; - -const coll = db.monotonic_date_operations; - -const runTest = function(documents, pipeline, expectedDocs) { - coll.drop(); - assert.commandWorked(coll.insertMany(documents)); - pipeline.push({$sort: {_id: 1}}); - assert.eq(coll.aggregate(pipeline).toArray(), expectedDocs); -}; - -const dateFormat = "%Y-%m-%dT%H:%M:%S.%L%z"; - -// New York DST fall switch -runTest( - [ - {_id: 0, time: ISODate("2022-11-06T03:00:00Z")}, // 2022-11-05T23:00:00 in New York - {_id: 1, time: ISODate("2022-11-06T05:00:00Z")}, - {_id: 2, time: ISODate("2022-11-06T05:30:00Z")}, - {_id: 3, time: ISODate("2022-11-06T06:00:00Z")}, // Moment of DST switch - {_id: 4, time: ISODate("2022-11-06T06:30:00Z")}, - {_id: 5, time: ISODate("2022-11-06T07:00:00Z")}, - ], - [{ - $project: { - localTime: { - $dateToString: { - date: "$time", - format: dateFormat, - timezone: "America/New_York", - } - }, - diffMinutes: { - $dateDiff: { - startDate: ISODate("2022-11-06T04:00:00Z"), - endDate: "$time", - unit: "minute", - timezone: "America/New_York", - } - }, - diffDays: { - $dateDiff: { - startDate: ISODate("2022-11-06T04:00:00Z"), - endDate: "$time", - unit: "day", - timezone: "America/New_York", - } - } - } - }], - [ - { - _id: 0, - localTime: "2022-11-05T23:00:00.000-0400", - diffMinutes: NumberLong(-60), - diffDays: NumberLong(-1) - }, - { - _id: 1, - localTime: "2022-11-06T01:00:00.000-0400", - diffMinutes: NumberLong(60), - diffDays: NumberLong(0) - }, - { - _id: 2, - localTime: "2022-11-06T01:30:00.000-0400", - diffMinutes: NumberLong(90), - diffDays: NumberLong(0) - }, - { - _id: 3, - localTime: "2022-11-06T01:00:00.000-0500", - diffMinutes: NumberLong(120), - diffDays: NumberLong(0) - }, - { - _id: 4, - localTime: "2022-11-06T01:30:00.000-0500", - diffMinutes: NumberLong(150), - diffDays: NumberLong(0) - }, - { - _id: 5, - localTime: "2022-11-06T02:00:00.000-0500", - diffMinutes: NumberLong(180), - diffDays: NumberLong(0) - } - ]); - -// New York DST spring switch -runTest( - [ - {_id: 0, time: ISODate("2022-03-13T06:00:00Z")}, // 2022-03-13T01:00:00 in New York - {_id: 1, time: ISODate("2022-03-13T06:30:00Z")}, - {_id: 2, time: ISODate("2022-03-13T07:00:00Z")}, // Moment of DST switch - {_id: 3, time: ISODate("2022-03-13T07:30:00Z")}, - {_id: 4, time: ISODate("2022-03-13T08:00:00Z")}, - ], - [{ - $project: { - localTime: { - $dateToString: { - date: "$time", - format: dateFormat, - timezone: "America/New_York", - } - }, - diffMinutes: { - $dateDiff: { - startDate: ISODate("2022-03-13T00:00:00Z"), - endDate: "$time", - unit: "minute", - timezone: "America/New_York", - } - }, - diffDays: { - $dateDiff: { - startDate: ISODate("2022-03-13T00:00:00Z"), - endDate: "$time", - unit: "day", - timezone: "America/New_York", - } - } - } - }], - [ - { - _id: 0, - localTime: "2022-03-13T01:00:00.000-0500", - diffMinutes: NumberLong(360), - diffDays: NumberLong(1) - }, - { - _id: 1, - localTime: "2022-03-13T01:30:00.000-0500", - diffMinutes: NumberLong(390), - diffDays: NumberLong(1) - }, - { - _id: 2, - localTime: "2022-03-13T03:00:00.000-0400", - diffMinutes: NumberLong(420), - diffDays: NumberLong(1) - }, - { - _id: 3, - localTime: "2022-03-13T03:30:00.000-0400", - diffMinutes: NumberLong(450), - diffDays: NumberLong(1) - }, - { - _id: 4, - localTime: "2022-03-13T04:00:00.000-0400", - diffMinutes: NumberLong(480), - diffDays: NumberLong(1) - }, - ]); - -// Samoa day skip -runTest( - [ - {_id: 0, time: ISODate("2011-12-29T10:00:00Z")}, - {_id: 1, time: ISODate("2011-12-30T09:00:00Z")}, // 2011-12-29T23:00:00 in Apia, Samoa - {_id: 2, time: ISODate("2011-12-30T09:30:00Z")}, - {_id: 3, time: ISODate("2011-12-30T10:00:00Z")}, // Day skip moment - {_id: 4, time: ISODate("2011-12-30T10:30:00Z")}, - {_id: 5, time: ISODate("2011-12-30T11:00:00Z")}, - {_id: 6, time: ISODate("2011-12-30T10:00:00Z")}, - ], - [{ - $project: { - localTime: { - $dateToString: { - date: "$time", - format: dateFormat, - timezone: "Pacific/Apia", - } - }, - diffMinutes: { - $dateDiff: { - startDate: ISODate("2011-12-28T10:00:00Z"), - endDate: "$time", - unit: "minute", - timezone: "Pacific/Apia", - } - }, - diffDays: { - $dateDiff: { - startDate: ISODate("2011-12-28T10:00:00Z"), - endDate: "$time", - unit: "day", - timezone: "Pacific/Apia", - } - } - } - }], - [ - { - _id: 0, - localTime: "2011-12-29T00:00:00.000-1000", - diffMinutes: NumberLong(1440), - diffDays: NumberLong(1) - }, - { - _id: 1, - localTime: "2011-12-29T23:00:00.000-1000", - diffMinutes: NumberLong(2820), - diffDays: NumberLong(1) - }, - { - _id: 2, - localTime: "2011-12-29T23:30:00.000-1000", - diffMinutes: NumberLong(2850), - diffDays: NumberLong(1) - }, - { - _id: 3, - localTime: "2011-12-31T00:00:00.000+1400", - diffMinutes: NumberLong(2880), - diffDays: NumberLong(3) - }, - { - _id: 4, - localTime: "2011-12-31T00:30:00.000+1400", - diffMinutes: NumberLong(2910), - diffDays: NumberLong(3) - }, - { - _id: 5, - localTime: "2011-12-31T01:00:00.000+1400", - diffMinutes: NumberLong(2940), - diffDays: NumberLong(3) - }, - { - _id: 6, - localTime: "2011-12-31T00:00:00.000+1400", - diffMinutes: NumberLong(2880), - diffDays: NumberLong(3) - }, - ]); - -// Sao Paulo, Brazil DST fall switch -runTest( - [ - { - _id: 0, - time: ISODate("2018-11-04T02:00:00Z") - }, // 2018-11-03T23:00:00 in Sao Paulo, Brazil - {_id: 1, time: ISODate("2018-11-04T02:30:00Z")}, - {_id: 2, time: ISODate("2018-11-04T03:00:00Z")}, // Moment of DST switch - {_id: 3, time: ISODate("2018-11-04T03:30:00Z")}, - {_id: 4, time: ISODate("2018-11-04T04:00:00Z")}, - {_id: 5, time: ISODate("2018-11-04T04:30:00Z")}, - ], - [{ - $project: { - localTime: { - $dateToString: { - date: "$time", - format: dateFormat, - timezone: "America/Sao_Paulo", - } - }, - diffMinutes: { - $dateDiff: { - startDate: ISODate("2018-11-04T02:00:00Z"), - endDate: "$time", - unit: "minute", - timezone: "America/Sao_Paulo", - } - }, - diffDays: { - $dateDiff: { - startDate: ISODate("2018-11-04T02:00:00Z"), - endDate: "$time", - unit: "day", - timezone: "America/Sao_Paulo", - } - } - } - }], - [ - { - _id: 0, - localTime: "2018-11-03T23:00:00.000-0300", - diffMinutes: NumberLong(0), - diffDays: NumberLong(0) - }, - { - _id: 1, - localTime: "2018-11-03T23:30:00.000-0300", - diffMinutes: NumberLong(30), - diffDays: NumberLong(0) - }, - { - _id: 2, - localTime: "2018-11-04T01:00:00.000-0200", - diffMinutes: NumberLong(60), - diffDays: NumberLong(1) - }, - { - _id: 3, - localTime: "2018-11-04T01:30:00.000-0200", - diffMinutes: NumberLong(90), - diffDays: NumberLong(1) - }, - { - _id: 4, - localTime: "2018-11-04T02:00:00.000-0200", - diffMinutes: NumberLong(120), - diffDays: NumberLong(1) - }, - { - _id: 5, - localTime: "2018-11-04T02:30:00.000-0200", - diffMinutes: NumberLong(150), - diffDays: NumberLong(1) - }, - ]); - -// Sao Paulo, Brazil DST sprint switch -runTest( - [ - {_id: 0, time: ISODate("2018-02-18T00:30:00Z")}, // 2018-02-17T22:30:00 in Sao Paulo, Brazi - {_id: 1, time: ISODate("2018-02-18T01:00:00Z")}, - {_id: 2, time: ISODate("2018-02-18T01:30:00Z")}, - {_id: 3, time: ISODate("2018-02-18T02:00:00Z")}, // Moment of DST switch - {_id: 4, time: ISODate("2018-02-18T02:30:00Z")}, - {_id: 5, time: ISODate("2018-02-18T03:00:00Z")}, - {_id: 6, time: ISODate("2018-02-18T03:30:00Z")}, - ], - [{ - $project: { - localTime: { - $dateToString: { - date: "$time", - format: dateFormat, - timezone: "America/Sao_Paulo", - } - }, - diffMinutes: { - $dateDiff: { - startDate: ISODate("2018-02-18T00:30:00Z"), - endDate: "$time", - unit: "minute", - timezone: "America/Sao_Paulo", - } - }, - diffDays: { - $dateDiff: { - startDate: ISODate("2018-02-18T00:30:00Z"), - endDate: "$time", - unit: "day", - timezone: "America/Sao_Paulo", - } - } - } - }], - [ - { - _id: 0, - localTime: "2018-02-17T22:30:00.000-0200", - diffMinutes: NumberLong(0), - diffDays: NumberLong(0) - }, - { - _id: 1, - localTime: "2018-02-17T23:00:00.000-0200", - diffMinutes: NumberLong(30), - diffDays: NumberLong(0) - }, - { - _id: 2, - localTime: "2018-02-17T23:30:00.000-0200", - diffMinutes: NumberLong(60), - diffDays: NumberLong(0) - }, - { - _id: 3, - localTime: "2018-02-17T23:00:00.000-0300", - diffMinutes: NumberLong(90), - diffDays: NumberLong(0) - }, - { - _id: 4, - localTime: "2018-02-17T23:30:00.000-0300", - diffMinutes: NumberLong(120), - diffDays: NumberLong(0) - }, - { - _id: 5, - localTime: "2018-02-18T00:00:00.000-0300", - diffMinutes: NumberLong(150), - diffDays: NumberLong(1) - }, - { - _id: 6, - localTime: "2018-02-18T00:30:00.000-0300", - diffMinutes: NumberLong(180), - diffDays: NumberLong(1) - } - ]); -})(); diff --git a/jstests/core/mr_single_reduce.js b/jstests/core/mr_single_reduce.js deleted file mode 100644 index 380182d61fb..00000000000 --- a/jstests/core/mr_single_reduce.js +++ /dev/null @@ -1,22 +0,0 @@ -// @tags: [ -// # Step-down can cause mapReduce to fail. -// does_not_support_stepdowns, -// ] -(function() { -"use strict"; -const coll = db.bar; - -assert.commandWorked(coll.insert({x: 1})); - -const map = function() { - emit(0, "mapped value"); -}; - -const reduce = function(key, values) { - return "reduced value"; -}; - -const res = assert.commandWorked( - db.runCommand({mapReduce: 'bar', map: map, reduce: reduce, out: {inline: 1}})); -assert.eq(res.results[0], {_id: 0, value: "reduced value"}); -}()); diff --git a/jstests/core/nested_or_duplicate_predicates_index_scan.js b/jstests/core/nested_or_duplicate_predicates_index_scan.js deleted file mode 100644 index fae1c8fff9b..00000000000 --- a/jstests/core/nested_or_duplicate_predicates_index_scan.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Test a nested $or query which reproduces SERVER-84013, a bug in the subplanner. This bug had to - * do with the subplanner assuming that multiple invocations of MatchExpression::optimize() yielded - * the same expressions, which turns out not to be the case. The queries in this regression test - * excerise the $or -> $in rewrite which produce new $in expressions which themselves could be - * further optimized. - */ -(function() { -"use strict"; - -const coll = db.server84013; -coll.drop(); - -const docs = [ - {_id: 0, Country: {_id: "US"}, State: "California", City: "SanFrancisco"}, - {_id: 1, Country: {_id: "US"}, State: "NewYork", City: "Buffalo"}, -]; - -assert.commandWorked(coll.insert(docs)); -assert.commandWorked(coll.createIndex({"Country._id": 1, "State": 1})); - -assert.eq(docs.slice(0, 1), - coll.find({ - "$or": [ - {"Country._id": "DNE"}, - { - "Country._id": "US", - "State": "California", - "$or": [{"City": "SanFrancisco"}, {"City": {"$in": ["SanFrancisco"]}}] - } - ] - }) - .toArray()); - -assert.eq(docs.slice(0, 1), - coll.find({ - "$or": [ - {"Country._id": "DNE"}, - { - "Country._id": "US", - "State": "California", - "$or": [ - {"City": "SanFrancisco"}, - {"City": {$in: ["SanFrancisco"]}}, - {"Country._id": "DNE"}, - ] - }, - ] - }) - .toArray()); -})(); diff --git a/jstests/core/no_db_created.js b/jstests/core/no_db_created.js index d2d5bbcae7d..bb0d998a3fe 100644 --- a/jstests/core/no_db_created.js +++ b/jstests/core/no_db_created.js @@ -1,4 +1,4 @@ -// @tags: [requires_non_retryable_commands, uses_compact] +// @tags: [requires_non_retryable_commands] // checks that operations do not create a database @@ -34,4 +34,4 @@ assert.commandFailed(coll.runCommand("collMod", {expireAfterSeconds: 1})); noDB(mydb); assert.commandWorked(coll.insert({})); mydb.dropDatabase(); -}()); +}());
\ No newline at end of file diff --git a/jstests/core/notablescan.js b/jstests/core/notablescan.js new file mode 100644 index 00000000000..baef5d56ae6 --- /dev/null +++ b/jstests/core/notablescan.js @@ -0,0 +1,61 @@ +// check notablescan mode +// +// @tags: [ +// assumes_against_mongod_not_mongos, +// # This test attempts to perform read operations after having enabled the notablescan server +// # parameter. The former operations may be routed to a secondary in the replica set, whereas the +// # latter must be routed to the primary. +// assumes_read_preference_unchanged, +// assumes_superuser_permissions, +// does_not_support_stepdowns, +// # Server parameters are stored in-memory only so are not transferred onto the recipient. This +// # test sets the server parameter "notablescan" to force the node to not execute queries that +// # require a collection scan and return an error. +// tenant_migration_incompatible, +// ] + +t = db.test_notablescan; +t.drop(); + +try { + assert.commandWorked(db._adminCommand({setParameter: 1, notablescan: true})); + // commented lines are SERVER-2222 + if (0) { // SERVER-2222 + assert.throws(function() { + t.find({a: 1}).toArray(); + }); + } + t.save({a: 1}); + assert.throws(function() { + t.count({a: 1}); + }); + if (0) { + assert.throws(function() { + t.find({}).toArray(); + }); + } + assert.eq(1, t.find({}).itcount()); // SERVER-274 + + let err = assert.throws(function() { + t.find({a: 1}).toArray(); + }); + assert.includes(err.toString(), "No indexed plans available, and running with 'notablescan'"); + + err = assert.throws(function() { + t.find({a: 1}).hint({$natural: 1}).toArray(); + }); + assert.includes(err.toString(), + "hint $natural is not allowed, because 'notablescan' is enabled"); + + t.createIndex({a: 1}); + assert.eq(0, t.find({a: 1, b: 1}).itcount()); + assert.eq(1, t.find({a: 1, b: null}).itcount()); + + // SERVER-4327 + assert.eq(0, t.find({a: {$in: []}}).itcount()); + assert.eq(0, t.find({a: {$in: []}, b: 0}).itcount()); +} finally { + // We assume notablescan was false before this test started and restore that + // expected value. + assert.commandWorked(db._adminCommand({setParameter: 1, notablescan: false})); +} diff --git a/jstests/core/null_query_semantics.js b/jstests/core/null_query_semantics.js index aa9042c5ac5..1ff14cc710c 100644 --- a/jstests/core/null_query_semantics.js +++ b/jstests/core/null_query_semantics.js @@ -41,9 +41,6 @@ function testNullSemantics(coll) { [{_id: "a_null", a: null}, {_id: "a_undefined", a: undefined}, {_id: "no_a"}]; assert(resultsEq(expected, noProjectResults), tojson(noProjectResults)); - const count = coll.count({a: {$eq: null}}); - assert.eq(count, expected.length); - const projectResults = coll.find({a: {$eq: null}}, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults)); }()); @@ -60,9 +57,6 @@ function testNullSemantics(coll) { ]; assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - const count = coll.count({a: {$ne: null}}); - assert.eq(count, expected.length); - const projectResults = coll.find({a: {$ne: null}}, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults)); }()); @@ -78,9 +72,6 @@ function testNullSemantics(coll) { {_id: "no_a"}, ]; - const count = coll.count(query); - assert.eq(count, expected.length); - assert(resultsEq(noProjectResults, expected), noProjectResults); const projectResults = coll.find(query, projectToOnlyA).toArray(); @@ -98,9 +89,6 @@ function testNullSemantics(coll) { {_id: "no_a"}, ]; - const count = coll.count(query); - assert.eq(count, expected.length); - assert(resultsEq(noProjectResults, expected), noProjectResults); const projectResults = coll.find(query, projectToOnlyA).toArray(); @@ -118,9 +106,6 @@ function testNullSemantics(coll) { {_id: "a_subobject_b_undefined", a: {b: undefined}}, ]; - const count = coll.count(query); - assert.eq(count, expected.length); - assert(resultsEq(noProjectResults, expected), noProjectResults); const projectResults = coll.find(query, projectToOnlyA).toArray(); @@ -141,9 +126,6 @@ function testNullSemantics(coll) { ]; assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - const count = coll.count(query); - assert.eq(count, expected.length); - const projectResults = coll.find(query, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), projectResults); }()); @@ -155,9 +137,6 @@ function testNullSemantics(coll) { ]; assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - const count = coll.count({a: {$exists: false}}); - assert.eq(count, expected.length); - const projectResults = coll.find({a: {$exists: false}}, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults)); }()); @@ -165,19 +144,17 @@ function testNullSemantics(coll) { // Test the semantics of the query {"a.b": {$eq: null}}. (function testDottedEqualsNull() { const noProjectResults = coll.find({"a.b": {$eq: null}}).toArray(); - const expected = [ - {_id: "a_empty_subobject", a: {}}, - {_id: "a_null", a: null}, - {_id: "a_number", a: 4}, - {_id: "a_subobject_b_null", a: {b: null}}, - {_id: "a_subobject_b_undefined", a: {b: undefined}}, - {_id: "a_undefined", a: undefined}, - {_id: "no_a"} - ]; - assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - - const count = coll.count({"a.b": {$eq: null}}); - assert.eq(count, expected.length); + assert(resultsEq(noProjectResults, + [ + {_id: "a_empty_subobject", a: {}}, + {_id: "a_null", a: null}, + {_id: "a_number", a: 4}, + {_id: "a_subobject_b_null", a: {b: null}}, + {_id: "a_subobject_b_undefined", a: {b: undefined}}, + {_id: "a_undefined", a: undefined}, + {_id: "no_a"} + ]), + tojson(noProjectResults)); const projectResults = coll.find({"a.b": {$eq: null}}, projectToOnlyADotB).toArray(); assert(resultsEq(projectResults, @@ -188,11 +165,8 @@ function testNullSemantics(coll) { // Test the semantics of the query {"a.b": {$ne: null}}. (function testDottedNotEqualsNull() { const noProjectResults = coll.find({"a.b": {$ne: null}}).toArray(); - const expected = [{_id: "a_subobject_b_not_null", a: {b: "hi"}}]; - assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - - const count = coll.count({"a.b": {$ne: null}}); - assert.eq(count, expected.length); + assert(resultsEq(noProjectResults, [{_id: "a_subobject_b_not_null", a: {b: "hi"}}]), + tojson(noProjectResults)); const projectResults = coll.find({"a.b": {$ne: null}}, projectToOnlyADotB).toArray(); assert(resultsEq(projectResults, [{a: {b: "hi"}}]), tojson(projectResults)); @@ -209,9 +183,6 @@ function testNullSemantics(coll) { ]; assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - const count = coll.count({"a.b": {$exists: false}}); - assert.eq(count, expected.length); - const projectResults = coll.find({"a.b": {$exists: false}}, projectToOnlyADotB).toArray(); assert(resultsEq(projectResults, [{}, {a: {}}, {}, {}, {}]), tojson(projectResults)); }()); @@ -231,9 +202,6 @@ function testNullSemantics(coll) { `Expected no results for query ${tojson(elemMatchQuery)}, got ` + tojson(noProjectResults)); - const count = coll.count(elemMatchQuery); - assert.eq(count, 0); - let projectResults = coll.find(elemMatchQuery, projectToOnlyA).toArray(); assert(resultsEq(projectResults, []), `Expected no results for query ${tojson(elemMatchQuery)}, got ` + @@ -282,9 +250,6 @@ function testNullSemantics(coll) { ]; assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - const count = coll.count({a: {$eq: null}}); - assert.eq(count, expected.length); - const projectResults = coll.find({a: {$eq: null}}, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults)); }()); @@ -309,9 +274,6 @@ function testNullSemantics(coll) { ]; assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - const count = coll.count({a: {$ne: null}}); - assert.eq(count, expected.length); - const projectResults = coll.find({a: {$ne: null}}, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults)); }()); @@ -335,8 +297,6 @@ function testNullSemantics(coll) { {_id: "a_value_array_no_nulls", a: [1, "string", 4]}, ]; assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - const count = coll.count({a: {$not: {$gte: null}}}); - assert.eq(count, expected.length); }()); // Test the semantics of the query {a: {$in: [null, <number>]}}. @@ -354,9 +314,6 @@ function testNullSemantics(coll) { assert(resultsEq(noProjectResults, expected), noProjectResults); - const count = coll.count({a: {$in: [null, 75]}}); - assert.eq(count, expected.length); - const projectResults = coll.find(query, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), projectResults); }()); @@ -376,9 +333,6 @@ function testNullSemantics(coll) { assert(resultsEq(noProjectResults, expected), noProjectResults); - const count = coll.count({$or: [{a: null}, {a: 75}]}); - assert.eq(count, expected.length); - const projectResults = coll.find(query, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), projectResults); }()); @@ -406,9 +360,6 @@ function testNullSemantics(coll) { assert(resultsEq(noProjectResults, expected), noProjectResults); - const count = coll.count({a: {$nin: [null, 75]}}); - assert.eq(count, expected.length); - const projectResults = coll.find(query, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), projectResults); }()); @@ -444,9 +395,6 @@ function testNullSemantics(coll) { assert(resultsEq(noProjectResults, expected), noProjectResults); - const count = coll.count({a: {$nin: [null, []]}}); - assert.eq(count, expected.length); - const projectResults = coll.find(query, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), projectResults); }()); @@ -472,9 +420,6 @@ function testNullSemantics(coll) { assert(resultsEq(noProjectResults, expected), noProjectResults); - const count = coll.count(query); - assert.eq(count, expected.length); - const projectResults = coll.find(query, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), projectResults); }()); @@ -490,9 +435,6 @@ function testNullSemantics(coll) { ]; assert(resultsEq(noProjectResults, expectedEqualToNull), tojson(noProjectResults)); - const count = coll.count({a: {$elemMatch: {$eq: null}}}); - assert.eq(count, expectedEqualToNull.length); - let projectResults = coll.find({a: {$elemMatch: {$eq: null}}}, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expectedEqualToNull)), tojson(projectResults)); @@ -525,23 +467,25 @@ function testNullSemantics(coll) { // value for "b". (function testDottedEqualsNull() { const noProjectResults = coll.find({"a.b": {$eq: null}}).toArray(); - const expected = [ - {_id: "a_empty_subobject", a: {}}, - {_id: "a_null", a: null}, - {_id: "a_number", a: 4}, - {_id: "a_subobject_b_null", a: {b: null}}, - {_id: "a_subobject_b_undefined", a: {b: undefined}}, - {_id: "a_undefined", a: undefined}, - {_id: "no_a"}, - {_id: "a_object_array_all_b_nulls", a: [{b: null}, {b: undefined}, {b: null}, {}]}, - {_id: "a_object_array_some_b_nulls", a: [{b: null}, {b: 3}, {b: null}]}, - {_id: "a_object_array_some_b_undefined", a: [{b: undefined}, {b: 3}]}, - {_id: "a_object_array_some_b_missing", a: [{b: 3}, {}]}, - ]; - assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - - const count = coll.count({"a.b": {$eq: null}}); - assert.eq(count, expected.length); + assert( + resultsEq(noProjectResults, + [ + {_id: "a_empty_subobject", a: {}}, + {_id: "a_null", a: null}, + {_id: "a_number", a: 4}, + {_id: "a_subobject_b_null", a: {b: null}}, + {_id: "a_subobject_b_undefined", a: {b: undefined}}, + {_id: "a_undefined", a: undefined}, + {_id: "no_a"}, + { + _id: "a_object_array_all_b_nulls", + a: [{b: null}, {b: undefined}, {b: null}, {}] + }, + {_id: "a_object_array_some_b_nulls", a: [{b: null}, {b: 3}, {b: null}]}, + {_id: "a_object_array_some_b_undefined", a: [{b: undefined}, {b: 3}]}, + {_id: "a_object_array_some_b_missing", a: [{b: 3}, {}]}, + ]), + tojson(noProjectResults)); const projectResults = coll.find({"a.b": {$eq: null}}, projectToOnlyADotB).toArray(); assert(resultsEq(projectResults, @@ -564,20 +508,18 @@ function testNullSemantics(coll) { // Test the semantics of the query {"a.b": {$ne: null}}. (function testDottedNotEqualsNull() { const noProjectResults = coll.find({"a.b": {$ne: null}}).toArray(); - const expected = [ - {_id: "a_subobject_b_not_null", a: {b: "hi"}}, - {_id: "a_double_array", a: [[]]}, - {_id: "a_empty_array", a: []}, - {_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]}, - {_id: "a_value_array_all_nulls", a: [null, null]}, - {_id: "a_value_array_no_nulls", a: [1, "string", 4]}, - {_id: "a_value_array_with_null", a: [1, "string", null, 4]}, - {_id: "a_value_array_with_undefined", a: [1, "string", undefined, 4]} - ]; - assert(resultsEq(noProjectResults, expected), tojson(noProjectResults)); - - const count = coll.count({"a.b": {$ne: null}}); - assert.eq(count, expected.length); + assert(resultsEq(noProjectResults, + [ + {_id: "a_subobject_b_not_null", a: {b: "hi"}}, + {_id: "a_double_array", a: [[]]}, + {_id: "a_empty_array", a: []}, + {_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]}, + {_id: "a_value_array_all_nulls", a: [null, null]}, + {_id: "a_value_array_no_nulls", a: [1, "string", 4]}, + {_id: "a_value_array_with_null", a: [1, "string", null, 4]}, + {_id: "a_value_array_with_undefined", a: [1, "string", undefined, 4]} + ]), + tojson(noProjectResults)); const projectResults = coll.find({"a.b": {$ne: null}}, projectToOnlyADotB).toArray(); assert(resultsEq(projectResults, @@ -612,9 +554,6 @@ function testNullSemantics(coll) { {_id: "a_object_array_some_b_missing", a: [{b: 3}, {}]}, ]; - const count = coll.count({"a.b": {$in: [null, 75]}}); - assert.eq(count, expected.length); - assert(resultsEq(noProjectResults, expected), noProjectResults); }()); @@ -636,9 +575,6 @@ function testNullSemantics(coll) { {_id: "a_object_array_some_b_missing", a: [{b: 3}, {}]}, ]; - const count = coll.count({$or: [{"a.b": null}, {"a.b": 75}]}); - assert.eq(count, expected.length); - assert(resultsEq(noProjectResults, expected), noProjectResults); }()); @@ -659,9 +595,6 @@ function testNullSemantics(coll) { assert(resultsEq(noProjectResults, expected), noProjectResults); - const count = coll.count({"a.b": {$nin: [null, 75]}}); - assert.eq(count, expected.length); - const projectResults = coll.find(query, projectToOnlyA).toArray(); assert(resultsEq(projectResults, extractAValues(expected)), projectResults); }()); @@ -680,9 +613,6 @@ function testNullSemantics(coll) { {_id: "a_value_array_with_undefined", a: [1, "string", undefined, 4]}, ]; - const count = coll.count({"a.b": {$nin: [null, /^str.*/]}}); - assert.eq(count, expected.length); - assert(resultsEq(noProjectResults, expected), noProjectResults); const projectResults = coll.find(query, projectToOnlyA).toArray(); @@ -695,9 +625,6 @@ function testNullSemantics(coll) { let results = coll.find({"a.b": {$elemMatch: {$eq: null}}}).toArray(); assert(resultsEq(results, []), tojson(results)); - const count = coll.count({"a.b": {$elemMatch: {$eq: null}}}); - assert.eq(count, 0); - results = coll.find({"a.b": {$elemMatch: {$ne: null}}}).toArray(); assert(resultsEq(results, []), tojson(results)); }()); @@ -715,9 +642,6 @@ function testNullSemantics(coll) { ]; assert(resultsEq(noProjectResults, expectedEqualToNull), tojson(noProjectResults)); - const count = coll.count({a: {$elemMatch: {b: {$eq: null}}}}); - assert.eq(count, expectedEqualToNull.length); - let projectResults = coll.find({a: {$elemMatch: {b: {$eq: null}}}}, projectToOnlyADotB).toArray(); assert(resultsEq(projectResults, diff --git a/jstests/core/opcounters_write_cmd.js b/jstests/core/opcounters_write_cmd.js index 9e0d68cd928..6f74185e9f7 100644 --- a/jstests/core/opcounters_write_cmd.js +++ b/jstests/core/opcounters_write_cmd.js @@ -2,7 +2,6 @@ // @tags: [ // uses_multiple_connections, // assumes_standalone_mongod, -// inspects_command_opcounters, // ] // Legacy write mode test also available at jstests/gle. diff --git a/jstests/core/operation_latency_histogram.js b/jstests/core/operation_latency_histogram.js index 3597a064429..c87d5bfe20b 100644 --- a/jstests/core/operation_latency_histogram.js +++ b/jstests/core/operation_latency_histogram.js @@ -12,7 +12,6 @@ // # Tenant migrations passthrough suites automatically retry operations on TenantMigrationAborted // # errors. // tenant_migration_incompatible, -// uses_compact // ] // diff --git a/jstests/core/or_to_in.js b/jstests/core/or_to_in.js index 5d3c745dc95..3c9cacfbcdf 100644 --- a/jstests/core/or_to_in.js +++ b/jstests/core/or_to_in.js @@ -25,25 +25,19 @@ function compareValues(v1, v2) { } // Check that 'expectedQuery' and 'actualQuery' have the same plans, and produce the same result. -function assertEquivPlanAndResult(expectedQuery, actualQuery, supportWithCollation) { +function assertEquivPlanAndResult(expectedQuery, actualQuery) { const expectedExplain = coll.find(expectedQuery).explain("queryPlanner"); const actualExplain = coll.find(actualQuery).explain("queryPlanner"); // The queries must be rewritten into the same form. - assert.docEq(expectedExplain.queryPlanner.parsedQuery, actualExplain.queryPlanner.parsedQuery); + assert.docEq(expectedExplain.parsedQuery, actualExplain.parsedQuery); - // We are always running these queries to ensure a server crash is not triggered. - // TODO SERVER-72450: Add appropriate assertions for the output. - const expectedExplainCollation = + // Check if the test queries produce the same plans with collations + const expectedExplainColln = coll.find(expectedQuery).sort({f1: 1}).collation({locale: 'en_US'}).explain("queryPlanner"); - const actualExplainCollation = + const actualExplainColln = coll.find(actualQuery).sort({f1: 1}).collation({locale: 'en_US'}).explain("queryPlanner"); - - if (supportWithCollation) { - // Check if the test queries produce the same plans with collations. - assert.docEq(expectedExplainCollation.queryPlanner.parsedQuery, - actualExplainCollation.queryPlanner.parsedQuery); - } + assert.docEq(expectedExplainColln.parsedQuery, actualExplainColln.parsedQuery); // Make sure both queries have the same access plan. const expectedPlan = getWinningPlan(expectedExplain.queryPlanner); @@ -57,13 +51,12 @@ function assertEquivPlanAndResult(expectedQuery, actualQuery, supportWithCollati const actualRes = coll.find(actualQuery).toArray(); assert(arrayEq(expectedRes, actualRes, false, compareValues), `expected=${expectedRes}, actual=${actualRes}`); - // also with collation - const expectedResCollation = + const expectedResColln = coll.find(expectedQuery).sort({f1: 1}).collation({locale: 'en_US'}).toArray(); - const actualResCollation = + const actualResColln = coll.find(actualQuery).sort({f1: 1}).collation({locale: 'en_US'}).toArray(); - assert(arrayEq(expectedResCollation, actualResCollation, false, compareValues), + assert(arrayEq(expectedResColln, actualResColln, false, compareValues), `expected=${expectedRes}, actual=${actualRes}`); } @@ -101,71 +94,32 @@ assert.commandWorked(coll.insert(data)); // Pairs of queries where the first one is expressed via OR (which is supposed to be // rewritten as IN), and the second one is an equivalent query using IN. -// -// The third element of the array is optional, if present, implies that the rewrite is not -// supported when there is a collation involved. -// -// TODO SERVER-72450: Remove or update this logic related to collation, and enforce stronger -// assertions. const positiveTestQueries = [ - {actualQuery: {$or: [{f1: 5}, {f1: 3}, {f1: 7}]}, expectedQuery: {f1: {$in: [7, 3, 5]}}}, - { - actualQuery: {$or: [{f1: {$eq: 5}}, {f1: {$eq: 3}}, {f1: {$eq: 7}}]}, - expectedQuery: {f1: {$in: [7, 3, 5]}} - }, - { - actualQuery: {$or: [{f1: 42}, {f1: NaN}, {f1: 99}]}, - expectedQuery: {f1: {$in: [42, NaN, 99]}} - }, - { - actualQuery: {$or: [{f1: /^x/}, {f1: "ab"}]}, - expectedQuery: {f1: {$in: [/^x/, "ab"]}}, - cannotRewriteWithCollation: true - }, - { - actualQuery: {$or: [{f1: /^x/}, {f1: "^a"}]}, - expectedQuery: {f1: {$in: [/^x/, "^a"]}}, - cannotRewriteWithCollation: true - }, - { - actualQuery: {$or: [{f1: 42}, {f1: null}, {f1: 99}]}, - expectedQuery: {f1: {$in: [42, 99, null]}} - }, - { - actualQuery: {$or: [{f1: 1}, {f2: 9}, {f1: 99}]}, - expectedQuery: {$or: [{f2: 9}, {f1: {$in: [1, 99]}}]} - }, - { - actualQuery: {$or: [{f1: {$regex: /^x/}}, {f1: {$regex: /ab/}}]}, - expectedQuery: {f1: {$in: [/^x/, /ab/]}} - }, - { - actualQuery: - {$and: [{$or: [{f1: 7}, {f1: 3}, {f1: 5}]}, {$or: [{f1: 1}, {f1: 2}, {f1: 3}]}]}, - expectedQuery: {$and: [{f1: {$in: [7, 3, 5]}}, {f1: {$in: [1, 2, 3]}}]} - }, - { - actualQuery: - {$or: [{$or: [{f1: 7}, {f1: 3}, {f1: 5}]}, {$or: [{f1: 1}, {f1: 2}, {f1: 3}]}]}, - expectedQuery: {$or: [{f1: {$in: [7, 3, 5]}}, {f1: {$in: [1, 2, 3]}}]} - }, - { - actualQuery: - {$or: [{$and: [{f1: 7}, {f2: 7}, {f1: 5}]}, {$or: [{f1: 1}, {f1: 2}, {f1: 3}]}]}, - expectedQuery: {$or: [{$and: [{f1: 7}, {f2: 7}, {f1: 5}]}, {f1: {$in: [1, 2, 3]}}]}, - }, - { - actualQuery: {$or: [{f2: [32, 52]}, {f2: [42, [13, 11]]}]}, - expectedQuery: {f2: {$in: [[32, 52], [42, [13, 11]]]}} - }, - {actualQuery: {$or: [{f2: 52}, {f2: 13}]}, expectedQuery: {f2: {$in: [52, 13]}}}, - {actualQuery: {$or: [{f2: [11]}, {f2: [23]}]}, expectedQuery: {f2: {$in: [[11], [23]]}}}, - {actualQuery: {$or: [{f1: 42}, {f1: null}]}, expectedQuery: {f1: {$in: [42, null]}}}, - { - actualQuery: {$or: [{f1: "a"}, {f1: "b"}, {f1: /c/}]}, - expectedQuery: {f1: {$in: ["a", "b", /c/]}}, - cannotRewriteWithCollation: true - }, + [{$or: [{f1: 5}, {f1: 3}, {f1: 7}]}, {f1: {$in: [7, 3, 5]}}], + [{$or: [{f1: {$eq: 5}}, {f1: {$eq: 3}}, {f1: {$eq: 7}}]}, {f1: {$in: [7, 3, 5]}}], + [{$or: [{f1: 42}, {f1: NaN}, {f1: 99}]}, {f1: {$in: [42, NaN, 99]}}], + [{$or: [{f1: /^x/}, {f1: "ab"}]}, {f1: {$in: [/^x/, "ab"]}}], + [{$or: [{f1: /^x/}, {f1: "^a"}]}, {f1: {$in: [/^x/, "^a"]}}], + [{$or: [{f1: 42}, {f1: null}, {f1: 99}]}, {f1: {$in: [42, 99, null]}}], + [{$or: [{f1: 1}, {f2: 9}, {f1: 99}]}, {$or: [{f2: 9}, {f1: {$in: [1, 99]}}]}], + [{$or: [{f1: {$regex: /^x/}}, {f1: {$regex: /ab/}}]}, {f1: {$in: [/^x/, /ab/]}}], + [ + {$and: [{$or: [{f1: 7}, {f1: 3}, {f1: 5}]}, {$or: [{f1: 1}, {f1: 2}, {f1: 3}]}]}, + {$and: [{f1: {$in: [7, 3, 5]}}, {f1: {$in: [1, 2, 3]}}]} + ], + [ + {$or: [{$or: [{f1: 7}, {f1: 3}, {f1: 5}]}, {$or: [{f1: 1}, {f1: 2}, {f1: 3}]}]}, + {$or: [{f1: {$in: [7, 3, 5]}}, {f1: {$in: [1, 2, 3]}}]} + ], + [ + {$or: [{$and: [{f1: 7}, {f2: 7}, {f1: 5}]}, {$or: [{f1: 1}, {f1: 2}, {f1: 3}]}]}, + {$or: [{$and: [{f1: 7}, {f2: 7}, {f1: 5}]}, {f1: {$in: [1, 2, 3]}}]}, + ], + [{$or: [{f2: [32, 52]}, {f2: [42, [13, 11]]}]}, {f2: {$in: [[32, 52], [42, [13, 11]]]}}], + [{$or: [{f2: 52}, {f2: 13}]}, {f2: {$in: [52, 13]}}], + [{$or: [{f2: [11]}, {f2: [23]}]}, {f2: {$in: [[11], [23]]}}], + [{$or: [{f1: 42}, {f1: null}]}, {f1: {$in: [42, null]}}], + [{$or: [{f1: "a"}, {f1: "b"}, {f1: /c/}]}, {f1: {$in: ["a", "b", /c/]}}], ]; // These $or queries should not be rewritten into $in because of different semantics. @@ -179,27 +133,22 @@ for (const query of negativeTestQueries) { assertOrNotRewrittenToIn(query); } -function testOrToIn(queries, usesCollation) { +function testOrToIn(queries) { for (const queryPair of queries) { - if (usesCollation && queryPair.cannotRewriteWithCollation) { - continue; - } - assertEquivPlanAndResult( - queryPair.actualQuery, queryPair.expectedQuery, !queryPair.cannotRewriteWithCollation); + assertEquivPlanAndResult(queryPair[0], queryPair[1]); } } -testOrToIn(positiveTestQueries, false /* usesCollation */); // test without indexes +testOrToIn(positiveTestQueries); // test without indexes assert.commandWorked(coll.createIndex({f1: 1})); -testOrToIn(positiveTestQueries, false /* usesCollation */); // single index +testOrToIn(positiveTestQueries); // single index assert.commandWorked(coll.createIndex({f2: 1})); assert.commandWorked(coll.createIndex({f1: 1, f2: 1})); -testOrToIn(positiveTestQueries, - false /* usesCollation */); // three indexes, requires multiplanning +testOrToIn(positiveTestQueries); // three indexes, requires multiplanning // Test with a collection that has a collation, and that collation is the same as the query // collation @@ -207,12 +156,12 @@ coll.drop(); assert.commandWorked(db.createCollection("orToIn", {collation: {locale: 'en_US'}})); coll = db.orToIn; assert.commandWorked(coll.insert(data)); -testOrToIn(positiveTestQueries, true /* usesCollation */); +testOrToIn(positiveTestQueries); // Test with a collection that has a collation, and that collation is different from the query // collation coll.drop(); assert.commandWorked(db.createCollection("orToIn", {collation: {locale: 'de'}})); coll = db.orToIn; assert.commandWorked(coll.insert(data)); -testOrToIn(positiveTestQueries, true /* usesCollation */); +testOrToIn(positiveTestQueries); }()); diff --git a/jstests/core/partialFilterExpression_with_geoWithin.js b/jstests/core/partialFilterExpression_with_geoWithin.js index f3ce022636b..794a6b4cae9 100644 --- a/jstests/core/partialFilterExpression_with_geoWithin.js +++ b/jstests/core/partialFilterExpression_with_geoWithin.js @@ -1,14 +1,15 @@ // @tags: [requires_non_retryable_writes, requires_fcv_51] load("jstests/libs/analyze_plan.js"); -load("jstests/libs/feature_flag_util.js"); - (function() { "use strict"; const coll = db.partialFilterExpression_with_geoWithin; coll.drop(); -if (FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { +const isFeatureEnabled = db.adminCommand({getParameter: 1, featureFlagTimeseriesMetricIndexes: 1}) + .featureFlagTimeseriesMetricIndexes.value; + +if (isFeatureEnabled) { // The first collection ensures our changes work with a variety of types (polygon, point, // linestring) and guarantees some shapes are not inside our index (bigPoly20). var bigPoly20 = { @@ -166,4 +167,4 @@ if (FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { // inside the limits of our polygon (or in other words, inside the UWS of Manhattan ). assert.eq(results.length, 1); } -})(); +})();
\ No newline at end of file diff --git a/jstests/core/partial_index_logical.js b/jstests/core/partial_index_logical.js deleted file mode 100644 index 3d0c332e57a..00000000000 --- a/jstests/core/partial_index_logical.js +++ /dev/null @@ -1,177 +0,0 @@ -/** - * Test the planners ability to distinguish parameterized queries in the presence of a partial index - * containing logical expressions ($and, $or). - * - * @tags: [ - * # Since the plan cache is per-node state, this test assumes that all operations are happening - * # against the same mongod. - * assumes_read_preference_unchanged, - * assumes_read_concern_unchanged, - * does_not_support_stepdowns, - * # If all chunks are moved off of a shard, it can cause the plan cache to miss commands. - * assumes_balancer_off, - * assumes_unsharded_collection, - * # Plan cache state is node-local and will not get migrated alongside tenant data. - * tenant_migration_incompatible, - * requires_fcv_60, - * no_selinux, - * ] - */ -(function() { -"use strict"; - -load("jstests/libs/analyze_plan.js"); // For getPlanCacheKeyFromShape. - -const coll = db[jsTestName()]; -coll.drop(); -(function partialIndexMixedFields() { - coll.drop(); - - // Create enough competing indexes such that a query is eligible for caching (single plan - // queries are not cached). - assert.commandWorked(coll.createIndex({num: 1}, {partialFilterExpression: {num: 5, foo: 6}})); - assert.commandWorked(coll.createIndex({num: -1})); - assert.commandWorked(coll.createIndex({num: -1, not_num: 1})); - - assert.commandWorked(coll.insert([ - {_id: 0, num: 5, foo: 6}, - {_id: 1, num: 5, foo: 7}, - ])); - - // Run a query which is eligible to use the {num: 1} index as it is covered by the partial - // filter expression. - assert.eq(coll.find({num: 5, foo: 6}).itcount(), 1); - assert.eq(coll.find({num: 5, foo: 6}).itcount(), 1); - const matchingKey = - getPlanCacheKeyFromShape({query: {num: 5, foo: 6}, collection: coll, db: db}); - assert.eq( - 1, - coll.aggregate([{$planCacheStats: {}}, {$match: {planCacheKey: matchingKey}}]).itcount()); - - // This query should not be eligible for the {num: 1} index despite the path 'num' being - // compatible (per the plan cache key encoding). - assert.eq(1, coll.find({num: 5, foo: 7}).itcount()); - const nonCoveredKey = - getPlanCacheKeyFromShape({query: {num: 5, foo: 7}, collection: coll, db: db}); - assert.eq( - 1, - coll.aggregate([{$planCacheStats: {}}, {$match: {planCacheKey: nonCoveredKey}}]).itcount()); - - // Sanity check that the generated keys are different due to the index compatibility. - assert.neq(nonCoveredKey, matchingKey); -})(); - -(function partialIndexDisjunction() { - coll.drop(); - - // Create enough competing indexes such that a query is eligible for caching (single plan - // queries are not cached). - assert.commandWorked(coll.createIndex( - {num: 1}, - {partialFilterExpression: {$or: [{num: {$exists: true}}, {num: {$type: 'number'}}]}})); - assert.commandWorked(coll.createIndex({num: -1})); - assert.commandWorked(coll.createIndex({num: -1, not_num: 1})); - - assert.commandWorked(coll.insert([ - {_id: 0}, - {_id: 1, num: null}, - {_id: 2, num: 5}, - ])); - - // Run a query which is eligible to use the {num: 1} index as it is covered by the partial - // filter expression. - assert.eq(coll.find({num: 5}).itcount(), 1); - assert.eq(coll.find({num: 5}).itcount(), 1); - const numericKey = getPlanCacheKeyFromShape({query: {num: 5}, collection: coll, db: db}); - assert.eq( - 1, coll.aggregate([{$planCacheStats: {}}, {$match: {planCacheKey: numericKey}}]).itcount()); - - // The plan for the query above should now be in the cache and active. Now execute a query with - // a very similar shape, however the predicate parameters are not satisfied by the partial - // filter expression. This is because {num: null} should match both explicit null as well as - // missing values (the latter are not indexed). - assert.eq(2, coll.find({num: null}).itcount()); - const nullKey = getPlanCacheKeyFromShape({query: {num: null}, collection: coll, db: db}); - assert.eq(1, - coll.aggregate([{$planCacheStats: {}}, {$match: {planCacheKey: nullKey}}]).itcount()); - - // Sanity check that the generated keys are different due to the index compatibility. - assert.neq(nullKey, numericKey); -})(); - -(function partialIndexDisjunctionWithCollation() { - coll.drop(); - - const caseInsensitive = {locale: "en_US", strength: 2}; - - // Create enough competing indexes such that a query is eligible for caching (single plan - // queries are not cached). - assert.commandWorked(coll.createIndex({a: 1}, { - partialFilterExpression: {$or: [{a: {$gt: 0}}, {a: {$gt: ""}}]}, - collation: caseInsensitive, - })); - assert.commandWorked(coll.createIndex({a: -1})); - assert.commandWorked(coll.createIndex({a: -1, b: 1})); - - assert.commandWorked(coll.insert([ - {_id: 0, a: "some"}, - {_id: 1, a: "string"}, - ])); - - // Populate the plan cache for a query which is eligible for the partial index. This is true - // without an explicit collation because the query text does not contain any string comparisons. - assert.eq(coll.aggregate({$match: {a: {$in: [1, 3]}}}).itcount(), 0); - assert.eq(coll.aggregate({$match: {a: {$in: [1, 3]}}}).itcount(), 0); - const simpleCollationKey = - getPlanCacheKeyFromShape({query: {a: {$in: [1, 3]}}, collection: coll, db: db}); - assert.eq(1, - coll.aggregate([{$planCacheStats: {}}, {$match: {planCacheKey: simpleCollationKey}}]) - .itcount()); - - // A collation-sensitive query should _not_ use the cached plan since the default simple - // collation does not match the collation on the index. - assert.eq(coll.aggregate({$match: {a: {$in: ["a", "Some"]}}}).itcount(), 0); - assert.eq(coll.aggregate({$match: {a: {$in: ["a", "Some"]}}}).itcount(), 0); - const collationSensitiveKey = - getPlanCacheKeyFromShape({query: {a: {$in: ["a", "Some"]}}, collection: coll, db: db}); - assert.eq( - 1, - coll.aggregate([{$planCacheStats: {}}, {$match: {planCacheKey: collationSensitiveKey}}]) - .itcount()); - - // Sanity check that the generated keys are different due to the collation and index - // compatibility. - assert.neq(collationSensitiveKey, simpleCollationKey); -})(); - -(function partialIndexConjunction() { - coll.drop(); - - // Create enough competing indexes such that a query is eligible for caching (single plan - // queries are not cached). - assert.commandWorked( - coll.createIndex({num: 1}, {partialFilterExpression: {num: {$gt: 0, $lt: 10}}})); - assert.commandWorked(coll.createIndex({num: -1})); - assert.commandWorked(coll.createIndex({num: -1, not_num: 1})); - - assert.commandWorked(coll.insert([ - {_id: 0}, - {_id: 1, num: 1}, - {_id: 2, num: 11}, - ])); - - // Run a query which is eligible to use the {num: 1} index as it is covered by the partial - // filter expression. - assert.eq(coll.find({num: {$gt: 0, $lt: 10}}).itcount(), 1); - assert.eq(coll.find({num: {$gt: 0, $lt: 10}}).itcount(), 1); - const validKey = - getPlanCacheKeyFromShape({query: {num: {$gt: 0, $lt: 10}}, collection: coll, db: db}); - assert.eq( - 1, coll.aggregate([{$planCacheStats: {}}, {$match: {planCacheKey: validKey}}]).itcount()); - - // The plan for the query above should now be in the cache and active. Now execute a query with - // a very similar shape, however the predicate parameters are not satisfied by the partial - // filter expression. - assert.eq(2, coll.find({num: {$gt: 0, $lt: 12}}).itcount()); -})(); -})(); diff --git a/jstests/core/plan_cache_list_plans.js b/jstests/core/plan_cache_list_plans.js index 802a25635e0..ca571a9e39e 100644 --- a/jstests/core/plan_cache_list_plans.js +++ b/jstests/core/plan_cache_list_plans.js @@ -12,7 +12,6 @@ // assumes_unsharded_collection, // does_not_support_stepdowns, // inspects_whether_plan_cache_entry_is_active, -// references_foreign_collection, // ] (function() { diff --git a/jstests/core/plan_cache_stats_shard_and_host.js b/jstests/core/plan_cache_stats_shard_and_host.js index 0e19786e8f6..e81dcd60692 100644 --- a/jstests/core/plan_cache_stats_shard_and_host.js +++ b/jstests/core/plan_cache_stats_shard_and_host.js @@ -5,14 +5,12 @@ // assumes_balancer_off, // assumes_read_concern_unchanged, // assumes_read_preference_unchanged, -// does_not_support_stepdowns, -// requires_fcv_60, +// does_not_support_stepdowns // ] (function() { "use strict"; load("jstests/libs/fixture_helpers.js"); // For 'FixtureHelpers'. -load('jstests/libs/analyze_plan.js'); // For getPlanCacheKeyFromExplain(). const coll = db.plan_cache_stats_shard_and_host; coll.drop(); @@ -24,20 +22,8 @@ assert.commandWorked(coll.createIndex({b: 1})); assert.commandWorked(coll.insert({a: 2, b: 3})); assert.eq(1, coll.find({a: 2, b: 3}).itcount()); -const explain = coll.find({a: 2, b: 3}).explain(); -const planCacheKey = getPlanCacheKeyFromExplain(explain, db); - -function filterPlanCacheEntriesByKey(planCacheKey, planCacheContents) { - let filteredPlanCacheEntries = []; - for (const entry of planCacheContents) { - if (entry.planCacheKey === planCacheKey) { - filteredPlanCacheEntries.push(entry); - } - } - return filteredPlanCacheEntries; -} - -let planCacheContents = filterPlanCacheEntriesByKey(planCacheKey, planCache.list()); +// List the contents of the plan cache for the collection. +let planCacheContents = planCache.list(); // We expect every shard that has a chunk for the collection to have produced a plan cache entry. assert.eq( @@ -59,16 +45,11 @@ for (const entry of planCacheContents) { // shard/host. As a future improvement, we should return plan cache information from every host in // every shard. But for now, we use regular host targeting to choose a particular host in each // shard. -planCacheContents = filterPlanCacheEntriesByKey( - planCacheKey, planCache.list([{$group: {_id: "$shard", count: {$sum: 1}}}])); - +planCacheContents = planCache.list([{$group: {_id: "$shard", count: {$sum: 1}}}]); for (const entry of planCacheContents) { assert.eq(entry.count, 1, entry); } - -planCacheContents = filterPlanCacheEntriesByKey( - planCacheKey, planCache.list([{$group: {_id: "$host", count: {$sum: 1}}}])); - +planCacheContents = planCache.list([{$group: {_id: "$host", count: {$sum: 1}}}]); for (const entry of planCacheContents) { assert.eq(entry.count, 1, entry); } @@ -76,5 +57,5 @@ for (const entry of planCacheContents) { // Clear the plan cache and verify that attempting to list the plan cache now returns an empty // array. coll.getPlanCache().clear(); -assert.eq([], filterPlanCacheEntriesByKey(planCacheKey, planCache.list())); +assert.eq([], planCache.list()); }()); diff --git a/jstests/core/profile_agg.js b/jstests/core/profile_agg.js index b1ce28dd696..d6611fa98ab 100644 --- a/jstests/core/profile_agg.js +++ b/jstests/core/profile_agg.js @@ -1,7 +1,6 @@ // @tags: [ // does_not_support_stepdowns, // requires_profiling, -// references_foreign_collection, // ] // Confirms that profiled aggregation execution contains all expected metrics with proper values. diff --git a/jstests/core/profile_update.js b/jstests/core/profile_update.js index 8a3e680da60..f9c9d91738c 100644 --- a/jstests/core/profile_update.js +++ b/jstests/core/profile_update.js @@ -98,9 +98,12 @@ assert.commandWorked(coll.update({_id: "new value", a: 4}, {$inc: {b: 1}}, {upse profileObj = getLatestProfilerEntry(testDB); const collectionIsClustered = ClusteredCollectionUtil.areAllCollectionsClustered(db.getMongo()); -const expectedPlan = collectionIsClustered ? "CLUSTERED_IXSCAN" : "IXSCAN { _id: 1 }"; -const expectedKeysExamined = 0; -const expectedDocsExamined = collectionIsClustered ? 1 : 0; +// A clustered collection has no actual index on _id. While a bounded collection scan is in +// principle an efficient option, the query planner only defaults to collection scan if no suitable +// index is available. +const expectedPlan = collectionIsClustered ? "IXSCAN { a: 1 }" : "IXSCAN { _id: 1 }"; +const expectedKeysExamined = collectionIsClustered ? 1 : 0; +const expectedDocsExamined = expectedKeysExamined; const expectedKeysInserted = collectionIsClustered ? 1 : 2; assert.eq(profileObj.command, @@ -144,17 +147,13 @@ for (var i = 0; i < indices.length; i++) { const profileObj = profiles[i]; const index = indices[i]; - let expectedPlan = "IXSCAN { _id: 1 }"; - let expectedKeysExamined = 0; - let expectedDocsExamined = 0; - let expectedKeysInserted = 2; - - if (collectionIsClustered) { - expectedPlan = "CLUSTERED_IXSCAN"; - expectedKeysExamined = 0; - expectedDocsExamined = (i + 1 == indices.length) ? 1 : 2; - expectedKeysInserted = 1; - } + // A clustered collection has no actual index on _id. While a bounded collection scan is in + // principle an efficient option, the query planner only defaults to collection scan if no + // suitable index is available. + const expectedPlan = collectionIsClustered ? "IXSCAN { a: 1 }" : "IXSCAN { _id: 1 }"; + const expectedKeysExamined = collectionIsClustered ? 1 : 0; + const expectedDocsExamined = expectedKeysExamined; + const expectedKeysInserted = collectionIsClustered ? 1 : 2; assert.eq( profileObj.command, diff --git a/jstests/core/project_with_collation.js b/jstests/core/project_with_collation.js deleted file mode 100644 index 33d7a5cc2a2..00000000000 --- a/jstests/core/project_with_collation.js +++ /dev/null @@ -1,185 +0,0 @@ -// Tests to verify the behavior of find command's project in the presence of collation. -// -// @tags: [ -// assumes_no_implicit_collection_creation_after_drop, -// requires_fcv_60, -// no_selinux, -// ] - -(function() { -'use strict'; - -const collation = { - locale: "en_US", - strength: 2 -}; -const withCollationCollName = jsTestName() + "_collation"; -const noCollationCollName = jsTestName() + "_noCollation"; - -function setupCollection(withCollation) { - const insertCollName = withCollation ? withCollationCollName : noCollationCollName; - db[insertCollName].drop(); - if (withCollation) { - assert.commandWorked(db.createCollection(insertCollName, {collation: withCollation})); - } - - const insertColl = db[insertCollName]; - assert.commandWorked(insertColl.insert( - {_id: 0, str: "a", array: [{str: "b"}, {str: "A"}, {str: "B"}, {str: "a"}]})); - assert.commandWorked(insertColl.insert({_id: 1, str: "a", elemMatch: [{str: "A"}, "ignored"]})); - assert.commandWorked(insertColl.insert({_id: 2, str: "A", elemMatch: ["ignored", {str: "a"}]})); - assert.commandWorked(insertColl.insert({_id: 3, str: "B"})); - - return insertColl; -} - -function runQueryWithCollation(testColl, collationToUse) { - let findCmd = - testColl.find({str: 'A', elemMatch: {$elemMatch: {str: "a"}}}, {_id: 1, 'elemMatch.$': 1}); - if (collationToUse) { - findCmd = findCmd.collation(collationToUse); - } - const elemMatchOutput = findCmd.toArray(); - assert.sameMembers(elemMatchOutput, - [{_id: 1, elemMatch: [{str: "A"}]}, {_id: 2, elemMatch: [{str: "a"}]}]); - - findCmd = - testColl.find({str: 'A'}, {sortedArray: {$sortArray: {input: "$array", sortBy: {str: 1}}}}); - if (collationToUse) { - findCmd = findCmd.collation(collationToUse); - } - const sortArrayOutput = findCmd.toArray(); - assert.sameMembers(sortArrayOutput, [ - {_id: 0, sortedArray: [{str: "A"}, {str: "a"}, {str: "b"}, {str: "B"}]}, - {_id: 1, sortedArray: null}, - {_id: 2, sortedArray: null} - ]); - - const findAndUpdateOutput = testColl.findAndModify({ - query: {_id: 1, str: 'A', elemMatch: {$elemMatch: {str: "a"}}}, - fields: {_id: 1, 'elemMatch.$': 1, updated: 1}, - update: {$set: {updated: true}}, - collation: collationToUse - }); - assert.docEq(findAndUpdateOutput, {_id: 1, elemMatch: [{str: "A"}]}); - - const findAndUpdateWithSortArrayOutput = testColl.findAndModify({ - query: {_id: 0, str: 'A'}, - fields: {_id: 1, str: 1, sortedArray: {$sortArray: {input: "$array", sortBy: {str: 1}}}}, - update: {$set: {updated: true}}, - collation: collationToUse, - new: true - }); - assert.docEq(findAndUpdateWithSortArrayOutput, - {_id: 0, str: "a", sortedArray: [{str: "A"}, {str: "a"}, {str: "b"}, {str: "B"}]}); - - const findAndRemoveOutput = testColl.findAndModify({ - query: {_id: 1, str: 'A', elemMatch: {$elemMatch: {str: "a"}}}, - fields: {_id: 1, 'elemMatch.$': 1, updated: 1}, - remove: true, - collation: collationToUse, - }); - assert.docEq(findAndRemoveOutput, {_id: 1, elemMatch: [{str: "A"}], updated: true}); - - const findAndRemoveWithSortArrayOutput = testColl.findAndModify({ - query: {_id: 0, str: 'A'}, - fields: {_id: 1, str: 1, sortedArray: {$sortArray: {input: "$array", sortBy: {str: 1}}}}, - remove: true, - collation: collationToUse - }); - assert.docEq(findAndRemoveWithSortArrayOutput, - {_id: 0, str: "a", sortedArray: [{str: "A"}, {str: "a"}, {str: "b"}, {str: "B"}]}); -} - -// The output for the below two tests should not depend on the collection level collation. -let collWithCollation = setupCollection({locale: "en_US"}); -runQueryWithCollation(collWithCollation, collation); - -let noCollationColl = setupCollection(false); -runQueryWithCollation(noCollationColl, collation); - -// Tests to verify that the projection code inherits collection level collation in the absence of -// query level collation. -collWithCollation = setupCollection(collation); -runQueryWithCollation(collWithCollation, null); - -// The output of this should not depend on the collection level collation and simple collation -// should be applied always. -function queryWithSimpleCollation(testColl) { - const elemMatchOutput = - testColl.find({str: 'A', elemMatch: {$elemMatch: {str: "a"}}}, {_id: 1, 'elemMatch.$': 1}) - .collation({locale: "simple"}) - .toArray(); - assert.sameMembers(elemMatchOutput, [{_id: 2, elemMatch: [{str: "a"}]}]); - - const sortArrayOutput = - testColl.find({str: 'a'}, {sortedArray: {$sortArray: {input: "$array", sortBy: {str: 1}}}}) - .collation({locale: "simple"}) - .toArray(); - assert.sameMembers(sortArrayOutput, [ - {_id: 0, sortedArray: [{str: "A"}, {str: "B"}, {str: "a"}, {str: "b"}]}, - {_id: 1, sortedArray: null} - ]); - - // Test findAndModify command with 'update'. Ensure that simple collation is always honored. - const findAndUpdateOutput = testColl.findAndModify({ - query: {str: 'A', elemMatch: {$elemMatch: {str: "a"}}}, - fields: {_id: 1, 'elemMatch.$': 1, updated: 1}, - update: {$set: {updated: true}}, - collation: {locale: "simple"} - }); - assert.docEq(findAndUpdateOutput, {_id: 2, elemMatch: [{str: "a"}]}); - - const findAndUpdateWithSortArrayOutput = testColl.findAndModify({ - query: {_id: 0, str: 'a'}, - fields: {_id: 1, str: 1, sortedArray: {$sortArray: {input: "$array", sortBy: {str: 1}}}}, - update: {$set: {updated: true}}, - collation: {locale: "simple"}, - new: true - }); - assert.docEq(findAndUpdateWithSortArrayOutput, - {_id: 0, str: "a", sortedArray: [{str: "A"}, {str: "B"}, {str: "a"}, {str: "b"}]}); - - // Test findAndModify command with remove:true. Ensure that simple collation is always honored. - const findAndRemoveOutput = testColl.findAndModify({ - query: {_id: 2, str: 'A', elemMatch: {$elemMatch: {str: "a"}}}, - fields: {_id: 1, 'elemMatch.$': 1, updated: 1}, - remove: true, - collation: {locale: "simple"}, - }); - assert.docEq(findAndRemoveOutput, {_id: 2, elemMatch: [{str: "a"}], updated: true}); - - const findAndRemoveWithSortArrayOutput = testColl.findAndModify({ - query: {_id: 0, str: 'a'}, - fields: {_id: 1, str: 1, sortedArray: {$sortArray: {input: "$array", sortBy: {str: 1}}}}, - remove: true, - collation: {locale: "simple"} - }); - assert.docEq(findAndRemoveWithSortArrayOutput, - {_id: 0, str: "a", sortedArray: [{str: "A"}, {str: "B"}, {str: "a"}, {str: "b"}]}); -} - -noCollationColl = setupCollection(false); -queryWithSimpleCollation(noCollationColl); - -collWithCollation = setupCollection(collation); -queryWithSimpleCollation(collWithCollation); - -// Test with views. -(function viewWithCollation() { - collWithCollation = setupCollection(collation); - db[jsTestName() + "_view"].drop(); - assert.commandWorked( - db.createView(jsTestName() + "_view", withCollationCollName, [], {collation: collation})); - const viewColl = db[jsTestName() + "_view"]; - - const sortArrayOutput = - viewColl.find({str: 'A'}, {sortedArray: {$sortArray: {input: "$array", sortBy: {str: 1}}}}) - .toArray(); - assert.sameMembers(sortArrayOutput, [ - {_id: 0, sortedArray: [{str: "A"}, {str: "a"}, {str: "b"}, {str: "B"}]}, - {_id: 1, sortedArray: null}, - {_id: 2, sortedArray: null} - ]); -})(); -})(); diff --git a/jstests/core/query/internal_strip_invalid_assignment.js b/jstests/core/query/internal_strip_invalid_assignment.js deleted file mode 100644 index b8868c6e951..00000000000 --- a/jstests/core/query/internal_strip_invalid_assignment.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Test that '$_internalSchemaAllElemMatchFromIndex' does not use an index, and does not trigger an - * assertion failure when we strip its index assignment from special indexes. - * - * Originally intended to reproduce SERVER-82717. - * - * @tags: [ - * # Explain command does not support read concerns other than local. - * assumes_read_concern_local, - * ] - */ -(function() { -load('jstests/libs/analyze_plan.js'); // for planHasStage - -const coll = db.getCollection(jsTestName()); - -assert.commandWorked(coll.createIndex({a: 1, foo: '2dsphere'})); -assert.commandWorked(coll.createIndex({a: 1, '$**': 'text'})); -assert.commandWorked(coll.createIndex({a: 1}, {partialFilterExpression: {foo: 123}})); - -const query = { - "$or": [ - // Has to be the same field. - {"a": 1}, - { - "a": { - // Checks that ALL elements of an array match this predicate. - // An index on {a: 1, ...} doesn't help, because: - // - If it's multikey, each index entry only tells us one array-element, while - // the predicate needs to check all elements. - // - If it's non-multikey, the predicate is trivially false, because it expects - // an array. - "$_internalSchemaAllElemMatchFromIndex": [ - // Number of elements to skip. - NumberLong(0), - // Predicate to run on each element. - {"i": {"$regex": "b"}}, - ] - } - } - ] -}; -const explain = coll.find(query).explain(); -assert(planHasStage(db, explain, "COLLSCAN"), explain); -})(); diff --git a/jstests/core/rename_collection_system_db.js b/jstests/core/rename_collection_system_db.js index e9e166914c0..8d46ad27a04 100644 --- a/jstests/core/rename_collection_system_db.js +++ b/jstests/core/rename_collection_system_db.js @@ -16,9 +16,10 @@ systemUsers.drop(); coll.drop(); coll.insert({}); -// system.foo and system.users aren't in the allowlist so they can't be renamed to or from +// system.foo isn't in the allowlist so it can't be renamed to or from assert.commandFailed(coll.renameCollection(systemFoo.getName())); assert.commandFailed(systemFoo.renameCollection(coll.getName())); -assert.commandFailed(coll.renameCollection(systemUsers.getName())); -assert.commandFailed(systemUsers.renameCollection(coll.getName())); +// system.users is allowlisted so these should work +assert.commandWorked(coll.renameCollection(systemUsers.getName())); +assert.commandWorked(systemUsers.renameCollection(coll.getName())); diff --git a/jstests/core/rename_collection_view.js b/jstests/core/rename_collection_view.js deleted file mode 100644 index eda30c4e2fb..00000000000 --- a/jstests/core/rename_collection_view.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Basic test around rename collection involving views - */ - -(function() { -"use strict"; - -const collNamePrefix = "rename_collection_view_collection"; -const viewNamePrefix = "rename_collection_view_view"; -let collCounter = 0; -let viewCounter = 0; - -function getNewCollName() { - return collNamePrefix + collCounter++; -} - -function getNewColl() { - let coll = db[getNewCollName()]; - coll.drop(); - assert.commandWorked(db[coll.getName()].insert({_id: 1})); - return coll; -} - -/** - * Drops a view and then recreates against an underlying collection. Handle the case for those - * suites where the test runs multiple times against the same cluster/replicaset instance and view - * already exists - */ -function getNewView(collName) { - // new view with empty filter - const viewName = viewNamePrefix + viewCounter++; - db.runCommand({drop: viewName}); - assert.commandWorked(db.createView(viewName, collName, [])); - return db[viewName]; -} - -jsTest.log( - "Rename existing view should fail with CommandNotSupportedOnView indipendently on the target type (view or collection)"); -{ - const srcColl = getNewColl(); - const srcView = getNewView(srcColl.getName()); - const dstColl = getNewColl(); - const dstView = getNewView(dstColl.getName()); - - assert.commandFailedWithCode( - db.adminCommand({renameCollection: srcView.getFullName(), to: dstColl.getFullName()}), - ErrorCodes.CommandNotSupportedOnView, - "rename view to existing collection should fail with CommandNotSupportedOnView"); - - assert.commandFailedWithCode( - db.adminCommand( - {renameCollection: srcView.getFullName(), to: dstColl.getFullName(), dropTarget: true}), - ErrorCodes.CommandNotSupportedOnView, - "rename view to existing collection should fail with CommandNotSupportedOnView even if dropTarget is true"); - - assert.commandFailedWithCode( - db.adminCommand({renameCollection: srcView.getFullName(), to: dstView.getFullName()}), - ErrorCodes.CommandNotSupportedOnView, - "rename view to existing view should fail with CommandNotSupportedOnView"); - - assert.commandFailedWithCode( - db.adminCommand( - {renameCollection: srcView.getFullName(), to: dstView.getFullName(), dropTarget: true}), - ErrorCodes.CommandNotSupportedOnView, - "rename view to existing view should fail with CommandNotSupportedOnView even if dropTarget is true"); -} - -jsTest.log("Rename coll to existing view should fail with NamespaceExists"); -{ - const srcColl = getNewColl(); - const dstColl = getNewColl(); - - const dstView = getNewView(dstColl.getName()); - - assert.commandFailedWithCode( - db.adminCommand({renameCollection: srcColl.getFullName(), to: dstView.getFullName()}), - ErrorCodes.NamespaceExists, - "rename collection to existing view should fail with NamespaceExists"); - - assert.commandFailedWithCode( - db.adminCommand( - {renameCollection: srcColl.getFullName(), to: dstView.getFullName(), dropTarget: true}), - ErrorCodes.NamespaceExists, - "rename collection to existing view should fail with NamespaceExists even if dropTarget is true"); -} -})(); diff --git a/jstests/core/shell_connection_strings.js b/jstests/core/shell_connection_strings.js index bd0646e3647..0cf2f3867d5 100644 --- a/jstests/core/shell_connection_strings.js +++ b/jstests/core/shell_connection_strings.js @@ -1,7 +1,6 @@ // Test mongo shell connect strings. // @tags: [ // uses_multiple_connections, -// docker_incompatible, // ] (function() { 'use strict'; diff --git a/jstests/core/timeseries/bucket_unpacking_with_sort.js b/jstests/core/timeseries/bucket_unpacking_with_sort.js index 695e94daec9..029697be74a 100644 --- a/jstests/core/timeseries/bucket_unpacking_with_sort.js +++ b/jstests/core/timeseries/bucket_unpacking_with_sort.js @@ -17,9 +17,6 @@ * requires_timeseries, * # Explain of a resolved view must be executed by mongos. * directly_against_shardsvrs_incompatible, - * # Time-series hint by index key did not exist in 5.0. - * # Also, the 5.0 version of this feature does not support reverse collscan. - * requires_fcv_60, * ] */ (function() { diff --git a/jstests/core/timeseries/bucket_unpacking_with_sort_extended_range.js b/jstests/core/timeseries/bucket_unpacking_with_sort_extended_range.js deleted file mode 100644 index e74d1c3e914..00000000000 --- a/jstests/core/timeseries/bucket_unpacking_with_sort_extended_range.js +++ /dev/null @@ -1,269 +0,0 @@ -/** - * Test that sort queries work properly on dates ouside the 32 bit epoch range, - * [1970-01-01 00:00:00 UTC - 2038-01-29 03:13:07 UTC], when a collection scan is used. - * - * @tags: [ - * # Explain of a resolved view must be executed by mongos. - * directly_against_shardsvrs_incompatible, - * # This complicates aggregation extraction. - * do_not_wrap_aggregations_in_facets, - * # Refusing to run a test that issues an aggregation command with explain because it may - * # return incomplete results if interrupted by a stepdown. - * does_not_support_stepdowns, - * # We need a timeseries collection. - * requires_timeseries, - * # Cannot insert into a time-series collection in a multi-document transaction. - * does_not_support_transactions, - * # Time-series hint by index key did not exist in 5.0 - * requires_fcv_60, - * ] - */ - -(function() { -"use strict"; - -load("jstests/aggregation/extras/utils.js"); // For getExplainedPipelineFromAggregation. -load("jstests/core/timeseries/libs/timeseries.js"); -load('jstests/libs/analyze_plan.js'); - -if (!TimeseriesTest.bucketUnpackWithSortEnabled(db.getMongo())) { - jsTestLog("Skipping test because 'BucketUnpackWithSort' is disabled."); - return; -} - -const timeFieldName = "t"; - -// Create unindexed collection -const coll = db.timeseries_internal_bounded_sort_extended_range; -const buckets = db['system.buckets.' + coll.getName()]; -coll.drop(); -assert.commandWorked(db.createCollection(coll.getName(), {timeseries: {timeField: 't'}})); - -// Create collection indexed on time -const collIndexed = db.timeseries_internal_bounded_sort_extended_range_with_index; -const bucketsIndexed = db['system.buckets.' + collIndexed.getName()]; -collIndexed.drop(); -assert.commandWorked(db.createCollection(collIndexed.getName(), {timeseries: {timeField: 't'}})); -assert.commandWorked(collIndexed.createIndex({'t': 1})); - -jsTestLog(collIndexed.getIndexes()); -jsTestLog(bucketsIndexed.getIndexes()); - -function numShards() { - return db.getSiblingDB('config').shards.count(); -} -for (const collection of [buckets, bucketsIndexed]) { - if (FixtureHelpers.isSharded(collection) && numShards() >= 2) { - // Split and move data to create an interesting scenario: we have some data on each shard, - // but all the extended-range data is on a non-primary shard. This means view resolution is - // unaware of the extended-range data, because that happens on the primary shard. - - const shards = db.getSiblingDB('config').shards.find().toArray(); - const [shardName0, shardName1] = shards.map(doc => doc._id); - - assert.commandWorked(db.adminCommand({movePrimary: db.getName(), to: shardName0})); - const collName = collection.getFullName(); - // Our example data has documents between 2000-2003, and these dates are non-wrapping. - // So this goes on the primary shard, and everything else goes on the non-primary. - assert.commandWorked(sh.splitAt(collName, {'control.min.t': ISODate('2000-01-01')})); - assert.commandWorked(sh.splitAt(collName, {'control.min.t': ISODate('2003-01-01')})); - assert.commandWorked( - sh.moveChunk(collName, {'control.min.t': ISODate('1969-01-01')}, shardName1)); - assert.commandWorked( - sh.moveChunk(collName, {'control.min.t': ISODate('2000-01-01')}, shardName0)); - assert.commandWorked( - sh.moveChunk(collName, {'control.min.t': ISODate('2003-01-01')}, shardName1)); - } -} - -const intervalMillis = 60000; -function insertBucket(start) { - jsTestLog("Inserting bucket starting with " + Date(start).toString()); - - const batchSize = 1000; - - const batch = - Array.from({length: batchSize}, (_, j) => ({t: new Date(start + j * intervalMillis)})); - - assert.commandWorked(coll.insert(batch)); - assert.commandWorked(collIndexed.insert(batch)); -} - -// Insert some data. We'll insert 5 buckets in each range, with values < 0, -// values between 0 and FFFFFFFF(unsigned), and values > FFFFFFFF. It turns out, however, -// that Javascript's Date doesn't handle dates beyond 2039 either, so we rely on lower dates -// to test for unexpected behavior. -function insertDocuments() { - // We want to choose the underflow and overflow lower bits in such a way that we - // encourage wrong results when the upper bytes are removed. - const underflowMin = new Date("1969-01-01").getTime(); // Year before the 32 bit epoch - const normalMin = new Date("2002-01-01").getTime(); // Middle of the 32 bit epoch - - insertBucket(underflowMin); - - var numBatches = 5; - - const batchOffset = Math.floor(intervalMillis / (numBatches + 1)); - for (let i = 0; i < numBatches; ++i) { - const start = normalMin + i * batchOffset; - insertBucket(start); - } - assert.gt(buckets.aggregate([{$count: 'n'}]).next().n, 1, 'Expected more than one bucket'); -} - -insertDocuments(); - -const unpackStage = getAggPlanStages(coll.explain().aggregate(), '$_internalUnpackBucket')[0]; - -function assertSorted(result, ascending) { - let prev = ascending ? {t: -Infinity} : {t: Infinity}; - for (const doc of result) { - if (ascending) { - assert.lt(+prev.t, - +doc.t, - 'Found two docs not in ascending time order: ' + tojson({prev, doc})); - } else { - assert.gt(+prev.t, - +doc.t, - 'Found two docs not in descending time order: ' + tojson({prev, doc})); - } - - prev = doc; - } -} - -function checkAgainstReferenceBoundedSortUnexpected( - collection, reference, pipeline, hint, sortOrder) { - const options = hint ? {hint: hint} : {}; - - const bucket = db['system.buckets.' + coll.getName()]; - - const plan = collection.explain().aggregate(pipeline, options); - if (FixtureHelpers.isSharded(buckets) && numShards() >= 2) { - // With a sharded collection, some shards might not have any extended-range data, - // so they might still use $_internalBoundedSort. But we know at least one - // shard has extended-range data, so we know at least one shard has to - // use a blocking sort. - const bounded = getAggPlanStages(plan, "$_internalBoundedSort"); - const blocking = getAggPlanStages(plan, "$sort"); - assert.gt(blocking.length, 0, {bounded, blocking, plan}); - assert.lt(bounded.length, - FixtureHelpers.numberOfShardsForCollection(buckets), - {bounded, blocking, plan}); - } else { - const stages = getAggPlanStages(plan, "$_internalBoundedSort"); - assert.eq([], stages, plan); - } - - const opt = collection.aggregate(pipeline, options).toArray(); - assertSorted(opt, sortOrder); - - assert.eq(reference.length, opt.length); - for (var i = 0; i < opt.length; ++i) { - assert.docEq(reference[i], opt[i]); - } -} - -function checkAgainstReferenceBoundedSortExpected( - collection, reference, pipeline, hint, sortOrder) { - const options = hint ? {hint: hint} : {}; - - const plan = collection.explain().aggregate(pipeline, options); - const stages = getAggPlanStages(plan, "$_internalBoundedSort"); - assert.neq([], stages, plan); - - const opt = collection.aggregate(pipeline, options).toArray(); - assertSorted(opt, sortOrder); - - assert.eq(reference.length, opt.length); - for (var i = 0; i < opt.length; ++i) { - assert.docEq(reference[i], opt[i]); - } -} - -function runTest(ascending) { - const reference = buckets - .aggregate([ - unpackStage, - {$_internalInhibitOptimization: {}}, - {$sort: {t: ascending ? 1 : -1}}, - ]) - .toArray(); - assertSorted(reference, ascending); - - // Check plan using collection scan - checkAgainstReferenceBoundedSortUnexpected(coll, - reference, - [ - {$sort: {t: ascending ? 1 : -1}}, - ], - {}, - ascending); - - // Check plan using hinted collection scan - checkAgainstReferenceBoundedSortUnexpected(coll, - reference, - [ - {$sort: {t: ascending ? 1 : -1}}, - ], - {"$natural": ascending ? 1 : -1}, - ascending); - - const referenceIndexed = bucketsIndexed - .aggregate([ - unpackStage, - {$_internalInhibitOptimization: {}}, - {$sort: {t: ascending ? 1 : -1}}, - ]) - .toArray(); - assertSorted(referenceIndexed, ascending); - - // Check plan using index scan. If we've inserted a date before 1-1-1970, we round the min - // up towards 1970, rather then down, which has the effect of increasing the control.min.t. - // This means the minimum time in the bucket is likely to be lower than indicated and thus, - // actual dates may be out of order relative to what's indicated by the bucket bounds. - checkAgainstReferenceBoundedSortUnexpected(collIndexed, - referenceIndexed, - [ - {$sort: {t: ascending ? 1 : -1}}, - ], - {}, - ascending); - - // Check plan using hinted index scan - checkAgainstReferenceBoundedSortUnexpected(collIndexed, - referenceIndexed, - [ - {$sort: {t: ascending ? 1 : -1}}, - ], - {"t": 1}, - ascending); - - // Check plan using hinted collection scan - checkAgainstReferenceBoundedSortUnexpected(collIndexed, - referenceIndexed, - [ - {$sort: {t: ascending ? 1 : -1}}, - ], - {"$natural": ascending ? 1 : -1}, - ascending); - - // The workaround in all cases is to create a reverse index on the time field, though - // it's necessary to force use of the reversed index. - const reverseIdxName = "reverseIdx"; - collIndexed.createIndex({t: -1}, {name: reverseIdxName}); - - checkAgainstReferenceBoundedSortExpected(collIndexed, - referenceIndexed, - [ - {$sort: {t: ascending ? 1 : -1}}, - ], - {"t": -1}, - ascending); - - collIndexed.dropIndex(reverseIdxName); -} - -runTest(false); // descending -runTest(true); // ascending -})(); diff --git a/jstests/core/timeseries/bucket_unpacking_with_sort_plan_cache.js b/jstests/core/timeseries/bucket_unpacking_with_sort_plan_cache.js index e69a8d7a860..25e1c99312e 100644 --- a/jstests/core/timeseries/bucket_unpacking_with_sort_plan_cache.js +++ b/jstests/core/timeseries/bucket_unpacking_with_sort_plan_cache.js @@ -21,8 +21,6 @@ * directly_against_shardsvrs_incompatible, * # We use the profiler to get info in order to force replanning. * requires_profiling, - * # The 5.0 version of this feature does not support reverse collscan. - * requires_fcv_60, * ] */ (function() { diff --git a/jstests/core/timeseries/libs/timeseries.js b/jstests/core/timeseries/libs/timeseries.js index 540e0bcea55..ea8d71c87a1 100644 --- a/jstests/core/timeseries/libs/timeseries.js +++ b/jstests/core/timeseries/libs/timeseries.js @@ -11,20 +11,6 @@ var TimeseriesTest = class { return FeatureFlagUtil.isEnabled(conn, "TimeseriesBucketCompression"); } - static bucketsMayHaveMixedSchemaData(coll) { - const catalog = coll.aggregate([{$listCatalog: {}}]).toArray()[0]; - const tsMixedSchemaOptionNewFormat = catalog.md.options.storageEngine && - catalog.md.options.storageEngine.wiredTiger && - catalog.md.options.storageEngine.wiredTiger.configString; - // TODO SERVER-92533 Simplify once SERVER-91195 is backported to all supported branches - if (tsMixedSchemaOptionNewFormat !== undefined) { - return tsMixedSchemaOptionNewFormat == - "app_metadata=(timeseriesBucketsMayHaveMixedSchemaData=true)"; - } else { - return catalog.md.timeseriesBucketsMayHaveMixedSchemaData; - } - } - /** * Returns whether time-series updates and deletes are supported. */ @@ -66,16 +52,9 @@ var TimeseriesTest = class { } static bucketUnpackWithSortEnabled(conn) { - const resp50 = conn.adminCommand({getParameter: 1, featureFlagBucketUnpackWithSort50: 1}); - const resp = conn.adminCommand({getParameter: 1, featureFlagBucketUnpackWithSort: 1}); - assert(Boolean(resp50.ok) ^ Boolean(resp.ok), - `Expected exactly one of these feature flags to exist: ${tojson(resp50)} vs ${ - tojson(resp)}`); - if (resp50.ok) { - return assert.commandWorked(resp50).featureFlagBucketUnpackWithSort50.value; - } else { - return assert.commandWorked(resp).featureFlagBucketUnpackWithSort.value; - } + return assert + .commandWorked(conn.adminCommand({getParameter: 1, featureFlagBucketUnpackWithSort: 1})) + .featureFlagBucketUnpackWithSort.value; } /** @@ -220,7 +199,7 @@ var TimeseriesTest = class { static ensureDataIsDistributedIfSharded(coll, splitPointDate) { const db = coll.getDB(); - const buckets = db[this.getBucketsCollName(coll.getName())]; + const buckets = db["system.buckets." + coll.getName()]; if (FixtureHelpers.isSharded(buckets)) { const timeFieldName = db.getCollectionInfos({name: coll.getName()})[0].options.timeseries.timeField; @@ -269,8 +248,4 @@ var TimeseriesTest = class { } } } - - static getBucketsCollName(collName) { - return `system.buckets.${collName}`; - } }; diff --git a/jstests/core/timeseries/nondefault_collation.js b/jstests/core/timeseries/nondefault_collation.js index 01f9b8c635c..3ff1c63d330 100644 --- a/jstests/core/timeseries/nondefault_collation.js +++ b/jstests/core/timeseries/nondefault_collation.js @@ -1,13 +1,8 @@ /** - * Correctness tests for TS collections with collation that might not match the explicit collation, - * specified in the query. - * - * Queries on timeseries attempt various optimizations to avoid unpacking of buckets. These rely on - * the meta field and the control data (currently, min and max), computed for each bucket. - * Collection's collation might affect the computed control values. + * Test ensures that users can specify non-default collation when querying on time-series + * collections. * * @tags: [ - * assumes_unsharded_collection, * requires_non_retryable_writes, * requires_pipeline_optimization, * requires_getmore, @@ -26,221 +21,123 @@ load("jstests/libs/analyze_plan.js"); const coll = db.timeseries_nondefault_collation; const bucketsColl = db.getCollection('system.buckets.' + coll.getName()); +coll.drop(); // implicitly drops bucketsColl. + +const timeFieldName = 'time'; +const metaFieldName = 'meta'; + const numericOrdering = { - locale: "en_US", - numericOrdering: true, - strength: 1 // case and diacritics ignored + collation: {locale: "en_US", numericOrdering: true} }; + const caseSensitive = { - locale: "en_US", - strength: 1, - caseLevel: true + collation: {locale: "en_US", strength: 1, caseLevel: true, numericOrdering: true} }; + const diacriticSensitive = { - locale: "en_US", - strength: 2, - caseLevel: false + collation: {locale: "en_US", strength: 2} }; -const insensitive = { - locale: "en_US", + +const englishCollation = { + locale: 'en', strength: 1 }; -// Find on meta field isn't different from a find on any other view, but let's check it anyway. -(function testFind_MetaField() { - coll.drop(); - - assert.commandWorked(db.createCollection( - coll.getName(), - {timeseries: {timeField: 'time', metaField: 'meta'}, collation: numericOrdering})); - assert.contains(bucketsColl.getName(), db.getCollectionNames()); - - assert.commandWorked(coll.insert({time: ISODate(), meta: "1", value: 42})); - assert.commandWorked(coll.insert({time: ISODate(), meta: "10", value: 42})); - assert.commandWorked(coll.insert({time: ISODate(), meta: "5", value: 42})); - - // Use the collection's collation with numeric ordering. - let res1 = coll.find({meta: {$gt: "4"}}); - assert.eq(2, res1.itcount(), res1.toArray()); // should match "5" and "10" - - // Use explicit collation with lexicographic ordering. - let res2 = coll.find({meta: {$gt: "4"}}).collation(insensitive); - assert.eq(1, res2.itcount(), res2.toArray()); // should match only "5" -}()); - -// For the measurement fields each bucket computes additional "control values", such as min/max and -// might use them to avoid unpacking. -(function testFind_MeasurementField() { - coll.drop(); - - assert.commandWorked(db.createCollection( - coll.getName(), - {timeseries: {timeField: 'time', metaField: 'meta'}, collation: numericOrdering})); - assert.contains(bucketsColl.getName(), db.getCollectionNames()); - - // The 'numericOrdering' on the collection means that the max of the bucket with the three docs - // below is "10" (while the lexicographic max is "5"). - assert.commandWorked(coll.insert({time: ISODate(), meta: 42, value: "1"})); - assert.commandWorked(coll.insert({time: ISODate(), meta: 42, value: "10"})); - assert.commandWorked(coll.insert({time: ISODate(), meta: 42, value: "5"})); - - // A query with default collation would use the bucket's min/max and find the matches. We are - // not checking the unpacking optimizations here as it's not a concern of collation per se. - let res1 = coll.find({value: {$gt: "4"}}); - assert.eq(2, res1.itcount(), res1.toArray()); // should match "5" and "10" - - // If a query with 'insensitive' collation, which doesn't do numeric ordering, used the bucket's - // min/max it would miss the bucket. Check, that it doesn't. - let res2 = coll.find({value: {$gt: "4"}}).collation(insensitive); - assert.eq(1, res2.itcount(), res2.toArray()); // should match only "5" -}()); - -(function testFind_OnlyQueryHasCollation() { - coll.drop(); - - assert.commandWorked( - db.createCollection(coll.getName(), {timeseries: {timeField: 'time', metaField: 'meta'}})); - - // This should generate a bucket with control.min.value = 'C' and control.max.value = 'c'. - assert.commandWorked(coll.insert({time: ISODate(), meta: 42, value: "C"})); - assert.commandWorked(coll.insert({time: ISODate(), meta: 42, value: "b"})); - assert.commandWorked(coll.insert({time: ISODate(), meta: 42, value: "c"})); - - // A query with default collation would use the bucket's min/max and find the two matches. - const resWithNoCollation = coll.find({value: {$lt: "c"}}); - assert.eq(2, - resWithNoCollation.itcount(), - resWithNoCollation.toArray()); // should match "C" and "b". - - // If a query with 'insensitive' collation used the bucket's min/max it would miss the bucket. - // Check, that it doesn't. - const resWithCollation_find = coll.find({value: {$lt: "c"}}).collation(insensitive); - assert.eq(1, - resWithCollation_find.itcount(), - resWithCollation_find.toArray()); // should match only "b". - - // Run the same test with aggregate command. - const resWithCollation_agg = - coll.aggregate([{$match: {value: {$lt: "c"}}}], {collation: insensitive}).toArray(); - assert.eq(1, resWithCollation_agg.length, resWithCollation_agg); -}()); - -(function testAgg_GroupByMetaField() { - coll.drop(); - - assert.commandWorked(db.createCollection( - coll.getName(), - {timeseries: {timeField: 'time', metaField: 'meta'}, collation: numericOrdering})); - assert.contains(bucketsColl.getName(), db.getCollectionNames()); - - assert.commandWorked(coll.insert({time: ISODate(), meta: "1", val: 1})); - assert.commandWorked(coll.insert({time: ISODate(), meta: "5", val: 1})); - - // Using collection's collation with numeric ordering. - let res1 = - coll.aggregate([{$bucket: {groupBy: "$meta", boundaries: ["1", "10", "50"]}}]).toArray(); - assert.eq(1, res1.length); - assert.eq({_id: "1", count: 2}, res1[0]); - - // Using explicit collation with lexicographic ordering. - let res2 = coll.aggregate([{$bucket: {groupBy: "$meta", boundaries: ["1", "10", "50"]}}], - {collation: insensitive}) - .toArray(); - assert.eq(2, res2.length); - assert.eq({_id: "1", count: 1}, res2[0]); // "1" goes here - assert.eq({_id: "10", count: 1}, res2[1]); // "5" goes here -}()); - -(function testAgg_GroupByMeasurementField() { - coll.drop(); - - assert.commandWorked(db.createCollection( - coll.getName(), - {timeseries: {timeField: 'time', metaField: 'meta'}, collation: insensitive})); - assert.contains(bucketsColl.getName(), db.getCollectionNames()); - - // Cause two different buckets with various case/diacritics in each for the measurement 'name'. - assert.commandWorked(coll.insert({time: ISODate(), meta: "a", name: 'A'})); - assert.commandWorked(coll.insert({time: ISODate(), meta: "a", name: 'a'})); - assert.commandWorked(coll.insert({time: ISODate(), meta: "a", name: 'á'})); - assert.commandWorked(coll.insert({time: ISODate(), meta: "b", name: 'A'})); - assert.commandWorked(coll.insert({time: ISODate(), meta: "b", name: 'a'})); - assert.commandWorked(coll.insert({time: ISODate(), meta: "b", name: 'ä'})); - - // Test with the collection's collation, which is case and diacritic insensitive. - assert.eq(1, coll.aggregate([{$sortByCount: "$name"}]).itcount()); - - // Test with explicit collation that is different from the collection's. - assert.eq(2, coll.aggregate([{$sortByCount: "$name"}], {collation: caseSensitive}).itcount()); - assert.eq(3, - coll.aggregate([{$sortByCount: "$name"}], {collation: diacriticSensitive}).itcount()); -}()); - -// For $group queries that would put whole buckets into the same group, it might be possible to -// avoid unpacking if the information the group is computing is exposed in the control data of each -// bucket. Currently, we only do this optimization for min/max with the meta as the group key. -(function testAgg_MinMaxOptimization() { - coll.drop(); - - assert.commandWorked(db.createCollection( - coll.getName(), - {timeseries: {timeField: 'time', metaField: 'meta'}, collation: numericOrdering})); - assert.contains(bucketsColl.getName(), db.getCollectionNames()); - - // These two docs will be placed in the same bucket, and the max for the bucket will be computed - // using collection's collation, that is, it should be "10". - assert.commandWorked(coll.insert({time: ISODate(), meta: 42, val: "10"})); - assert.commandWorked(coll.insert({time: ISODate(), meta: 42, val: "5"})); - - // Let's check our understanding of what happens with the bucketing as otherwise the tests below - // won't be testing what we think they are. - let buckets = bucketsColl.find().toArray(); - assert.eq(1, buckets.length, "All docs should be placed into the same bucket"); - assert.eq("10", buckets[0].control.max.val, "Computed max control for 'val' measurement"); - - // Use the collection's collation with numeric ordering. - let res1 = coll.aggregate([{$group: {_id: "$meta", v: {$max: "$val"}}}]).toArray(); - assert.eq("10", res1[0].v, "max val in numeric ordering per the collection's collation"); - - // Use the collection's collation with lexicographic ordering. - let res2 = - coll.aggregate([{$group: {_id: "$meta", v: {$max: "$val"}}}], {collation: insensitive}) - .toArray(); - assert.eq("5", res2[0].v, "max val in lexicographic ordering per the query collation"); -}()); - -(function testFind_IndexWithDifferentCollation() { - coll.drop(); - - assert.commandWorked(db.createCollection( - coll.getName(), - {timeseries: {timeField: 'time', metaField: 'meta'}, collation: diacriticSensitive})); - assert.contains(bucketsColl.getName(), db.getCollectionNames()); - - // Create index with a different collation. - assert.commandWorked(coll.createIndex({meta: 1}, {collation: insensitive})); - - // We only check that the correct plan is chosen so the contents of the collection don't matter - // as long as it's not empty. - assert.commandWorked(coll.insert({time: ISODate(), meta: 42})); - assert.commandWorked(coll.insert({time: ISODate(), meta: "the answer"})); - - // Queries that don't specify explicit collation should use the collection's default collation - // which isn't compatible with the index, so the index should NOT be used. - let query = coll.find({meta: "str"}).explain(); - assert(!aggPlanHasStage(query, "IXSCAN"), query); - - // Queries with an explicit collation which isn't compatible with the index, should NOT do - // index scan. - query = coll.find({meta: "str"}).collation(caseSensitive).explain(); - assert(!aggPlanHasStage(query, "IXSCAN"), query); - - // Queries with the same collation as in the index, should do index scan. - query = coll.find({meta: "str"}).collation(insensitive).explain(); - assert(aggPlanHasStage(query, "IXSCAN"), query); +const simpleCollation = { + collation: {locale: "simple"} +}; - // Numeric queries that don't rely on collation should do index scan. - query = coll.find({meta: 1}).explain(); - assert(aggPlanHasStage(query, "IXSCAN"), query); -}()); +assert.commandWorked(db.createCollection(coll.getName(), { + timeseries: {timeField: timeFieldName, metaField: metaFieldName}, + collation: englishCollation +})); +assert.contains(bucketsColl.getName(), db.getCollectionNames()); + +assert.commandWorked( + coll.insert({[timeFieldName]: ISODate(), [metaFieldName]: "1", name: 'A', name2: "á"})); +assert.commandWorked( + coll.insert({[timeFieldName]: ISODate(), [metaFieldName]: "2", name: 'a', name2: "á"})); +assert.commandWorked( + coll.insert({[timeFieldName]: ISODate(), [metaFieldName]: "5", name: 'A', name2: "á"})); +assert.commandWorked( + coll.insert({[timeFieldName]: ISODate(), [metaFieldName]: "10", name: 'a', name2: "á"})); +assert.commandWorked( + coll.insert({[timeFieldName]: ISODate(), [metaFieldName]: "20", name: 'A', name2: "a"})); +assert.commandWorked( + coll.insert({[timeFieldName]: ISODate(), [metaFieldName]: "50", name: 'B', name2: "a"})); +assert.commandWorked( + coll.insert({[timeFieldName]: ISODate(), [metaFieldName]: "100", name: 'b', name2: "a"})); +assert.commandWorked( + coll.insert({[timeFieldName]: ISODate(), [metaFieldName]: "200", name: 'B', name2: "a"})); +assert.commandWorked( + coll.insert({[timeFieldName]: ISODate(), [metaFieldName]: "500", name: 'b', name2: "a"})); + +// Default collation is case and diacretic insensitive. +assert.eq(2, coll.aggregate([{$sortByCount: "$name"}]).itcount()); +assert.eq(1, coll.aggregate([{$sortByCount: "$name2"}]).itcount()); + +// Test that a explicit collation different from collection's default passes for a timeseries +// collection. +let results = + coll.aggregate([{$bucket: {groupBy: "$meta", boundaries: ["1", "10", "100", "1000"]}}], + numericOrdering) + .toArray(); +assert.eq(3, results.length); +assert.eq({_id: "1", count: 3}, results[0]); +assert.eq({_id: "10", count: 3}, results[1]); +assert.eq({_id: "100", count: 3}, results[2]); + +assert.eq(4, coll.aggregate([{$sortByCount: "$name"}], caseSensitive).itcount()); +assert.eq(2, coll.aggregate([{$sortByCount: "$name2"}], diacriticSensitive).itcount()); + +coll.drop(); +const defaultCollation = { + locale: "en", + numericOrdering: true, + caseLevel: true, + strength: 2 +}; +assert.commandWorked(db.createCollection(coll.getName(), { + timeseries: {timeField: timeFieldName, metaField: metaFieldName}, + collation: defaultCollation +})); +assert.contains(bucketsColl.getName(), db.getCollectionNames()); +assert.commandWorked(coll.createIndex({[metaFieldName]: 1}, {collation: {locale: "simple"}})); + +assert.commandWorked(coll.insert( + {[timeFieldName]: ISODate(), [metaFieldName]: 1, name: 'A', name2: "á", value: "1"})); +assert.commandWorked(coll.insert( + {[timeFieldName]: ISODate(), [metaFieldName]: 2, name: 'a', name2: "á", value: "11"})); +assert.commandWorked(coll.insert( + {[timeFieldName]: ISODate(), [metaFieldName]: 1, name: 'A', name2: "á", value: "50"})); +assert.commandWorked(coll.insert( + {[timeFieldName]: ISODate(), [metaFieldName]: 1, name: 'a', name2: "á", value: "100"})); +assert.commandWorked(coll.insert( + {[timeFieldName]: ISODate(), [metaFieldName]: "2", name: 'A', name2: "a", value: "3"})); +assert.commandWorked(coll.insert( + {[timeFieldName]: ISODate(), [metaFieldName]: "5", name: 'B', name2: "a", value: "-100"})); +assert.commandWorked(coll.insert( + {[timeFieldName]: ISODate(), [metaFieldName]: "1", name: 'b', name2: "a", value: "-200"})); +assert.commandWorked(coll.insert( + {[timeFieldName]: ISODate(), [metaFieldName]: "2", name: 'B', name2: "a", value: "1000"})); +assert.commandWorked(coll.insert( + {[timeFieldName]: ISODate(), [metaFieldName]: "5", name: 'b', name2: "a", value: "4"})); + +// This collection has been created using non simple collation. The collection was then indexed on +// its metadata using simple collation. These tests confirm that queries on the indexed field using +// nondefault (simple) collation use the index. They also confirm that queries that don't involve +// strings but do use default collation, on indexed fields, also use the index. +const nonDefaultCollationQuery = coll.find({meta: 2}, {collation: englishCollation}).explain(); +assert(aggPlanHasStage(nonDefaultCollationQuery, "IXSCAN"), nonDefaultCollationQuery); + +const simpleNonDefaultCollationQuery = coll.find({meta: 2}, simpleCollation).explain(); +assert(aggPlanHasStage(simpleNonDefaultCollationQuery, "IXSCAN"), simpleNonDefaultCollationQuery); + +const defaultCollationQuery = coll.find({meta: 1}, {collation: defaultCollation}).explain(); +assert(aggPlanHasStage(defaultCollationQuery, "IXSCAN"), defaultCollationQuery); + +// This test guarantees that the bucket's min/max matches the query's min/max regardless of +// collation. +results = coll.find({value: {$gt: "4"}}, simpleCollation); +assert.eq(4, results.itcount()); }()); diff --git a/jstests/core/timeseries/partialFilterExpression_with_internalBucketGeoWithin.js b/jstests/core/timeseries/partialFilterExpression_with_internalBucketGeoWithin.js index 61521e7f86e..5be75d79e28 100644 --- a/jstests/core/timeseries/partialFilterExpression_with_internalBucketGeoWithin.js +++ b/jstests/core/timeseries/partialFilterExpression_with_internalBucketGeoWithin.js @@ -10,11 +10,12 @@ */ load("jstests/libs/analyze_plan.js"); -load("jstests/libs/feature_flag_util.js"); load('jstests/noPassthrough/libs/index_build.js'); - (function() { -if (FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { +const isFeatureEnabled = db.adminCommand({getParameter: 1, featureFlagTimeseriesMetricIndexes: 1}) + .featureFlagTimeseriesMetricIndexes.value; + +if (isFeatureEnabled) { const timeFieldName = "timestamp"; const coll = db.partialFilterExpression_with_internalBucketGeoWithin; diff --git a/jstests/core/timeseries/timeseries_bucket_rename.js b/jstests/core/timeseries/timeseries_bucket_rename.js new file mode 100644 index 00000000000..98a0b73b810 --- /dev/null +++ b/jstests/core/timeseries/timeseries_bucket_rename.js @@ -0,0 +1,28 @@ +/** + * Tests that a system.buckets collection cannot be renamed. + * + * @tags: [ + * does_not_support_stepdowns, + * does_not_support_transactions, + * requires_getmore, + * ] + */ +(function() { +'use strict'; + +const coll = db.timeseries_bucket_rename; +const bucketsColl = db.getCollection('system.buckets.' + coll.getName()); + +const timeFieldName = 'time'; + +coll.drop(); +assert.commandWorked(db.createCollection(coll.getName(), {timeseries: {timeField: timeFieldName}})); +assert.contains(bucketsColl.getName(), db.getCollectionNames()); + +assert.commandFailedWithCode(db.adminCommand({ + renameCollection: bucketsColl.getFullName(), + to: db.getName() + ".otherColl", + dropTarget: false +}), + ErrorCodes.IllegalOperation); +})(); diff --git a/jstests/core/timeseries/timeseries_collection_uuid.js b/jstests/core/timeseries/timeseries_collection_uuid.js deleted file mode 100644 index 2ba80cc8f16..00000000000 --- a/jstests/core/timeseries/timeseries_collection_uuid.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Tests using the collectionUUID parameter when operating on a time-series collection. - * - * @tags: [ - * does_not_support_transactions, - * requires_fcv_60, - * requires_timeseries, - * ] - */ - -(function() { -"use strict"; - -const dbName = jsTestName(); -const collName = "coll"; - -const testDB = db.getSiblingDB(dbName); -testDB.dropDatabase(); - -assert.commandWorked( - testDB.createCollection(collName, {timeseries: {timeField: "t", metaField: "m"}})); -const coll = testDB[collName]; -const bucketsColl = testDB["system.buckets." + collName]; - -const nonexistentUUID = UUID(); -const bucketsCollUUID = testDB.getCollectionInfos({name: bucketsColl.getName()})[0].info.uuid; - -const testInsert = function(uuid, ordered) { - assert.commandFailedWithCode(testDB.runCommand({ - insert: collName, - documents: [{t: ISODate()}], - collectionUUID: uuid, - ordered: ordered, - }), - ErrorCodes.CollectionUUIDMismatch); -}; - -testInsert(nonexistentUUID, true); -testInsert(nonexistentUUID, false); -testInsert(bucketsCollUUID, true); -testInsert(bucketsCollUUID, false); -}()); diff --git a/jstests/core/timeseries/timeseries_collmod.js b/jstests/core/timeseries/timeseries_collmod.js index 07c9166b632..69fd7dbeb82 100644 --- a/jstests/core/timeseries/timeseries_collmod.js +++ b/jstests/core/timeseries/timeseries_collmod.js @@ -19,17 +19,6 @@ assert.commandWorked( db.createCollection(collName, {timeseries: {timeField: "time", granularity: 'seconds'}})); assert.commandWorked(coll.createIndex({"time": 1})); -// Setting prepareUnique should return an error on a time-series collection. -assert.commandFailedWithCode( - db.runCommand( - {"collMod": collName, "index": {"keyPattern": {"time": 1}, "prepareUnique": true}}), - ErrorCodes.InvalidOptions); - -assert.commandFailedWithCode( - db.runCommand( - {"collMod": collName, "index": {"keyPattern": {"time": 1}, "prepareUnique": false}}), - ErrorCodes.InvalidOptions); - // Tries to convert a time-series secondary index to TTL index. assert.commandFailedWithCode( db.runCommand( @@ -67,4 +56,4 @@ assert.commandWorked(db.runCommand({"collMod": collName, "expireAfterSeconds": 6 // Successfully sets the granularity for a time-series collection. assert.commandWorked( db.runCommand({"collMod": collName, "timeseries": {"granularity": "minutes"}})); -})(); +})();
\ No newline at end of file diff --git a/jstests/core/timeseries/timeseries_computed_field.js b/jstests/core/timeseries/timeseries_computed_field.js deleted file mode 100644 index fab03d1a3b0..00000000000 --- a/jstests/core/timeseries/timeseries_computed_field.js +++ /dev/null @@ -1,856 +0,0 @@ -/** - * Test use of computed fields in aggregations on time series collections. - * @tags: [ - * requires_timeseries, - * does_not_support_stepdowns, - * directly_against_shardsvrs_incompatible, - * # "Explain of a resolved view must be executed by mongos" - * directly_against_shardsvrs_incompatible, - * # Some suites use mixed-binary cluster setup where some nodes might have the flag enabled while - * # others -- not. For this test we need control over whether the flag is set on the node that - * # ends up executing the query. - * assumes_standalone_mongod - * ] - */ -load("jstests/core/timeseries/libs/timeseries.js"); - -TimeseriesTest.run((insert) => { - const datePrefix = 1680912440; - - let coll = db.timeseries_computed_field; - const bucketsColl = db.getCollection('system.buckets.' + coll.getName()); - - const timeFieldName = 'time'; - const metaFieldName = 'measurement'; - - coll.drop(); - assert.commandWorked(db.createCollection(coll.getName(), { - timeseries: {timeField: timeFieldName, metaField: metaFieldName}, - })); - assert.contains(bucketsColl.getName(), db.getCollectionNames()); - - insert(coll, { - _id: 0, - [timeFieldName]: new Date(datePrefix + 100), // ISODate("1970-01-20T10:55:12.540Z") - [metaFieldName]: "cpu", - topLevelScalar: 123, - topLevelScalarDouble: 123.8778645, - topLevelLargeNumber: 12345678912345, - topLevelArray: [1, 2, 3, 4], - arrOfObj: [{x: 1}, {x: 2}, {x: 3}, {x: 4}], - obj: {a: 123}, - length: 0, - }); - insert(coll, { - _id: 1, - [timeFieldName]: new Date(datePrefix + 200), // ISODate("1970-01-20T10:55:12.640Z") - [metaFieldName]: "cpu", - topLevelScalar: 456, - topLevelScalarDouble: 546.76858699, - topLevelArray: [101, 102, 103, 104], - arrOfObj: [{x: 101}, {x: 102}, {x: 103}, {x: 104}], - obj: {a: 456}, - length: 23, - }); - // Insert a document that will be placed in a different bucket. - insert(coll, { - _id: 2, - [timeFieldName]: new Date(datePrefix + 300), - [metaFieldName]: "gpu", - length: -2, - }); - - // Computing a field on a dotted path which is an array, then grouping on it. Note that the - // semantics for setting a computed field on a dotted array path are particularly strange, but - // should be preserved for backwards compatibility. - { - const res = coll.aggregate([ - {$addFields: {"arrOfObj.x": {$trim: {input: "test string"}}}}, - {$match: {topLevelScalar: {$gte: 0}}}, - {$group: {_id: null, max: {$max: "$arrOfObj.x"}}} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].max, ["test string", "test string", "test string", "test string"], res); - } - - { - const res = coll.aggregate([ - {$addFields: {"arrOfObj.x": {$add: ["$topLevelScalar", 1]}}}, - {$match: {topLevelScalar: {$gte: 0}}}, - {$group: {_id: null, max: {$max: "$arrOfObj.x"}}} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].max, [457, 457, 457, 457], res); - } - - // Computing a field and then filtering by it. - { - const res = coll.aggregate([ - {$addFields: {"arrOfObj.x": {$trim: {input: "test string"}}}}, - {$match: {"arrOfObj.x": "test string"}}, - {$group: {_id: null, max: {$max: "$arrOfObj.x"}}} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].max, ["test string", "test string", "test string", "test string"], res); - } - - { - // Computing a field based on a dotted path which does not traverse arrays. - const res = coll.aggregate([ - {$addFields: {"computedA": {$add: ["$obj.a", 1]}}}, - // Only one document should have a value where obj.a was 457 - // (456 + 1). - {$match: {"computedA": 457}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - - { - // Include an unnecessary computed field before counting the number of documents. - const res = coll.aggregate([{$addFields: {"a": 1}}, {$count: "count"}]).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 3, res); - } - - // mathematical expressions - { - let pipeline = [ - {$addFields: {"computedA": {$add: ["$topLevelScalar", 1]}}}, - {$match: {"computedA": 457}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$subtract: ["$topLevelScalar", 1]}}}, - {$match: {"computedA": 455}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$multiply: ["$topLevelScalar", 10]}}}, - {$match: {"computedA": 4560}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$divide: ["$topLevelScalar", 2]}}}, - {$match: {"computedA": 228}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$add: [1, "$topLevelScalar"]}}}, - {$match: {"computedA": 457}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$subtract: [200, "$topLevelScalar"]}}}, - {$match: {"computedA": 77}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$multiply: [10, "$topLevelScalar"]}}}, - {$match: {"computedA": 4560}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$divide: [4560, "$topLevelScalar"]}}}, - {$match: {"computedA": 10}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - - { - let pipeline = [ - {$addFields: {"computedA": {$add: ["$topLevelScalar", "$topLevelScalar"]}}}, - {$match: {"computedA": 912}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$subtract: ["$topLevelScalar", "$topLevelScalar"]}}}, - {$match: {"computedA": 0}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$multiply: ["$topLevelScalar", "$topLevelScalar"]}}}, - {$match: {"computedA": 15129}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$divide: ["$topLevelScalar", "$topLevelScalar"]}}}, - {$match: {"computedA": 1}}, - {$count: "count"} - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - let pipeline = [ - { - $addFields: { - "hourDiff": { - $dateDiff: { - "startDate": "$time", - "endDate": new Date("1970-01-21"), - "unit": "hour" - } - } - } - }, - {$match: {"hourDiff": {$gte: 12}}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "open": {"$first": "$topLevelScalar"}, - } - } - ]; - - assert.docEq([{"_id": {"time": ISODate("1970-01-20T10:55:00Z")}, "open": 123}], - coll.aggregate(pipeline).toArray()); - } - - { - let pipeline = [ - {$addFields: {"mt": {$multiply: ["$topLevelScalar", 10]}}}, - {$match: {"mt": {$gte: 2000}}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "open": {"$first": "$topLevelScalar"}, - } - } - ]; - - assert.docEq([{"_id": {"time": ISODate("1970-01-20T10:55:00Z")}, "open": 456}], - coll.aggregate(pipeline).toArray()); - } - - { - let pipeline = [ - { - $addFields: { - "hourDiff": { - $dateDiff: { - "startDate": "$time", - "endDate": new Date("1970-01-21"), - "unit": "hour" - } - } - } - }, - {$match: {$or: [{topLevelScalar: {$gt: 200, $lt: 800}}, {hourDiff: {$gte: 12}}]}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "open": {"$first": "$topLevelScalar"}, - } - } - ]; - - assert.docEq([{"_id": {"time": ISODate("1970-01-20T10:55:00Z")}, "open": 123}], - coll.aggregate(pipeline).toArray()); - } - - { - // Try a project stage which adds and remove subfields. - const res = coll.aggregate([ - {$project: {"obj.newField": "$topLevelScalar"}}, - {$project: {"_id": 0, "obj.a": 0}} - ]) - .toArray(); - assert.eq(res.length, 3, res); - assert.eq(res[0], {"obj": {"newField": 123}}, res); - assert.eq(res[1], {"obj": {"newField": 456}}, res); - assert.eq(res[2], {"obj": {}}, res); - } - - { - // Try a replaceRoot stage which remove all fields. - const res = coll.aggregate([ - {$match: {"time": {$gte: new Date(datePrefix + 200)}}}, - {$addFields: {}}, - {$project: {"measurement0": {$floor: "$topLevelScalar"}}}, - {$replaceRoot: {newRoot: {}}} - ]) - .toArray(); - assert.eq(res.length, 2, res); - assert.eq(res[0], {}, res); - assert.eq(res[1], {}, res); - } - - { - // Try a $match that works on fields that are not projected. - const res = - coll.aggregate([ - {$project: {"obj.a": 1}}, - {$match: {$or: [{"topLevelScalar": {$gt: 10}}, {$expr: {$literal: true}}]}}, - {$sort: {_id: 1}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 3, res); - } - - { - const res = coll.aggregate([{ - "$project": { - "t": { - "$dateDiff": { - "startDate": "$time", - "endDate": new Date(datePrefix + 150), - "unit": "millisecond" - } - } - } - }]) - .toArray(); - assert.eq(res.length, 3, res); - assert.docEq(res[0], {_id: 0, "t": NumberLong(50)}, res); - assert.docEq(res[1], {_id: 1, "t": NumberLong(-50)}, res); - assert.docEq(res[2], {_id: 2, "t": NumberLong(-150)}, res); - } - - { - const res = coll.aggregate([{ - "$project": { - "t": { - "$dateDiff": { - "startDate": new Date(datePrefix - 10), - "endDate": "$time", - "unit": "millisecond" - } - } - } - }]) - .toArray(); - assert.eq(res.length, 3, res); - assert.docEq(res[0], {_id: 0, "t": NumberLong(110)}, res); - assert.docEq(res[1], {_id: 1, "t": NumberLong(210)}, res); - assert.docEq(res[2], {_id: 2, "t": NumberLong(310)}, res); - } - - { - const res = coll.aggregate([ - { - $addFields: { - "computedField": { - $dateTrunc: { - date: "$time", - unit: "second", - } - } - } - }, - {$match: {computedField: new Date(datePrefix - 440)}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 3, res); - } - - { - const res = coll.aggregate([{$match: {topLevelScalar: {$exists: true}}}, {$count: "count"}]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - const res = - coll.aggregate([{$match: {topLevelScalar: {$lt: 456}}}, {$count: "count"}]).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - - { - const res = - coll.aggregate([{$match: {topLevelScalar: {$lte: 456}}}, {$count: "count"}]).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - const res = - coll.aggregate([{$match: {topLevelScalar: {$gt: 123}}}, {$count: "count"}]).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - - { - const res = - coll.aggregate([{$match: {topLevelScalar: {$gte: 123}}}, {$count: "count"}]).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - const res = - coll.aggregate([{$match: {topLevelScalar: {$eq: 123}}}, {$count: "count"}]).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - - { - const res = - coll.aggregate([{$match: {topLevelScalar: {$ne: 123}}}, {$count: "count"}]).toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - const res = coll.aggregate([ - { - $match: { - $or: [ - {"topLevelScalar": 123}, - {"topLevelScalar": 456}, - ] - } - }, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - const res = coll.aggregate([ - {$addFields: {"computedField": {$and: ["$topLevelScalar", true]}}}, - {$match: {computedField: true}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - const res = coll.aggregate([ - {$addFields: {"computedField": {$and: ["$topLevelScalar", "$obj.a"]}}}, - {$match: {computedField: true}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - const res = coll.aggregate([ - {$addFields: {"computedField": {$or: ["$topLevelScalar", false]}}}, - {$match: {computedField: true}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - const res = coll.aggregate([ - {$addFields: {"computedField": {$or: ["$topLevelScalar", "$obj.a"]}}}, - {$match: {computedField: true}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - const res = coll.aggregate([ - {$addFields: {"computedField": {$not: ["$topLevelScalar"]}}}, - {$match: {computedField: true}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - - { - const res = - coll.aggregate([ - {$addFields: {"computedField": {$anyElementTrue: [["$topLevelScalar"]]}}}, - {$match: {computedField: true}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 2, res); - } - - { - const res = - coll.aggregate([ - {$match: {length: {$ne: 0}}}, - {$set: {"computedA": {$multiply: ["$topLevelScalar", "$topLevelScalar"]}}}, - {$addFields: {"ratio": {$divide: ["$computedA", "$length"]}}}, - {$match: {ratio: {$gt: 0}}}, - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0]._id, 1, res); - } - - { - let pipeline = [ - {$addFields: {"computedA": {$round: ["$topLevelScalarDouble", 2]}}}, - {$match: {"computedA": {$gte: 100}}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "value": {$sum: "$computedA"}, - } - } - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.docEq([{"_id": {"time": ISODate("1970-01-20T10:55:00Z")}, "value": 670.65}], res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$round: ["$topLevelScalarDouble"]}}}, - {$match: {"computedA": {$gte: 100}}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "value": {$sum: "$computedA"}, - } - } - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.docEq([{"_id": {"time": ISODate("1970-01-20T10:55:00Z")}, "value": 671}], res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$round: [2, "$topLevelScalarDouble"]}}}, - {$match: {"computedA": {$gte: 100}}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "value": {$sum: "$computedA"}, - } - } - ]; - - assert.throws(() => coll.aggregate(pipeline)); - } - { - let pipeline = [ - {$addFields: {"computedA": {$round: ["$topLevelScalarDouble", "$topLevelScalar"]}}}, - {$match: {"computedA": {$gte: 100}}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "value": {$sum: "$computedA"}, - } - } - ]; - - assert.throws(() => coll.aggregate(pipeline)); - } - - { - let pipeline = [ - {$addFields: {"computedA": {$trunc: ["$topLevelScalarDouble", 2]}}}, - {$match: {"computedA": {$gte: 100}}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "value": {$sum: "$computedA"}, - } - } - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.docEq([{"_id": {"time": ISODate("1970-01-20T10:55:00Z")}, "value": 670.63}], res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$trunc: ["$topLevelScalarDouble"]}}}, - {$match: {"computedA": {$gte: 100}}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "value": {$sum: "$computedA"}, - } - } - ]; - - const res = coll.aggregate(pipeline).toArray(); - assert.docEq([{"_id": {"time": ISODate("1970-01-20T10:55:00Z")}, "value": 669}], res); - } - { - let pipeline = [ - {$addFields: {"computedA": {$trunc: [2, "$topLevelScalarDouble"]}}}, - {$match: {"computedA": {$gte: 100}}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "value": {$sum: "$computedA"}, - } - } - ]; - - assert.throws(() => coll.aggregate(pipeline)); - } - { - let pipeline = [ - {$addFields: {"computedA": {$trunc: ["$topLevelScalarDouble", "$topLevelScalar"]}}}, - {$match: {"computedA": {$gte: 100}}}, - { - $group: { - "_id": {"time": {"$dateTrunc": {"date": "$time", "unit": "minute"}}}, - "value": {$sum: "$computedA"}, - } - } - ]; - - assert.throws(() => coll.aggregate(pipeline)); - } - - { - let pipeline = [ - {$addFields: {md: {$mod: ["$topLevelScalar", 5]}}}, - { - $group: { - "_id": {$dateTrunc: {"date": "$time", "unit": "minute"}}, - "total1": {$sum: "$md"}, - "total2": {$sum: "$topLevelScalar"} - } - } - ]; - - assert.docEq([{"_id": ISODate("1970-01-20T10:55:00Z"), "total1": 4, "total2": 579}], - coll.aggregate(pipeline).toArray()); - } - - { - let pipeline = [ - {$addFields: {md: {$mod: [500, "$topLevelScalar"]}}}, - { - $group: { - "_id": {$dateTrunc: {"date": "$time", "unit": "minute"}}, - "total1": {$sum: "$md"}, - "total2": {$sum: "$topLevelScalar"} - } - } - ]; - - assert.docEq([{"_id": ISODate("1970-01-20T10:55:00Z"), "total1": 52, "total2": 579}], - coll.aggregate(pipeline).toArray()); - } - - { - let pipeline = [ - {$addFields: {md: {$mod: ["$topLevelScalar", "$topLevelScalar"]}}}, - { - $group: { - "_id": {$dateTrunc: {"date": "$time", "unit": "minute"}}, - "total1": {$sum: "$md"}, - "total2": {$sum: "$topLevelScalar"} - } - } - ]; - - assert.docEq([{"_id": ISODate("1970-01-20T10:55:00Z"), "total1": 0, "total2": 579}], - coll.aggregate(pipeline).toArray()); - } - - { - let pipeline = [ - {$addFields: {md: {$mod: ["$topLevelScalar", 5]}}}, - {$match: {md: {$mod: [4, 1]}}}, - { - $group: { - "_id": {$dateTrunc: {"date": "$time", "unit": "minute"}}, - "total1": {$sum: "$md"}, - "total2": {$sum: "$topLevelScalar"} - } - } - ]; - - assert.docEq([{"_id": ISODate("1970-01-20T10:55:00Z"), "total1": 1, "total2": 456}], - coll.aggregate(pipeline).toArray()); - } - { - let pipeline = [{$match: {topLevelLargeNumber: {$mod: [1, 0]}}}, {$count: "count"}]; - - const res = coll.aggregate(pipeline).toArray(); - - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - { - const res = - coll.aggregate([ - { - $addFields: { - "computedField": - {$dateAdd: {startDate: "$time", unit: "millisecond", amount: 100}} - } - }, - {$match: {computedField: new Date(datePrefix + 400)}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - - { - const res = coll.aggregate([ - { - $addFields: { - "computedField": { - $dateSubtract: - {startDate: "$time", unit: "millisecond", amount: 100} - } - } - }, - {$match: {computedField: new Date(datePrefix)}}, - {$count: "count"} - ]) - .toArray(); - assert.eq(res.length, 1, res); - assert.eq(res[0].count, 1, res); - } - - { - // Test the case where a computed meta field is computed to missing. - const res = - coll.aggregate([{"$project": {"_id": 0, [metaFieldName]: "$$REMOVE"}}]).toArray(); - assert.eq(res.length, coll.count(), res); - for (let doc of res) { - assert.eq(doc, {}, res); - } - } - - { - // Test the case where a computed meta field is computed to missing because of a project - // out. - const res = coll.aggregate([ - {"$project": {[metaFieldName]: 0}}, - {"$project": {[metaFieldName]: "$" + metaFieldName}}, - {$match: {a: 1}} - ]) - .toArray(); - assert.eq(res.length, 0, res); - } - - { - // Test the case where a field is projected out and then projected back in. - const res = coll.aggregate([ - {"$project": {"_id": 0, [metaFieldName]: 1}}, - {"$project": {"_id": 0, [timeFieldName]: "$" + timeFieldName}}, - {"$project": {"_id": 1, [timeFieldName]: 1}} - ]) - .toArray(); - assert.eq(res.length, coll.count(), res); - for (let doc of res) { - assert.eq(doc, {}, res); - } - } - - { - // Test the case where a field is projected out and then projected back in. - const res = coll.aggregate([ - {"$project": {"_id": 0, [metaFieldName]: 1}}, - {"$project": {"_id": 0, [timeFieldName]: 1}} - ]) - .toArray(); - assert.eq(res.length, coll.count(), res); - for (let doc of res) { - assert.eq(doc, {}, res); - } - } - - { - // Test the case where a field is projected out and then projected back in. - const res = coll.aggregate([ - {"$project": {"_id": 0, [metaFieldName]: 0}}, - {"$project": {"_id": 0, [metaFieldName]: 1}}, - {"$project": {"_id": 0, [timeFieldName]: 1}} - ]) - .toArray(); - assert.eq(res.length, coll.count(), res); - for (let doc of res) { - assert.eq(doc, {}, res); - } - } -}); diff --git a/jstests/core/timeseries/timeseries_create_collection.js b/jstests/core/timeseries/timeseries_create_collection.js index 99616605163..feb2f443d26 100644 --- a/jstests/core/timeseries/timeseries_create_collection.js +++ b/jstests/core/timeseries/timeseries_create_collection.js @@ -17,15 +17,6 @@ assert.commandWorked(testDB.dropDatabase()); const timeFieldName = 'time'; const coll = testDB.t; -// Fails to create a time-series collection with null-embedded timeField or metaField. -assert.commandFailedWithCode( - testDB.createCollection(coll.getName(), {timeseries: {timeField: '\0time'}}), - ErrorCodes.BadValue); -assert.commandFailedWithCode( - testDB.createCollection(coll.getName(), - {timeseries: {timeField: timeFieldName, metaField: 't\0ag'}}), - ErrorCodes.BadValue); - // Create a timeseries collection, listCollection should show view and bucket collection assert.commandWorked( testDB.createCollection(coll.getName(), {timeseries: {timeField: timeFieldName}})); diff --git a/jstests/core/timeseries/timeseries_delete_hint.js b/jstests/core/timeseries/timeseries_delete_hint.js index 69fb2bc5a91..0facca816e1 100644 --- a/jstests/core/timeseries/timeseries_delete_hint.js +++ b/jstests/core/timeseries/timeseries_delete_hint.js @@ -18,11 +18,11 @@ (function() { "use strict"; +load("jstests/core/timeseries/libs/timeseries.js"); load("jstests/libs/curop_helpers.js"); -load("jstests/libs/feature_flag_util.js"); load('jstests/libs/parallel_shell_helpers.js'); -if (!FeatureFlagUtil.isEnabled(db, "TimeseriesUpdatesAndDeletes")) { +if (!TimeseriesTest.timeseriesUpdatesAndDeletesEnabled(db.getMongo())) { jsTestLog("Skipping test because the time-series updates and deletes feature flag is disabled"); return; } @@ -71,7 +71,7 @@ const validateDeleteIndex = (docsToInsert, : assert.commandWorked( testDB.runCommand({delete: coll.getName(), deletes: deleteQuery})); assert.eq(res["n"], expectedNRemoved); - assert.sameMembers(coll.find({}, {_id: 0}).toArray(), expectedRemainingDocs); + assert.docEq(coll.find({}, {_id: 0}).toArray(), expectedRemainingDocs); assert(coll.drop()); }, docsToInsert, diff --git a/jstests/core/timeseries/timeseries_field_parsed_as_bson.js b/jstests/core/timeseries/timeseries_field_parsed_as_bson.js deleted file mode 100644 index 70210a90072..00000000000 --- a/jstests/core/timeseries/timeseries_field_parsed_as_bson.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Tests that timeseries timeField is parsed as bson. - * - * @tags: [ - * # We need a timeseries collection. - * requires_timeseries, - * # Cannot insert into a time-series collection in a multi-document transaction - * does_not_support_transactions, - * multiversion_incompatible - * ] - */ - -(function() { -'use strict'; - -const collName = "timeseries_field_parsed_as_bson"; -const coll = db.getCollection(collName); - -coll.drop(); -const timeField = "badInput']}}}}}}"; -assert.commandWorked(db.createCollection(collName, {timeseries: {timeField: timeField}})); - -const timeseriesCollInfo = db.getCollectionInfos({name: "system.buckets." + collName})[0]; -jsTestLog("Timeseries system collection info: " + tojson(timeseriesCollInfo)); -const properties = {}; -properties[timeField] = { - "bsonType": "date" -}; -const expectedValidator = { - "$jsonSchema": { - "bsonType": "object", - "required": ["_id", "control", "data"], - "properties": { - "_id": {"bsonType": "objectId"}, - "control": { - "bsonType": "object", - "required": ["version", "min", "max"], - "properties": { - "version": {"bsonType": "number"}, - "min": - {"bsonType": "object", "required": [timeField], "properties": properties}, - "max": - {"bsonType": "object", "required": [timeField], "properties": properties}, - "closed": {"bsonType": "bool"}, - "count": {"bsonType": "number", "minimum": 1} - }, - "additionalProperties": false - }, - "data": {"bsonType": "object"}, - "meta": {} - }, - "additionalProperties": false - } -}; - -assert(timeseriesCollInfo.options); -assert.eq(timeseriesCollInfo.options.validator, expectedValidator); - -const doc = { - a: 1, - [timeField]: new Date("2021-01-01") -}; -assert.commandWorked(coll.insert(doc)); -assert.docEq([doc], coll.aggregate([{$match: {}}, {$project: {_id: 0}}]).toArray()); - -coll.drop(); -assert.commandWorked(db.createCollection(collName, {timeseries: {timeField: "\\"}})); -coll.drop(); -assert.commandWorked(db.createCollection(collName, {timeseries: {timeField: "\\\\"}})); -})(); diff --git a/jstests/core/timeseries/timeseries_filter_extended_range.js b/jstests/core/timeseries/timeseries_filter_extended_range.js deleted file mode 100644 index 2b86e29c943..00000000000 --- a/jstests/core/timeseries/timeseries_filter_extended_range.js +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Test that find/match type queries work properly on dates ouside the 32 bit epoch range, - * [1970-01-01 00:00:00 UTC - 2038-01-29 03:13:07 UTC]. - * - * @tags: [ - * # Refusing to run a test that issues an aggregation command with explain because it may - * # return incomplete results if interrupted by a stepdown. - * does_not_support_stepdowns, - * # We need a timeseries collection. - * requires_timeseries, - * # Cannot insert into a time-series collection in a multi-document transaction - * does_not_support_transactions, - * # Explain of a resolved view must be executed by mongos. - * directly_against_shardsvrs_incompatible, - * ] - */ - -(function() { -"use strict"; -const timeFieldName = "time"; - -/* - * Creates a collection, populates it, runs the `query` and ensures that the result set - * is equal to `results`. - * - * If overflow is set we create a document with dates above the 32 bit range (year 2040) - * If underflow is set, we create a document with dates below the 32 bit range (year 1965) - */ -function runTest(underflow, overflow, query, results) { - // Setup our DB & our collections. - const tsColl = db.getCollection(jsTestName()); - tsColl.drop(); - - assert.commandWorked( - db.createCollection(tsColl.getName(), {timeseries: {timeField: timeFieldName}})); - - const dates = [ - // If underflow, we want to insert a date that would fall below the epoch - // i.e. 1970-01-01 00:00:00 UTC. Otherwise we use a date within the epoch. - {[timeFieldName]: underflow ? new Date("1965-01-01") : new Date("1971-01-01")}, - {[timeFieldName]: new Date("1975-01-01")}, - {[timeFieldName]: new Date("1980-01-01")}, - {[timeFieldName]: new Date("1995-01-01")}, - // If overflow, we want to insert a date that would use more than 32 bit milliseconds after - // the epoch. This overflow will occur 2038-01-29 03:13:07 UTC. Otherwise we go slightly - // before the end of the 32 bit epoch. - {[timeFieldName]: overflow ? new Date("2040-01-01") : new Date("2030-01-01")} - ]; - assert.commandWorked(tsColl.insert(dates)); - - // Make sure the expected results are in the correct order for comparison below. - function cmpTimeFields(a, b) { - return (b[timeFieldName].getTime() - a[timeFieldName].getTime()); - } - results.sort(cmpTimeFields); - - const pipeline = [{$match: query}, {$project: {_id: 0, [timeFieldName]: 1}}]; - - const plan = tsColl.explain().aggregate(pipeline); - - // Verify agg pipeline. We don't want to go through a plan that encourages a sort order to - // avoid BUS and index selection, so we sort after gathering the results. - const aggActuals = tsColl.aggregate(pipeline).toArray(); - aggActuals.sort(cmpTimeFields); - assert.docEq(results, aggActuals, JSON.stringify(plan, null, 4)); - - // Verify the equivalent find command. We again don't want to go through a plan that - // encourages a sort order to avoid BUS and index selection, so we sort after gathering the - // results. - let findActuals = tsColl.find(query, {_id: 0, [timeFieldName]: 1}).toArray(); - findActuals.sort(cmpTimeFields); - assert.docEq(findActuals, results); -} - -runTest(false, - false, - {[timeFieldName]: {$eq: new Date("1980-01-01")}}, - [{[timeFieldName]: new Date("1980-01-01")}]); -runTest(false, - true, - {[timeFieldName]: {$eq: new Date("2040-01-01")}}, - [{[timeFieldName]: new Date("2040-01-01")}]); -runTest(true, - false, - {[timeFieldName]: {$eq: new Date("1965-01-01")}}, - [{[timeFieldName]: new Date("1965-01-01")}]); - -runTest(false, - false, - {[timeFieldName]: {$lt: new Date("1980-01-01")}}, - [{[timeFieldName]: new Date("1971-01-01")}, {[timeFieldName]: new Date("1975-01-01")}]); -runTest(false, - true, - {[timeFieldName]: {$lt: new Date("1980-01-01")}}, - [{[timeFieldName]: new Date("1971-01-01")}, {[timeFieldName]: new Date("1975-01-01")}]); -runTest(true, - false, - {[timeFieldName]: {$lt: new Date("1980-01-01")}}, - [{[timeFieldName]: new Date("1965-01-01")}, {[timeFieldName]: new Date("1975-01-01")}]); -runTest(true, - true, - {[timeFieldName]: {$lt: new Date("1980-01-01")}}, - [{[timeFieldName]: new Date("1965-01-01")}, {[timeFieldName]: new Date("1975-01-01")}]); - -runTest(false, - false, - {[timeFieldName]: {$gt: new Date("1980-01-01")}}, - [{[timeFieldName]: new Date("1995-01-01")}, {[timeFieldName]: new Date("2030-01-01")}]); -runTest(false, - true, - {[timeFieldName]: {$gt: new Date("1980-01-01")}}, - [{[timeFieldName]: new Date("1995-01-01")}, {[timeFieldName]: new Date("2040-01-01")}]); -runTest(true, - false, - {[timeFieldName]: {$gt: new Date("1980-01-01")}}, - [{[timeFieldName]: new Date("1995-01-01")}, {[timeFieldName]: new Date("2030-01-01")}]); -runTest(true, - true, - {[timeFieldName]: {$gt: new Date("1980-01-01")}}, - [{[timeFieldName]: new Date("1995-01-01")}, {[timeFieldName]: new Date("2040-01-01")}]); - -runTest(false, false, {[timeFieldName]: {$lte: new Date("1980-01-01")}}, [ - {[timeFieldName]: new Date("1971-01-01")}, - {[timeFieldName]: new Date("1975-01-01")}, - {[timeFieldName]: new Date("1980-01-01")} -]); -runTest(false, true, {[timeFieldName]: {$lte: new Date("1980-01-01")}}, [ - {[timeFieldName]: new Date("1971-01-01")}, - {[timeFieldName]: new Date("1975-01-01")}, - {[timeFieldName]: new Date("1980-01-01")} -]); -runTest(true, false, {[timeFieldName]: {$lte: new Date("1980-01-01")}}, [ - {[timeFieldName]: new Date("1965-01-01")}, - {[timeFieldName]: new Date("1975-01-01")}, - {[timeFieldName]: new Date("1980-01-01")} -]); -runTest(true, true, {[timeFieldName]: {$lte: new Date("1980-01-01")}}, [ - {[timeFieldName]: new Date("1965-01-01")}, - {[timeFieldName]: new Date("1975-01-01")}, - {[timeFieldName]: new Date("1980-01-01")} -]); - -runTest(false, false, {[timeFieldName]: {$gte: new Date("1980-01-01")}}, [ - {[timeFieldName]: new Date("1980-01-01")}, - {[timeFieldName]: new Date("1995-01-01")}, - {[timeFieldName]: new Date("2030-01-01")} -]); -runTest(false, true, {[timeFieldName]: {$gte: new Date("1980-01-01")}}, [ - {[timeFieldName]: new Date("1980-01-01")}, - {[timeFieldName]: new Date("1995-01-01")}, - {[timeFieldName]: new Date("2040-01-01")} -]); -runTest(true, false, {[timeFieldName]: {$gte: new Date("1980-01-01")}}, [ - {[timeFieldName]: new Date("1980-01-01")}, - {[timeFieldName]: new Date("1995-01-01")}, - {[timeFieldName]: new Date("2030-01-01")} -]); -runTest(true, true, {[timeFieldName]: {$gte: new Date("1980-01-01")}}, [ - {[timeFieldName]: new Date("1980-01-01")}, - {[timeFieldName]: new Date("1995-01-01")}, - {[timeFieldName]: new Date("2040-01-01")} -]); - -// Verify ranges that straddle the lower and upper epoch boundaries work properly. -runTest( - true, false, {[timeFieldName]: {$gt: new Date("1920-01-01"), $lt: new Date("1980-01-01")}}, [ - {[timeFieldName]: new Date("1965-01-01")}, - {[timeFieldName]: new Date("1975-01-01")}, - ]); -runTest( - false, true, {[timeFieldName]: {$gt: new Date("1980-01-01"), $lt: new Date("2050-01-01")}}, [ - {[timeFieldName]: new Date("1995-01-01")}, - {[timeFieldName]: new Date("2040-01-01")}, - ]); -runTest( - false, false, {[timeFieldName]: {$gt: new Date("1920-01-01"), $lt: new Date("1980-01-01")}}, [ - {[timeFieldName]: new Date("1971-01-01")}, - {[timeFieldName]: new Date("1975-01-01")}, - ]); -runTest( - false, false, {[timeFieldName]: {$gt: new Date("1980-01-01"), $lt: new Date("2050-01-01")}}, [ - {[timeFieldName]: new Date("1995-01-01")}, - {[timeFieldName]: new Date("2030-01-01")}, - ]); -})(); diff --git a/jstests/core/timeseries/timeseries_geonear_lookup.js b/jstests/core/timeseries/timeseries_geonear_lookup.js deleted file mode 100644 index 78f68eb014e..00000000000 --- a/jstests/core/timeseries/timeseries_geonear_lookup.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Test that time-series works as expected with $geoNear within a $lookup. - * - * @tags: [ - * does_not_support_transactions, - * requires_pipeline_optimization, - * # We need a timeseries collection. - * requires_timeseries, - * references_foreign_collection, - * ] - */ - -// create a timeseries collection -const timeFieldName = "time"; -const metaFieldName = "tags"; -const testDB = db; - -const tsColl = db.getCollection("ts_coll"); - -const kMaxDistance = Math.PI * 2.0; - -tsColl.drop(); -assert.commandWorked(testDB.createCollection( - tsColl.getName(), {timeseries: {timeField: timeFieldName, metaField: metaFieldName}})); - -assert.commandWorked(tsColl.createIndex({'tags.loc': '2dsphere'})); - -tsColl.insert({time: ISODate(), tags: {loc: [40, 40], descr: 0}, value: 0}); - -const coll2 = db.getCollection("store_min_max_values"); -coll2.drop(); -assert.commandWorked(coll2.insert({_id: 0, minimumDist: 0.0, maximumDist: kMaxDistance})); - -coll2.aggregate([{$lookup: {from: tsColl.getName(), -let: {minVal: "$minimumDist",maxVal:"$maximumDist"}, -pipeline: [ - {$geoNear: {near: {type: "Point", coordinates: [0, 0]}, - key: 'tags.loc', - distanceField: "tags.distance"}}], - as: 'output'}}]); diff --git a/jstests/core/timeseries/timeseries_geonear_measurements.js b/jstests/core/timeseries/timeseries_geonear_measurements.js index abbf7db9596..5c5427a02d1 100644 --- a/jstests/core/timeseries/timeseries_geonear_measurements.js +++ b/jstests/core/timeseries/timeseries_geonear_measurements.js @@ -23,10 +23,10 @@ (function() { "use strict"; +load("jstests/core/timeseries/libs/timeseries.js"); load("jstests/libs/analyze_plan.js"); -load("jstests/libs/feature_flag_util.js"); -if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { +if (!TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { jsTestLog( "Skipped test as the featureFlagTimeseriesMetricIndexes feature flag is not enabled."); return; diff --git a/jstests/core/timeseries/timeseries_graph_lookup.js b/jstests/core/timeseries/timeseries_graph_lookup.js index 25f47aa2cef..7b152cc6b1f 100644 --- a/jstests/core/timeseries/timeseries_graph_lookup.js +++ b/jstests/core/timeseries/timeseries_graph_lookup.js @@ -5,7 +5,6 @@ * does_not_support_transactions, * requires_timeseries, * requires_fcv_51, - * references_foreign_collection, * ] */ (function() { diff --git a/jstests/core/timeseries/timeseries_groupby_reorder.js b/jstests/core/timeseries/timeseries_groupby_reorder.js deleted file mode 100644 index 774d867d3f6..00000000000 --- a/jstests/core/timeseries/timeseries_groupby_reorder.js +++ /dev/null @@ -1,233 +0,0 @@ -/** - * Test the behavior of $group on time-series collections. Specifically, we are targeting rewrites - * that replace bucket unpacking with $group over the buckets collection. Currently, only $min/$max - * are supported for the rewrites. - * - * @tags: [ - * directly_against_shardsvrs_incompatible, - * does_not_support_stepdowns, - * does_not_support_transactions, - * requires_fcv_60, - * ] - */ -(function() { -"use strict"; - -load("jstests/libs/fixture_helpers.js"); -load("jstests/core/timeseries/libs/timeseries.js"); - -const coll = db.timeseries_groupby_reorder; -coll.drop(); - -// We will only check correctness of the results here as checking the plan in JSTests is brittle and -// is better done in document_source_internal_unpack_bucket_test/group_reorder_test.cpp. For the -// cases when the re-write isn't applicable, the used datasets should yield wrong result if the -// re-write is applied. -function runGroupRewriteTest(docs, pipeline, expectedResults) { - coll.drop(); - db.createCollection(coll.getName(), {timeseries: {metaField: "meta", timeField: "time"}}); - coll.insertMany(docs); - assert.docEq(expectedResults, coll.aggregate(pipeline).toArray(), () => { - return `Pipeline: ${tojson(pipeline)}. Explain: ${ - tojson(coll.explain().aggregate(pipeline))}`; - }); -} - -// Test with measurement group key -- a rewrite in this situation would be wrong. -(function testNonMetaGroupKey() { - const t = new Date(); - const docs = [ - {time: t, meta: 1, key: 2, val: 1}, // global min - {time: t, meta: 1, key: 1, val: 3}, // min for key = 1 - {time: t, meta: 1, key: 1, val: 5}, // max for key = 1 - {time: t, meta: 1, key: 2, val: 7}, // global max - ]; - runGroupRewriteTest(docs, - [{$group: {_id: "$key", min: {$min: "$val"}}}, {$match: {_id: 1}}], - [{"_id": 1, "min": 3}]); - runGroupRewriteTest(docs, - [{$group: {_id: "$key", max: {$max: "$val"}}}, {$match: {_id: 1}}], - [{"_id": 1, "max": 5}]); -})(); - -// While a group with const group key can be re-written in terms of a group on the buckets, we don't -// currently do it. However, when/if we start doing it, it should work. -(function testConstGroupKey_NoFilter() { - const t = new Date(); - const docs = [ - {time: t, meta: 1, val: 0}, - {time: t, meta: 1, val: 1}, - {time: t, meta: 1, val: 5}, - ]; - runGroupRewriteTest( - docs, [{$group: {_id: null, min: {$min: "$val"}}}], [{"_id": null, "min": 0}]); - runGroupRewriteTest( - docs, [{$group: {_id: null, max: {$max: "$val"}}}], [{"_id": null, "max": 5}]); -})(); - -// With a filter on meta the group re-write would still apply if the group key is const. -(function testConstGroupKey_WithFilterOnMeta() { - const t = new Date(); - const docs = [ - {time: t, meta: 1, val: 0}, - {time: t, meta: 2, val: 1}, - {time: t, meta: 2, val: 3}, - {time: t, meta: 1, val: 5}, - ]; - runGroupRewriteTest(docs, - [{$match: {meta: 2}}, {$group: {_id: null, min: {$min: "$val"}}}], - [{"_id": null, "min": 1}]); - runGroupRewriteTest(docs, - [{$match: {meta: 2}}, {$group: {_id: null, max: {$max: "$val"}}}], - [{"_id": null, "max": 3}]); -})(); - -// In presense of a non-meta filter the group re-write doesn't apply even if the group key is const. -(function testConstGroupKey_WithFilterOnMeasurement() { - const t = new Date(); - const docs = [ - {time: t, meta: 1, val: 0, include: false}, - {time: t, meta: 1, val: 1, include: true}, - {time: t, meta: 1, val: 5, include: false}, - ]; - runGroupRewriteTest(docs, - [{$match: {include: true}}, {$group: {_id: null, min: {$min: "$val"}}}], - [{"_id": null, "min": 1}]); - runGroupRewriteTest(docs, - [{$match: {include: true}}, {$group: {_id: null, max: {$max: "$val"}}}], - [{"_id": null, "max": 1}]); -})(); - -// Test with meta group key. The group re-write applies. -(function testMetaGroupKey_NoFilter() { - const t = new Date(); - const docs = [ - {time: t, meta: 1, val: 5}, - {time: t, meta: 2, val: 4}, - {time: t, meta: 2, val: 3}, - {time: t, meta: 1, val: 1}, - ]; - runGroupRewriteTest(docs, - [{$group: {_id: "$meta", min: {$min: "$val"}}}, {$match: {_id: 2}}], - [{"_id": 2, "min": 3}]); - runGroupRewriteTest(docs, - [{$group: {_id: "$meta", max: {$max: "$val"}}}, {$match: {_id: 2}}], - [{"_id": 2, "max": 4}]); -})(); - -// Test with meta group key preceeded by a filter on the meta key. The re-write still applies. -(function testMetaGroupKey_WithFilterOnMeta() { - const t = new Date(); - const docs = [ - {time: t, meta: 1, val: 5}, - {time: t, meta: 2, val: 4}, - {time: t, meta: 2, val: 3}, - {time: t, meta: 1, val: 1}, - ]; - runGroupRewriteTest(docs, - [{$match: {meta: 2}}, {$group: {_id: "$meta", min: {$min: "$val"}}}], - [{"_id": 2, "min": 3}]); - runGroupRewriteTest(docs, - [{$match: {meta: 2}}, {$group: {_id: "$meta", max: {$max: "$val"}}}], - [{"_id": 2, "max": 4}]); -})(); - -// Test with meta group key preceeded by a filter on a measurement key. The re-write doesn't apply. -(function testMetaGroupKey_WithFilterOnMeasurement() { - const t = new Date(); - const docs = [ - {time: t, meta: 1, val: 3, include: false}, - {time: t, meta: 1, val: 4, include: true}, - {time: t, meta: 1, val: 5, include: false}, - ]; - runGroupRewriteTest(docs, - [{$match: {include: true}}, {$group: {_id: "$meta", min: {$min: "$val"}}}], - [{"_id": 1, "min": 4}]); - runGroupRewriteTest(docs, - [{$match: {include: true}}, {$group: {_id: "$meta", max: {$max: "$val"}}}], - [{"_id": 1, "max": 4}]); -})(); - -// Test SERVER-73822 fix: complex $min and $max (i.e. not just straight field refs) work correctly. -(function testMetaGroupKey_WithNonPathExpressionUnderMinMax() { - const t = new Date(); - // min(a+b) != min(a) + min(b) and max(a+b) != max(a) + max(b) - const docs = [ - {time: t, meta: 1, a: 1, b: 20}, // max(a + b) - {time: t, meta: 1, a: 2, b: 10}, - {time: t, meta: 1, a: 3, b: 1}, // min(a + b) - ]; - runGroupRewriteTest(docs, - [{$group: {_id: "$meta", min: {$min: {$add: ["$a", "$b"]}}}}], - [{"_id": 1, "min": 4}]); - runGroupRewriteTest(docs, - [{$group: {_id: "$meta", max: {$max: {$add: ["$a", "$b"]}}}}], - [{"_id": 1, "max": 21}]); -})(); - -// Test with meta group key and a non-min/max accumulator that doesn't use any fields. The buckets -// still have to be unpacked because we don't know the number of events in uncompressed buckets. -(function testMetaGroupKey_WithAccumulatorNotUsingAnyFields() { - const t = new Date(); - const docs = [ - {time: t, meta: 1, val: 3}, - {time: t, meta: 3, val: 4}, - {time: t, meta: 1, val: 5}, - ]; - runGroupRewriteTest( - docs, [{$group: {_id: "$meta", x: {$sum: 1}}}, {$match: {_id: 1}}], [{"_id": 1, "x": 2}]); -})(); - -// Test with meta group key and a non-min/max accumulator that uses only the meta field. This query -// is _not_ eligible for the re-write w/o bucket unpacking because while it doesn't depend on any -// fields of the individual events it still depends on the number of events in a bucket. -(function testMetaGroupKey_WithNonMinMaxAccumulatorOnMeta() { - const t = new Date(); - const docs = [ - {time: t, meta: 1, val: 3}, - {time: t, meta: 3, val: 4}, - {time: t, meta: 1, val: 5}, - ]; - runGroupRewriteTest(docs, - [{$group: {_id: "$meta", x: {$sum: "$meta"}}}, {$match: {_id: 1}}], - [{"_id": 1, "x": 2}]); -})(); - -// In presence of a filter $min and $max on the meta field cannot be re-written because a filter -// might end up selecting nothing in buckets with a particular meta. -(function testMetaGroupKey_WithAccumulatorOnMeta_WithFilterOnMeasurement() { - const t = new Date(); - const docs = [ - {time: t, meta: 1, include: false}, - ]; - runGroupRewriteTest( - docs, [{$match: {include: true}}, {$group: {_id: "$meta", x: {$min: "$meta"}}}], []); -})(); - -// Test min/max on the time field (cannot rewrite $min because the control.time.min is rounded -// down). -(function testMetaGroupKey_WithMinMaxOnTime() { - const docs = [ - {time: ISODate("2023-07-20T23:16:47.683Z"), meta: 1}, - ]; - runGroupRewriteTest(docs, - [{$group: {_id: "$meta", min: {$min: "$time"}}}], - [{_id: 1, min: ISODate("2023-07-20T23:16:47.683Z")}]); -})(); - -// Test a group key which is an object with single field path. Ensure that the output perseveres the -// user requested object structure for _id field. -(function testMetaGroupKey_ObjectWithSingleFieldPathElement() { - const t = new Date(); - const docs = [ - {time: t, meta: {a: 1}, val: 5}, - {time: t, meta: {a: 2}, val: 4}, - {time: t, meta: {a: 2}, val: 3}, - {time: t, meta: {a: 1}, val: 1}, - ]; - runGroupRewriteTest( - docs, - [{$group: {_id: {d: "$meta.a"}, min: {$min: "$val"}}}, {$match: {_id: {d: 2}}}], - [{"_id": {d: 2}, "min": 3}]); -})(); -})(); diff --git a/jstests/core/timeseries/timeseries_index.js b/jstests/core/timeseries/timeseries_index.js index 9547362d105..bef86453999 100644 --- a/jstests/core/timeseries/timeseries_index.js +++ b/jstests/core/timeseries/timeseries_index.js @@ -11,9 +11,8 @@ (function() { "use strict"; -load("jstests/core/timeseries/libs/timeseries.js"); -load("jstests/libs/feature_flag_util.js"); load("jstests/libs/fixture_helpers.js"); +load("jstests/core/timeseries/libs/timeseries.js"); TimeseriesTest.run((insert) => { const collNamePrefix = 'timeseries_index_'; @@ -226,7 +225,7 @@ TimeseriesTest.run((insert) => { runTest({[metaFieldName + '.location']: "2d", [metaFieldName + '.tag1']: -1}, {'meta.location': "2d", 'meta.tag1': -1}); - if (FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { + if (TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { // Measurement 2dsphere index runTest({'loc': '2dsphere'}, {'data.loc': '2dsphere_bucket'}); } @@ -243,7 +242,7 @@ TimeseriesTest.run((insert) => { coll.getName(), {timeseries: {timeField: timeFieldName, metaField: metaFieldName}})); assert.commandWorked(insert(coll, doc), 'failed to insert doc: ' + tojson(doc)); - if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { + if (!TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { // Reject index keys that do not include the metadata field. assert.commandFailedWithCode(coll.createIndex({not_metadata: 1}), ErrorCodes.CannotCreateIndex); @@ -266,7 +265,7 @@ TimeseriesTest.run((insert) => { [ErrorCodes.CannotCreateIndex, ErrorCodes.InvalidOptions]); }; - if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { + if (!TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { // Partial indexes are not supported on time-series collections if the time-series metric // feature flag is disabled. testCreateIndexFailed({[metaFieldName]: 1}, {partialFilterExpression: {meta: {$gt: 5}}}); diff --git a/jstests/core/timeseries/timeseries_index_partial.js b/jstests/core/timeseries/timeseries_index_partial.js index 014158c3542..eb2512b1bd7 100644 --- a/jstests/core/timeseries/timeseries_index_partial.js +++ b/jstests/core/timeseries/timeseries_index_partial.js @@ -2,8 +2,6 @@ * Test creating and using partial indexes, on a time-series collection. * * @tags: [ - * # TODO (SERVER-73316): remove - * assumes_against_mongod_not_mongos, * does_not_support_stepdowns, * does_not_support_transactions, * requires_fcv_52, @@ -14,10 +12,10 @@ (function() { "use strict"; +load("jstests/core/timeseries/libs/timeseries.js"); load("jstests/libs/analyze_plan.js"); -load("jstests/libs/feature_flag_util.js"); -if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { +if (!TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { jsTestLog( "Skipped test as the featureFlagTimeseriesMetricIndexes feature flag is not enabled."); return; @@ -80,24 +78,10 @@ assert.commandFailedWithCode(coll.createIndex({a: 1}, {partialFilterExpression: // Test creating and using a partial index. { - let ixscanInWinningPlan = 0; - - // Make sure the {a: 1} index was considered for this query. + // Make sure the query uses the {a: 1} index. function checkPlan(predicate) { const explain = coll.find(predicate).explain(); - let scan = getAggPlanStage(explain, 'IXSCAN'); - // If scan is not present, check rejected plans - if (scan === null) { - const rejectedPlans = getRejectedPlans(getAggPlanStage(explain, "$cursor")["$cursor"]); - if (rejectedPlans.length === 1) { - const scans = getPlanStages(getRejectedPlan(rejectedPlans[0]), "IXSCAN"); - if (scans.length === 1) { - scan = scans[0]; - } - } - } else { - ixscanInWinningPlan++; - } + const scan = getAggPlanStage(explain, 'IXSCAN'); const indexes = buckets.getIndexes(); assert(scan, "Expected an index scan for predicate: " + tojson(predicate) + @@ -106,10 +90,10 @@ assert.commandFailedWithCode(coll.createIndex({a: 1}, {partialFilterExpression: } // Make sure the query results match a collection-scan plan. function checkResults(predicate) { - const result = coll.aggregate([{$match: predicate}], {hint: {a: 1}}).toArray(); + const result = coll.aggregate({$match: predicate}).toArray(); const unindexed = coll.aggregate([{$_internalInhibitOptimization: {}}, {$match: predicate}]).toArray(); - assert.sameMembers(result, unindexed); + assert.docEq(result, unindexed); } function checkPlanAndResults(predicate) { checkPlan(predicate); @@ -149,6 +133,10 @@ assert.commandFailedWithCode(coll.createIndex({a: 1}, {partialFilterExpression: const t1 = ISODate('2000-01-01T00:00:01Z'); const t2 = ISODate('2000-01-01T00:00:02Z'); + // When the collection is sharded, there is an index on time that can win, instead of the + // partial index. So only check the results in that case, not the plan. + const check = FixtureHelpers.isSharded(buckets) ? checkResults : checkPlanAndResults; + assert.commandWorked(coll.dropIndex({a: 1})); assert.commandWorked( coll.createIndex({a: 1}, {partialFilterExpression: {[timeField]: {$lt: t1}}})); @@ -177,7 +165,6 @@ assert.commandFailedWithCode(coll.createIndex({a: 1}, {partialFilterExpression: assert.commandWorked(coll.dropIndex({a: 1})); assert.sameMembers(coll.getIndexes(), extraIndexes); assert.sameMembers(buckets.getIndexes(), extraBucketIndexes); - assert.gt(ixscanInWinningPlan, 0); } // Check that partialFilterExpression can use a mixture of metadata, time, and measurement fields, diff --git a/jstests/core/timeseries/timeseries_index_spec.js b/jstests/core/timeseries/timeseries_index_spec.js index d3822dc642b..f13c64dd80b 100644 --- a/jstests/core/timeseries/timeseries_index_spec.js +++ b/jstests/core/timeseries/timeseries_index_spec.js @@ -16,7 +16,6 @@ "use strict"; load("jstests/core/timeseries/libs/timeseries.js"); -load("jstests/libs/feature_flag_util.js"); TimeseriesTest.run(() => { const collName = "timeseries_index_spec"; @@ -86,7 +85,7 @@ TimeseriesTest.run(() => { {name: "time_meta_field_downgradable"})); verifyAndDropIndex(/*isDowngradeCompatible=*/true, "time_meta_field_downgradable"); - if (FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { + if (TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { assert.commandWorked(coll.createIndex({x: 1}, {name: "x_1"})); verifyAndDropIndex(/*isDowngradeCompatible=*/false, "x_1"); @@ -111,7 +110,7 @@ TimeseriesTest.run(() => { // Creating an index directly on the buckets collection is permitted. However, these types of // index creations will not have an "originalSpec" field and rely on the reverse mapping // mechanism. - if (FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { + if (TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { assert.commandWorked( bucketsColl.createIndex({"control.min.y": 1, "control.max.y": 1}, {name: "y"})); diff --git a/jstests/core/timeseries/timeseries_index_use.js b/jstests/core/timeseries/timeseries_index_use.js index 6ce128ecbbd..8770047f4d2 100644 --- a/jstests/core/timeseries/timeseries_index_use.js +++ b/jstests/core/timeseries/timeseries_index_use.js @@ -50,9 +50,8 @@ const generateTest = (useHint) => { /** * Creates the index specified by the spec and options, then explains the query to ensure - * that the created index is used or was considered by multi-planner. - * Runs the query and verifies that the expected number of documents are matched. - * Finally, deletes the created index. + * that the created index is used. Runs the query and verifies that the expected number of + * documents are matched. Finally, deletes the created index. */ const testQueryUsesIndex = function( filter, numMatches, indexSpec, indexOpts = {}, queryOpts = {}) { @@ -68,23 +67,9 @@ const generateTest = (useHint) => { assert.eq(numMatches, query.itcount()); const explain = query.explain(); - if (useHint) { - const ixscan = getAggPlanStage(explain, "IXSCAN"); - assert.neq(null, ixscan, tojson(explain)); - assert.eq("testIndexName", ixscan.indexName, tojson(ixscan)); - } else { - let ixscan = getAggPlanStage(explain, "IXSCAN"); - // If ixscan is not present, check rejected plans - if (ixscan === null) { - const rejectedPlans = - getRejectedPlans(getAggPlanStage(explain, "$cursor")["$cursor"]); - assert.eq(1, rejectedPlans.length); - const ixscans = getPlanStages(getRejectedPlan(rejectedPlans[0]), "IXSCAN"); - assert.eq(1, ixscans.length); - ixscan = ixscans[0]; - } - assert.eq("testIndexName", ixscan.indexName, tojson(ixscan)); - } + const ixscan = getAggPlanStage(explain, "IXSCAN"); + assert.neq(null, ixscan, tojson(explain)); + assert.eq("testIndexName", ixscan.indexName, tojson(ixscan)); assert.commandWorked(coll.dropIndex("testIndexName")); }; diff --git a/jstests/core/timeseries/timeseries_insert_mixed_schema_bucket.js b/jstests/core/timeseries/timeseries_insert_mixed_schema_bucket.js deleted file mode 100644 index cbce31e9795..00000000000 --- a/jstests/core/timeseries/timeseries_insert_mixed_schema_bucket.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Tests directly inserting a time-series bucket with mixed schema. - * - * @tags: [ - * # $listCatalog does not include the tenant prefix in its results. - * command_not_supported_in_serverless, - * requires_timeseries, - * # $listCatalog not supported inside of a multi-document transaction - * does_not_support_transactions, - * # $listCatalog only exists since v6 - * multiversion_incompatible, - * ] - */ -(function() { -"use strict"; - -load("jstests/core/timeseries/libs/timeseries.js"); // For 'TimeseriesTest'. - -TestData.skipEnforceTimeseriesBucketsAreAlwaysCompressedOnValidate = true; - -const testDB = db.getSiblingDB(jsTestName()); -const collName = "ts"; - -assert.commandWorkedOrFailedWithCode(testDB.runCommand({drop: collName}), - ErrorCodes.NamespaceNotFound); -assert.commandWorked( - testDB.createCollection(collName, {timeseries: {timeField: "t", metaField: "m"}})); -const coll = testDB[collName]; -const bucketsColl = testDB["system.buckets." + collName]; - -const bucket = { - _id: ObjectId("65a6eb806ffc9fa4280ecac4"), - control: { - version: NumberInt(1), - min: { - _id: ObjectId("65a6eba7e6d2e848e08c3750"), - t: ISODate("2024-01-16T20:48:00Z"), - a: 1, - }, - max: { - _id: ObjectId("65a6eba7e6d2e848e08c3751"), - t: ISODate("2024-01-16T20:48:39.448Z"), - a: "a", - }, - }, - meta: 0, - data: { - _id: { - 0: ObjectId("65a6eba7e6d2e848e08c3750"), - 1: ObjectId("65a6eba7e6d2e848e08c3751"), - }, - t: { - 0: ISODate("2024-01-16T20:48:39.448Z"), - 1: ISODate("2024-01-16T20:48:39.448Z"), - }, - a: { - 0: "a", - 1: 1, - }, - } -}; - -assert.commandFailedWithCode(bucketsColl.insert(bucket), - ErrorCodes.CannotInsertTimeseriesBucketsWithMixedSchema); -assert.eq(TimeseriesTest.bucketsMayHaveMixedSchemaData(bucketsColl), false); -assert.commandWorked( - testDB.runCommand({collMod: collName, timeseriesBucketsMayHaveMixedSchemaData: true})); -assert.eq(TimeseriesTest.bucketsMayHaveMixedSchemaData(bucketsColl), true); -assert.commandWorked(bucketsColl.insert(bucket)); -assert.commandWorked(bucketsColl.deleteOne({_id: bucket._id})); -assert.eq(TimeseriesTest.bucketsMayHaveMixedSchemaData(bucketsColl), true); -assert.commandWorked( - testDB.runCommand({collMod: collName, timeseriesBucketsMayHaveMixedSchemaData: false})); -assert.eq(TimeseriesTest.bucketsMayHaveMixedSchemaData(bucketsColl), false); -})(); diff --git a/jstests/core/timeseries/timeseries_internal_bucket_geo_within.js b/jstests/core/timeseries/timeseries_internal_bucket_geo_within.js index 2e350ebdbb0..9c237d8dd77 100644 --- a/jstests/core/timeseries/timeseries_internal_bucket_geo_within.js +++ b/jstests/core/timeseries/timeseries_internal_bucket_geo_within.js @@ -4,7 +4,7 @@ * collection. * * @tags: [ - * requires_fcv_60, + * requires_fcv_51, * requires_pipeline_optimization, * requires_timeseries, * does_not_support_stepdowns, @@ -47,6 +47,10 @@ for (let collScanStage of collScanStages) { "field": "loc" } }; + // TODO SERVER-60373 Fix duplicate predicates for sharded time-series collection + if (FixtureHelpers.isSharded(bucketsColl)) { + expectedPredicate = {$and: [expectedPredicate, expectedPredicate]}; + } assert.docEq(expectedPredicate, collScanStage.filter, collScanStages); } diff --git a/jstests/core/timeseries/timeseries_lastpoint_top.js b/jstests/core/timeseries/timeseries_lastpoint_top.js index c85f48fb076..2efd352b574 100644 --- a/jstests/core/timeseries/timeseries_lastpoint_top.js +++ b/jstests/core/timeseries/timeseries_lastpoint_top.js @@ -27,7 +27,6 @@ assert.commandWorked(testDB.dropDatabase()); // Do not run the rest of the tests if the lastpoint optimization is disabled. if (!FeatureFlagUtil.isEnabled(db, "LastPointQuery")) { - jsTestLog("Skipping the test."); return; } diff --git a/jstests/core/timeseries/timeseries_lookup.js b/jstests/core/timeseries/timeseries_lookup.js index 6d559e9356d..c173764a591 100644 --- a/jstests/core/timeseries/timeseries_lookup.js +++ b/jstests/core/timeseries/timeseries_lookup.js @@ -5,13 +5,11 @@ * does_not_support_transactions, * requires_timeseries, * requires_fcv_51, - * references_foreign_collection, * ] */ (function() { "use strict"; -load("jstests/aggregation/extras/utils.js"); load("jstests/core/timeseries/libs/timeseries.js"); TimeseriesTest.run((insert) => { @@ -26,44 +24,43 @@ TimeseriesTest.run((insert) => { Random.setRandomSeed(); const hosts = TimeseriesTest.generateHosts(numHosts); - let - testFunc = function(collAOption, collBOption) { - // Prepare two time-series collections. - const collA = testDB.getCollection("a"); - const collB = testDB.getCollection("b"); - collA.drop(); - collB.drop(); - assert.commandWorked(testDB.createCollection(collA.getName(), collAOption)); - assert.commandWorked(testDB.createCollection(collB.getName(), collBOption)); - - let entryCountPerHost = new Array(numHosts).fill(0); - - // Insert into collA, one entry per host. - for (let i = 0; i < numHosts; i++) { - let host = hosts[i]; - assert.commandWorked(insert(collA, { - measurement: "cpu", - time: ISODate(), - tags: host.tags, - })); - } - - // Insert some random documents to collB. - for (let i = 0; i < numDocs; i++) { - let host = TimeseriesTest.getRandomElem(hosts); - assert.commandWorked(insert(collB, { - measurement: "cpu", - time: ISODate(), - tags: host.tags, - })); - // Here we extract the hostId from "host.tags.hostname". It is expected that the - // "host.tags.hostname" is in the form of 'host_<hostNum>'. - entryCountPerHost[parseInt( - host.tags.hostname.substring(5, host.tags.hostname.length))]++; - } - - // Equality Match - let results = collA.aggregate([ + let testFunc = function(collAOption, collBOption) { + // Prepare two time-series collections. + const collA = testDB.getCollection("a"); + const collB = testDB.getCollection("b"); + collA.drop(); + collB.drop(); + assert.commandWorked(testDB.createCollection(collA.getName(), collAOption)); + assert.commandWorked(testDB.createCollection(collB.getName(), collBOption)); + + let entryCountPerHost = new Array(numHosts).fill(0); + + // Insert into collA, one entry per host. + for (let i = 0; i < numHosts; i++) { + let host = hosts[i]; + assert.commandWorked(insert(collA, { + measurement: "cpu", + time: ISODate(), + tags: host.tags, + })); + } + + // Insert some random documents to collB. + for (let i = 0; i < numDocs; i++) { + let host = TimeseriesTest.getRandomElem(hosts); + assert.commandWorked(insert(collB, { + measurement: "cpu", + time: ISODate(), + tags: host.tags, + })); + // Here we extract the hostId from "host.tags.hostname". It is expected that the + // "host.tags.hostname" is in the form of 'host_<hostNum>'. + entryCountPerHost[parseInt( + host.tags.hostname.substring(5, host.tags.hostname.length))]++; + } + + // Equality Match + let results = collA.aggregate([ { $lookup: { from: collB.getName(), @@ -82,106 +79,13 @@ TimeseriesTest.run((insert) => { }, {$sort: {host: 1}} ]).toArray(); - assert.eq(numHosts, results.length, results); - for (let i = 0; i < numHosts; i++) { - assert.eq({host: "host_" + i, matchedB: entryCountPerHost[i]}, results[i], results); - } + assert.eq(numHosts, results.length, results); + for (let i = 0; i < numHosts; i++) { + assert.eq({host: "host_" + i, matchedB: entryCountPerHost[i]}, results[i], results); + } - // Equality Match With Let (uncorrelated) - // Make sure injected $sequentialDocumentCache (right after unpack bucket stage) - // in the inner pipeline is removed. - results = collA.aggregate([ - { - $lookup: { - from: collB.getName(), - let: {"outer_hostname": "$tags.hostname"}, - pipeline: [ - // $match will be pushed before unpack bucket stage - {$match: {$expr: {$eq: ["$$outer_hostname", hosts[0].tags.hostname]}}}, - ], - as: "matchedB" - } - }, { - $project: { - _id: 0, - host: "$tags.hostname", - matchedB: { - $size: "$matchedB" - } - } - }, - {$sort: {host: 1}} - ]).toArray(); - assert.eq(numHosts, results.length, results); - for (let i = 0; i < numHosts; i++) { - const matched = i === 0 ? numDocs : 0; - assert.eq({host: "host_" + i, matchedB: matched}, results[i], results); - } - - // Equality Match With Let (uncorrelated) - // Make sure injected $sequentialDocumentCache in the inner pipeline is removed. - // $sequentialDocumentCache is not located right after unpack bucket stage. - results = collA.aggregate([ - { - $lookup: { - from: collB.getName(), - let: {"outer_hostname": "$tags.hostname"}, - pipeline: [ - {$match: {$expr: {$eq: ["$$outer_hostname", hosts[0].tags.hostname]}}}, - {$set: {foo: {$const: 123}}}, // uncorrelated - ], - as: "matchedB" - } - }, { - $project: { - _id: 0, - host: "$tags.hostname", - matchedB: { - $size: "$matchedB" - } - } - }, - {$sort: {host: 1}} - ]).toArray(); - assert.eq(numHosts, results.length, results); - for (let i = 0; i < numHosts; i++) { - const matched = i === 0 ? numDocs : 0; - assert.eq({host: "host_" + i, matchedB: matched}, results[i], results); - } - - // Equality Match With Let (correlated, no $match re-order) - // Make sure injected $sequentialDocumentCache in the inner pipeline is removed. - // $sequentialDocumentCache is located at the very end of pipeline. - results = collA.aggregate([ - { - $lookup: { - from: collB.getName(), - let: {"outer_hostname": "$tags.hostname"}, - pipeline: [ - {$match: {$expr: {$eq: ["$$outer_hostname", hosts[0].tags.hostname]}}}, - {$set: {foo: "$$outer_hostname"}}, // correlated - ], - as: "matchedB" - } - }, { - $project: { - _id: 0, - host: "$tags.hostname", - matchedB: { - $size: "$matchedB" - } - } - }, - {$sort: {host: 1}} - ]).toArray(); - assert.eq(numHosts, results.length, results); - for (let i = 0; i < numHosts; i++) { - const matched = i === 0 ? numDocs : 0; - assert.eq({host: "host_" + i, matchedB: matched}, results[i], results); - } - - // Unequal joins - results = collA.aggregate([ + // Unequal joins + results = collA.aggregate([ { $lookup: { from: collB.getName(), @@ -210,28 +114,13 @@ TimeseriesTest.run((insert) => { }, {$sort: {host: 1}} ]).toArray(); - assert.eq(numHosts, results.length, results); - let expectedCount = 0; - for (let i = 0; i < numHosts; i++) { - expectedCount += entryCountPerHost[i]; - assert.eq( - {host: "host_" + i, matchedB: expectedCount}, results[i], entryCountPerHost); - } - - // $sequenceDocumentsCache might optimize out $internalUnpackBucket and cause a - // crash on such query. - results = collA.aggregate([ - {$lookup: { - from: collB.getName(), - as: 'docs', - let: {}, - pipeline: [ - {$sort: {_id: 1}} - ] - }} - ]).toArray(); - assert.eq(numHosts, results.length, results); - }; + assert.eq(numHosts, results.length, results); + let expectedCount = 0; + for (let i = 0; i < numHosts; i++) { + expectedCount += entryCountPerHost[i]; + assert.eq({host: "host_" + i, matchedB: expectedCount}, results[i], entryCountPerHost); + } + }; // Exhaust the combinations of non-time-series and time-series collections for inner and outer // $lookup collections. @@ -247,91 +136,3 @@ TimeseriesTest.run((insert) => { }); }); })(); - -{ - // Test that we get the right results for $lookup on a timeseries collection with an internal - // pipeline containing both correlated and uncorrelated $match stages. Ensures we do not cache - // the results of this internal pipeline incorrectly - // (src/mongo/db/pipeline/document_source_sequential_document_cache.h) regardless of the order - // of the $match stages. - const testDB = db.getSiblingDB(jsTestName()); - testDB.local.insertMany([{_id: 0, key: 1}, {_id: 1, key: 2}]); - testDB.createCollection("foreign", {timeseries: {timeField: "time", metaField: "meta"}}); - testDB.foreign.insertMany([ - {time: new Date(), _id: 0, meta: 1, val1: 42, val2: 100}, - {time: new Date(), _id: 2, meta: 2, val1: 17, val2: 100} - ]); - - // The $sequentialCache (document_source_sequential_document_cache.h) decides whether or not it - // can cache the results of the internal lookup pipeline based on if the pipeline is correlated - // or uncorrelated. With timeseries, many rewrites occur which can merge $match stages together, - // have them pushed into the $_internalUnpackBucket stage as an eventFilter, or push them in - // front of the - // $_internalUnpackBucket stage. We need to make sure that the ability of the cache to recognize - // when there is a correlated $match in the pipeline to remain regardless of these rewrites. - (function testUncorrelatedFollowedByCorrelatedMatch() { - const lookupStage = {$lookup: { - from: "foreign", - let: {lkey: "$key"}, - pipeline: [ - {$match: {$expr: {$lt: ["$val1","$val2"]}}}, - {$match: {$expr: {$eq: ["$meta","$$lkey"]}}}, - {$project: {lkey: "$$lkey",fkey: "$meta",val: "$val1",_id: 0,}}, - ], - as: "joined" - }}; - const result = testDB.local.aggregate(lookupStage); - assertArrayEq({ - actual: result.toArray(), - expected: [ - {"_id": 1, "key": 2, "joined": [{"lkey": 2, "fkey": 2, "val": 17}]}, - {"_id": 0, "key": 1, "joined": [{"lkey": 1, "fkey": 1, "val": 42}]} - ], - extraErrorMsg: `Unexpected result of running pipeline ${tojson(lookupStage)}` - }); - })(); - - (function testCorrelatedFollowedByUncorrelatedMatch() { - const lookupStage = {$lookup: { - from: "foreign", - let: {lkey: "$key"}, - pipeline: [ - {$match: {$expr: {$eq: ["$meta","$$lkey"]}}}, - {$match: {$expr: {$lt: ["$val1","$val2"]}}}, - {$project: {lkey: "$$lkey",fkey: "$meta",val: "$val1",_id: 0,}}, - ], - as: "joined" - }}; - const result = testDB.local.aggregate(lookupStage); - assertArrayEq({ - actual: result.toArray(), - expected: [ - {"_id": 1, "key": 2, "joined": [{"lkey": 2, "fkey": 2, "val": 17}]}, - {"_id": 0, "key": 1, "joined": [{"lkey": 1, "fkey": 1, "val": 42}]} - ], - extraErrorMsg: `Unexpected result of running pipeline ${tojson(lookupStage)}` - }); - })(); - - (function testUncorrelatedFollowedByUncorrelatedMatch() { - const lookupStage = {$lookup: { - from: "foreign", - let: {lkey: "$key"}, - pipeline: [ - {$match: {$expr: {$lt: ["$meta","$val1"]}}}, - {$match: {$expr: {$lt: ["$val1","$val2"]}}}, - {$project: {meta: "$meta",val: "$val1",_id: 0,}}, - ], - as: "joined" - }}; - const result = testDB.local.aggregate(lookupStage); - assertArrayEq({ - actual: result.toArray(), - expected: [ - {"_id": 0, "key": 1, "joined": [{"meta": 1, "val": 42}, {"meta": 2, "val": 17}]}, - {"_id": 1, "key": 2, "joined": [{"meta": 1, "val": 42}, {"meta": 2, "val": 17}]} - ], - extraErrorMsg: `Unexpected result of running pipeline ${tojson(lookupStage)}` - }); - })(); -} diff --git a/jstests/core/timeseries/timeseries_match_pushdown.js b/jstests/core/timeseries/timeseries_match_pushdown.js deleted file mode 100644 index 93c841da9ec..00000000000 --- a/jstests/core/timeseries/timeseries_match_pushdown.js +++ /dev/null @@ -1,634 +0,0 @@ -/** - * Tests that the $match stage followed by unpacking stage has been pushed down with correct - * predicates. - * - * @tags: [ - * requires_timeseries, - * requires_fcv_60, - * does_not_support_stepdowns, - * does_not_support_transactions, - * directly_against_shardsvrs_incompatible, - * ] - */ -(function() { -"use strict"; - -load("jstests/libs/analyze_plan.js"); // For getAggPlanStages - -const coll = db.timeseries_match_pushdown; -const timeField = 'time'; -const metaField = 'meta'; -const measureField = 'a'; - -// The docs and queries are designed so that some buckets match the query entirely, some buckets -// match the query partially, and some with no matches. -const defaultDocs = [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:07'), [measureField]: null, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: [1, 2, 3], [metaField]: 2} -]; - -/** - * Setup the collection and run a $match query with the specified 'eventFilter' or a 'pipeline'. - * Assert the 'wholeBucketFilter' is attached correctly to the unpacking stage, and has the expected - * result 'expectedDocs'. - */ -const runTest = function({docsToInsert, pipeline, eventFilter, wholeBucketFilter, expectedDocs}) { - // Set up the collection. Each test will have it's own collection setup. - coll.drop(); - assert.commandWorked(db.createCollection(coll.getName(), {timeseries: {timeField, metaField}})); - // Insert documents into the collection. - if (!docsToInsert) { - docsToInsert = defaultDocs; - } - assert.commandWorked(coll.insert(docsToInsert)); - - if (!pipeline) { - pipeline = [{$match: eventFilter}]; - } - const explain = assert.commandWorked(coll.explain().aggregate(pipeline)); - const unpackStages = getAggPlanStages(explain, '$_internalUnpackBucket'); - assert.eq(1, - unpackStages.length, - "Should only have a single $_internalUnpackBucket stage: " + tojson(explain)); - const unpackStage = unpackStages[0].$_internalUnpackBucket; - assert.docEq(unpackStage.eventFilter, eventFilter, "Incorrect eventFilter: " + tojson(explain)); - if (wholeBucketFilter) { - assert.docEq(unpackStage.wholeBucketFilter, - wholeBucketFilter, - "Incorrect wholeBucketFilter: " + tojson(explain)); - } else { - assert(!unpackStage.wholeBucketFilter, "Incorrect wholeBucketFilter: " + tojson(explain)); - } - - const docs = coll.aggregate([...pipeline, {$sort: {time: 1}}]).toArray(); - assert.eq(docs.length, expectedDocs.length, "Incorrect docs: " + tojson(docs)); - docs.forEach((doc, i) => { - // Do not need to check document _id, since checking time is already unique. - delete doc._id; - assert.docEq(doc, expectedDocs[i], "Incorrect docs: " + tojson(docs)); - }); -}; - -const aTime = ISODate('2022-01-01T00:00:03'); -const bTime = ISODate('2022-01-01T00:00:07'); -const bMeta = 3; -const aMeasure = 3; -const minTimeField = `control.min.${timeField}`; -const maxTimeField = `control.max.${timeField}`; - -// $gt on time -runTest({ - eventFilter: {[timeField]: {$gt: aTime}}, - wholeBucketFilter: {[minTimeField]: {$gt: aTime}}, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:07'), [measureField]: null, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: [1, 2, 3], [metaField]: 2}, - ], -}); - -// $gt on measurement -runTest({ - eventFilter: {[measureField]: {$gt: aMeasure}}, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - ], -}); - -// $gt in $expr on time -runTest({ - pipeline: [{$match: {$expr: {$gt: [`$${timeField}`, {$const: aTime}]}}}], - eventFilter: { - $and: [ - {[timeField]: {$_internalExprGt: aTime}}, - {$expr: {$gt: [`$${timeField}`, {$const: aTime}]}}, - ] - }, - wholeBucketFilter: { - $and: [ - {[minTimeField]: {$_internalExprGt: aTime}}, - {[minTimeField]: {$_internalExprGt: aTime}}, - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:07'), [measureField]: null, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: [1, 2, 3], [metaField]: 2}, - ], -}); - -// $gte on time -runTest({ - eventFilter: {[timeField]: {$gte: aTime}}, - wholeBucketFilter: {[minTimeField]: {$gte: aTime}}, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:07'), [measureField]: null, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: [1, 2, 3], [metaField]: 2} - ], -}); - -// $gte on measurement -runTest({ - eventFilter: {[measureField]: {$gte: aMeasure}}, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: [1, 2, 3], [metaField]: 2} - ], -}); - -// $gte in $expr on time -runTest({ - pipeline: [{$match: {$expr: {$gte: [`$${timeField}`, {$const: aTime}]}}}], - eventFilter: { - $and: [ - {[timeField]: {$_internalExprGte: aTime}}, - {$expr: {$gte: [`$${timeField}`, {$const: aTime}]}}, - ] - }, - wholeBucketFilter: { - $and: [ - {[minTimeField]: {$_internalExprGte: aTime}}, - {[minTimeField]: {$_internalExprGte: aTime}}, - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:07'), [measureField]: null, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: [1, 2, 3], [metaField]: 2} - ], -}); - -// $lt on time -runTest({ - eventFilter: {[timeField]: {$lt: aTime}}, - wholeBucketFilter: {[maxTimeField]: {$lt: aTime}}, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - ], -}); - -// $lt on measurement -runTest({ - eventFilter: {[measureField]: {$lt: aMeasure}}, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: [1, 2, 3], [metaField]: 2} - ], -}); - -// $lt in $expr on time -runTest({ - pipeline: [{$match: {$expr: {$lt: [`$${timeField}`, {$const: aTime}]}}}], - eventFilter: { - $and: [ - {[timeField]: {$_internalExprLt: aTime}}, - {$expr: {$lt: [`$${timeField}`, {$const: aTime}]}}, - ] - }, - wholeBucketFilter: { - $and: [ - {[maxTimeField]: {$_internalExprLt: aTime}}, - {[maxTimeField]: {$_internalExprLt: aTime}}, - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - ], -}); - -// $lte on time -runTest({ - eventFilter: {[timeField]: {$lte: aTime}}, - wholeBucketFilter: {[maxTimeField]: {$lte: aTime}}, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - ], -}); - -// $lte in $expr on time -runTest({ - pipeline: [{$match: {$expr: {$lte: [`$${timeField}`, {$const: aTime}]}}}], - eventFilter: { - $and: [ - {[timeField]: {$_internalExprLte: aTime}}, - {$expr: {$lte: [`$${timeField}`, {$const: aTime}]}}, - ] - }, - wholeBucketFilter: { - $and: [ - {[maxTimeField]: {$_internalExprLte: aTime}}, - {[maxTimeField]: {$_internalExprLte: aTime}}, - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - ], -}); - -// $lte on measurement -runTest({ - eventFilter: {[measureField]: {$lte: aMeasure}}, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: [1, 2, 3], [metaField]: 2} - ], -}); - -// $eq on time -runTest({ - eventFilter: {[timeField]: {$eq: aTime}}, - wholeBucketFilter: {$and: [{[minTimeField]: {$eq: aTime}}, {[maxTimeField]: {$eq: aTime}}]}, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - ], -}); - -// $eq in $expr on time -runTest({ - pipeline: [{$match: {$expr: {$eq: [`$${timeField}`, {$const: aTime}]}}}], - eventFilter: { - $and: [ - {[timeField]: {$_internalExprEq: aTime}}, - {$expr: {$eq: [`$${timeField}`, {$const: aTime}]}}, - ] - }, - wholeBucketFilter: { - $and: [ - {[minTimeField]: {$_internalExprEq: aTime}}, - {[maxTimeField]: {$_internalExprEq: aTime}}, - {[minTimeField]: {$_internalExprEq: aTime}}, - {[maxTimeField]: {$_internalExprEq: aTime}}, - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - ], -}); - -// $eq on measurement -runTest({ - docsToInsert: [ - ...defaultDocs, - {[timeField]: ISODate('2022-01-01T00:00:06'), [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: null, [metaField]: 1} - ], - eventFilter: {[measureField]: {$eq: aMeasure}}, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: [1, 2, 3], [metaField]: 2} - ], -}); - -// $and on time -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 3}, - {[timeField]: ISODate('2022-01-01T00:00:07'), [measureField]: 7, [metaField]: 3}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: 8, [metaField]: 3}, - ], - eventFilter: {$and: [{[timeField]: {$gt: aTime}}, {[timeField]: {$lt: bTime}}]}, - wholeBucketFilter: { - $and: [ - {[minTimeField]: {$gt: aTime}}, - {[maxTimeField]: {$lt: bTime}}, - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 3}, - ], -}); - -//$or on time -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 3}, - {[timeField]: ISODate('2022-01-01T00:00:07'), [measureField]: 7, [metaField]: 3}, - ], - eventFilter: {$or: [{[timeField]: {$lte: aTime}}, {[timeField]: {$gte: bTime}}]}, - wholeBucketFilter: { - $or: [ - {[maxTimeField]: {$lte: aTime}}, - {[minTimeField]: {$gte: bTime}}, - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:07'), [measureField]: 7, [metaField]: 3}, - ], -}); - -// $match on time and meta -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 3}, - {[timeField]: ISODate('2022-01-01T00:00:07'), [measureField]: 7, [metaField]: 3}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: 8, [metaField]: 3}, - ], - pipeline: [{$match: {$and: [{[timeField]: {$gt: aTime}}, {[metaField]: {$lte: bMeta}}]}}], - eventFilter: {[timeField]: {$gt: aTime}}, - wholeBucketFilter: { - [minTimeField]: {$gt: aTime}, - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 3}, - {[timeField]: ISODate('2022-01-01T00:00:07'), [measureField]: 7, [metaField]: 3}, - {[timeField]: ISODate('2022-01-01T00:00:08'), [measureField]: 8, [metaField]: 3}, - ], -}); - -// $match on time and meta inside $expr. There should not be a wholeBucketFilter, since the entire -// $and expression cannot be rewritten as a MatchExpression, and for $expr predicates we only -// generate a wholeBucketFilter for single predicates on the timeField. -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:03'), [metaField]: 1, [measureField]: 3}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [metaField]: 1, [measureField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [metaField]: 2, [measureField]: 3}, - ], - pipeline: [{ - $match: { - $expr: { - $and: - [{$eq: [`$${metaField}`, `$${measureField}`]}, {$gt: [`$${timeField}`, aTime]}] - } - } - }], - eventFilter: { - $and: [ - {[timeField]: {$_internalExprGt: aTime}}, - { - $expr: { - $and: [ - {$eq: [`$${metaField}`, `$${measureField}`]}, - {$gt: [`$${timeField}`, {$const: aTime}]} - ] - } - }, - ] - }, - expectedDocs: [{[timeField]: ISODate('2022-01-01T00:00:04'), [metaField]: 1, [measureField]: 1}] -}); - -// $match on time and meta inside $expr. The entire $and expression can be rewritten into a -// MatchExpression. However, for $expr predicates we only generate a wholeBucketFilter for single -// predicates on the timeField. -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:03'), [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [metaField]: 2}, - ], - pipeline: - [{$match: {$expr: {$and: [{$eq: [`$${metaField}`, 1]}, {$gt: [`$${timeField}`, aTime]}]}}}], - eventFilter: { - $and: [ - {[timeField]: {$_internalExprGt: aTime}}, - { - $expr: { - $and: [ - {$eq: [`$${metaField}`, {$const: 1}]}, - {$gt: [`$${timeField}`, {$const: aTime}]} - ] - } - }, - {[metaField]: {$_internalExprEq: 1}}, - {[timeField]: {$_internalExprGt: aTime}}, - ] - }, - expectedDocs: [{[timeField]: ISODate('2022-01-01T00:00:04'), [metaField]: 1}] -}); - -// $match on time or meta -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 3}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 4}, - ], - eventFilter: {$or: [{[timeField]: {$lte: aTime}}, {[metaField]: {$gt: bMeta}}]}, - wholeBucketFilter: { - $or: [ - {[maxTimeField]: {$lte: aTime}}, - {[metaField]: {$gt: bMeta}}, - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 4}, - ], -}); - -// double $match -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 3}, - ], - pipeline: [{$match: {[timeField]: {$gt: aTime}}}, {$match: {[timeField]: {$lt: bTime}}}], - eventFilter: {$and: [{[timeField]: {$gt: aTime}}, {[timeField]: {$lt: bTime}}]}, - wholeBucketFilter: { - $and: [ - {[minTimeField]: {$gt: aTime}}, - {[maxTimeField]: {$lt: bTime}}, - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 3}, - ], -}); - -// triple $match -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 0}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [measureField]: 4, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 3}, - ], - pipeline: [ - {$match: {[timeField]: {$gt: aTime}}}, - {$match: {[timeField]: {$lt: bTime}}}, - {$match: {[timeField]: {$lt: aTime}}}, - ], - eventFilter: { - $and: - [{[timeField]: {$gt: aTime}}, {[timeField]: {$lt: bTime}}, {[timeField]: {$lt: aTime}}] - }, - wholeBucketFilter: { - $and: [ - {[minTimeField]: {$gt: aTime}}, - {[maxTimeField]: {$lt: bTime}}, - {[maxTimeField]: {$lt: aTime}}, - ] - }, - expectedDocs: [], -}); - -// $and inside $expr with comparison on meta and measurement. There should not be a -// wholeBucketFilter, since the entire $and expression cannot be rewritten as a MatchExpression, and -// for $expr predicates we only generate a wholeBucketFilter for single predicates on the timeField. -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:00'), [measureField]: 0, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 3}, - ], - pipeline: [{ - $match: { - $expr: - {$and: [{$lt: [`$${measureField}`, `$${metaField}`]}, {$gt: [`$${metaField}`, 1]}]} - } - }], - eventFilter: { - $and: [ - {"meta": {$_internalExprGt: 1}}, - { - $expr: { - $and: [ - {$lt: [`$${measureField}`, `$${metaField}`]}, - {$gt: [`$${metaField}`, {$const: 1}]} - ] - } - } - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [metaField]: 2}, - ] -}); - -// Same test as above, but the entire $and expression can be rewritten as a MatchExpression. -// However, for $expr predicates we only generate a wholeBucketFilter for single predicates on the -// timeField. -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:00'), [measureField]: 0, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: null, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:06'), [measureField]: 6, [metaField]: 3}, - ], - pipeline: - [{$match: {$expr: {$and: [{$gte: [`$${measureField}`, 2]}, {$lt: [`$${metaField}`, 3]}]}}}], - eventFilter: { - $and: [ - {[measureField]: {$_internalExprGte: 2}}, - { - $expr: { - $and: [ - {$gte: [`$${measureField}`, {$const: 2}]}, - {$lt: [`$${metaField}`, {$const: 3}]} - ] - } - }, - {[measureField]: {$_internalExprGte: 2}}, - {[metaField]: {$_internalExprLt: 3}}, - ] - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 2}, - ] -}); - -// Same test as above but with $or and different comparison operators. -runTest({ - docsToInsert: [ - {[timeField]: ISODate('2022-01-01T00:00:00'), [measureField]: 0, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:05'), [measureField]: 5, [metaField]: 3}, - ], - pipeline: [{ - $match: { - $expr: - {$or: [{$lt: [`$${measureField}`, `$${metaField}`]}, {$lte: [`$${metaField}`, 2]}]} - } - }], - eventFilter: { - $expr: { - $or: [ - {$lt: [`$${measureField}`, `$${metaField}`]}, - {$lte: [`$${metaField}`, {$const: 2}]} - ] - } - }, - expectedDocs: [ - {[timeField]: ISODate('2022-01-01T00:00:00'), [measureField]: 0, [metaField]: 1}, - {[timeField]: ISODate('2022-01-01T00:00:01'), [measureField]: 1, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:02'), [measureField]: 2, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:03'), [measureField]: 3, [metaField]: 2}, - {[timeField]: ISODate('2022-01-01T00:00:04'), [metaField]: 2}, - ] -}); -})(); diff --git a/jstests/core/timeseries/timeseries_match_pushdown_with_project.js b/jstests/core/timeseries/timeseries_match_pushdown_with_project.js deleted file mode 100644 index 2d8e872c33a..00000000000 --- a/jstests/core/timeseries/timeseries_match_pushdown_with_project.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Tests that the unpacking stage has correct unpacking behaviour when $match is pushed into it. - * - * @tags: [ - * requires_timeseries, - * requires_fcv_60, - * does_not_support_stepdowns, - * does_not_support_transactions, - * directly_against_shardsvrs_incompatible, - * ] - */ -(function() { -"use strict"; - -load("jstests/libs/analyze_plan.js"); // For getAggPlanStages - -const coll = db.timeseries_match_pushdown_with_project; -coll.drop(); - -const timeField = 'time'; -const metaField = 'meta'; -assert.commandWorked(db.createCollection(coll.getName(), {timeseries: {timeField, metaField}})); - -const aTime = ISODate('2022-01-01T00:00:00'); -assert.commandWorked(coll.insert([ - {[timeField]: aTime, a: 1, b: 1, _id: 1}, - {[timeField]: aTime, a: 2, b: 2, _id: 2}, - {[timeField]: aTime, a: 3, b: 3, _id: 3}, - {[timeField]: aTime, a: 4, b: 4, _id: 4}, - {[timeField]: aTime, a: 5, b: 5, _id: 5}, - {[timeField]: aTime, a: 6, b: 6, _id: 6}, - {[timeField]: aTime, a: 7, b: 7, _id: 7}, - {[timeField]: aTime, a: 8, b: 8, _id: 8}, - {[timeField]: aTime, a: 9, b: 9, _id: 9}, -])); - -/** - * Runs a 'pipeline', asserts the bucket unpacking 'behaviour' (either include or exclude) is - * expected. - */ -const runTest = function({pipeline, behaviour, expectedDocs}) { - const explain = assert.commandWorked(coll.explain().aggregate(pipeline)); - const unpackStages = getAggPlanStages(explain, '$_internalUnpackBucket'); - assert.eq(1, - unpackStages.length, - "Should only have a single $_internalUnpackBucket stage: " + tojson(explain)); - const unpackStage = unpackStages[0].$_internalUnpackBucket; - if (behaviour.include) { - assert(unpackStage.include, - "Unpacking stage must have 'include' behaviour: " + tojson(explain)); - assert.sameMembers(behaviour.include, unpackStage.include); - } - if (behaviour.exclude) { - assert(unpackStage.exclude, - "Unpacking stage must have 'exclude' behaviour: " + tojson(explain)); - assert.sameMembers(behaviour.exclude, unpackStage.exclude); - } - - const docs = coll.aggregate([...pipeline, {$sort: {a: 1, b: 1, _id: 1}}]).toArray(); - assert.eq(docs.length, expectedDocs.length, "Incorrect docs: " + tojson(docs)); - docs.forEach((doc, i) => { - assert.docEq(doc, expectedDocs[i], "Incorrect docs: " + tojson(docs)); - }); -}; - -runTest({ - pipeline: [{$match: {a: {$gt: 5}}}, {$project: {b: 1}}], - behaviour: {include: ['_id', 'a', 'b']}, - expectedDocs: [ - {b: 6, _id: 6}, - {b: 7, _id: 7}, - {b: 8, _id: 8}, - {b: 9, _id: 9}, - ], -}); - -runTest({ - pipeline: [{$match: {a: {$gt: 5}}}, {$project: {_id: 0, b: 1}}], - behaviour: {include: ['a', 'b']}, - expectedDocs: [ - {b: 6}, - {b: 7}, - {b: 8}, - {b: 9}, - ], -}); - -runTest({ - pipeline: [{$match: {a: {$gt: 5}}}, {$project: {a: 1}}], - behaviour: {include: ['_id', 'a']}, - expectedDocs: [ - {a: 6, _id: 6}, - {a: 7, _id: 7}, - {a: 8, _id: 8}, - {a: 9, _id: 9}, - ], -}); - -runTest({ - pipeline: [{$match: {a: {$gt: 5}}}, {$project: {_id: 0, a: 1}}], - behaviour: {include: ['a']}, - expectedDocs: [ - {a: 6}, - {a: 7}, - {a: 8}, - {a: 9}, - ], -}); - -runTest({ - pipeline: [{$match: {a: {$gt: 5}}}, {$project: {a: 0}}], - behaviour: {exclude: []}, - expectedDocs: [ - {[timeField]: aTime, b: 6, _id: 6}, - {[timeField]: aTime, b: 7, _id: 7}, - {[timeField]: aTime, b: 8, _id: 8}, - {[timeField]: aTime, b: 9, _id: 9}, - ], -}); - -runTest({ - pipeline: [{$match: {a: {$gt: 5}}}, {$project: {b: 0}}], - behaviour: {exclude: []}, - expectedDocs: [ - {[timeField]: aTime, a: 6, _id: 6}, - {[timeField]: aTime, a: 7, _id: 7}, - {[timeField]: aTime, a: 8, _id: 8}, - {[timeField]: aTime, a: 9, _id: 9}, - ], -}); -})(); diff --git a/jstests/core/timeseries/timeseries_merge.js b/jstests/core/timeseries/timeseries_merge.js index fb6a23df803..59d4dbee889 100644 --- a/jstests/core/timeseries/timeseries_merge.js +++ b/jstests/core/timeseries/timeseries_merge.js @@ -6,7 +6,6 @@ * does_not_support_stepdowns, * does_not_support_transactions, * requires_timeseries, - * references_foreign_collection, * ] */ (function() { diff --git a/jstests/core/timeseries/timeseries_metric_index_2dsphere.js b/jstests/core/timeseries/timeseries_metric_index_2dsphere.js index 7096ace9513..e47119b5bbd 100644 --- a/jstests/core/timeseries/timeseries_metric_index_2dsphere.js +++ b/jstests/core/timeseries/timeseries_metric_index_2dsphere.js @@ -18,9 +18,8 @@ load("jstests/core/timeseries/libs/timeseries.js"); load("jstests/libs/analyze_plan.js"); -load("jstests/libs/feature_flag_util.js"); -if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { +if (!TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { return; } diff --git a/jstests/core/timeseries/timeseries_metric_index_ascending_descending.js b/jstests/core/timeseries/timeseries_metric_index_ascending_descending.js index 8a827b9d07b..bf1dc10e625 100644 --- a/jstests/core/timeseries/timeseries_metric_index_ascending_descending.js +++ b/jstests/core/timeseries/timeseries_metric_index_ascending_descending.js @@ -14,10 +14,9 @@ "use strict"; load("jstests/core/timeseries/libs/timeseries.js"); -load("jstests/libs/feature_flag_util.js"); load("jstests/libs/fixture_helpers.js"); -if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { +if (!TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { jsTestLog( "Skipped test as the featureFlagTimeseriesMetricIndexes feature flag is not enabled."); return; diff --git a/jstests/core/timeseries/timeseries_metric_index_compound.js b/jstests/core/timeseries/timeseries_metric_index_compound.js index c81f9a4c53c..2ecc4732b70 100644 --- a/jstests/core/timeseries/timeseries_metric_index_compound.js +++ b/jstests/core/timeseries/timeseries_metric_index_compound.js @@ -13,9 +13,8 @@ "use strict"; load("jstests/core/timeseries/libs/timeseries.js"); -load("jstests/libs/feature_flag_util.js"); -if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { +if (!TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { jsTestLog( "Skipped test as the featureFlagTimeseriesMetricIndexes feature flag is not enabled."); return; diff --git a/jstests/core/timeseries/timeseries_predicates.js b/jstests/core/timeseries/timeseries_predicates.js index ca78464cb31..a273cc8b128 100644 --- a/jstests/core/timeseries/timeseries_predicates.js +++ b/jstests/core/timeseries/timeseries_predicates.js @@ -17,7 +17,7 @@ const tsColl = db.timeseries_predicates_timeseries; coll.drop(); tsColl.drop(); assert.commandWorked( - db.createCollection(tsColl.getName(), {timeseries: {timeField: 'time', metaField: 'mt'}})); + db.createCollection(tsColl.getName(), {timeseries: {timeField: 'time', metaField: 'meta'}})); const bucketsColl = db.getCollection('system.buckets.' + tsColl.getName()); // Test that 'predicate' behaves correctly on the example documents, @@ -86,18 +86,18 @@ checkAllBucketings({x: {$exists: true}}, [ // Test $or... { - // ... on metric + mt. + // ... on metric + meta. checkAllBucketings({ $or: [ {x: {$lt: 0}}, - {'mt.y': {$gt: 0}}, + {'meta.y': {$gt: 0}}, ] }, [ - {x: +1, mt: {y: -1}}, - {x: +1, mt: {y: +1}}, - {x: -1, mt: {y: -1}}, - {x: -1, mt: {y: +1}}, + {x: +1, meta: {y: -1}}, + {x: +1, meta: {y: +1}}, + {x: -1, meta: {y: -1}}, + {x: -1, meta: {y: +1}}, ]); // ... when one argument can't be pushed down. @@ -131,18 +131,18 @@ checkAllBucketings({x: {$exists: true}}, [ // Test $and... { - // ... on metric + mt. + // ... on metric + meta. checkAllBucketings({ $and: [ {x: {$lt: 0}}, - {'mt.y': {$gt: 0}}, + {'meta.y': {$gt: 0}}, ] }, [ - {x: +1, mt: {y: -1}}, - {x: +1, mt: {y: +1}}, - {x: -1, mt: {y: -1}}, - {x: -1, mt: {y: +1}}, + {x: +1, meta: {y: -1}}, + {x: +1, meta: {y: +1}}, + {x: -1, meta: {y: -1}}, + {x: -1, meta: {y: +1}}, ]); // ... when one argument can't be pushed down. @@ -180,28 +180,28 @@ checkAllBucketings({ $or: [ { $and: [ - {'mt.a': {$gt: 0}}, + {'meta.a': {$gt: 0}}, {'x': {$lt: 0}}, ] }, { $and: [ - {'mt.b': {$gte: 0}}, + {'meta.b': {$gte: 0}}, {time: {$gt: ISODate('2020-01-01')}}, ] }, ] }, [ - {mt: {a: -1, b: -1}, x: -1, time: ISODate('2020-02-01')}, - {mt: {a: -1, b: -1}, x: -1, time: ISODate('2019-12-31')}, - {mt: {a: -1, b: -1}, x: +1, time: ISODate('2020-02-01')}, - {mt: {a: -1, b: -1}, x: +1, time: ISODate('2019-12-31')}, + {meta: {a: -1, b: -1}, x: -1, time: ISODate('2020-02-01')}, + {meta: {a: -1, b: -1}, x: -1, time: ISODate('2019-12-31')}, + {meta: {a: -1, b: -1}, x: +1, time: ISODate('2020-02-01')}, + {meta: {a: -1, b: -1}, x: +1, time: ISODate('2019-12-31')}, - {mt: {a: +1, b: -1}, x: -1, time: ISODate('2020-02-01')}, - {mt: {a: +1, b: -1}, x: -1, time: ISODate('2019-12-31')}, - {mt: {a: +1, b: -1}, x: +1, time: ISODate('2020-02-01')}, - {mt: {a: +1, b: -1}, x: +1, time: ISODate('2019-12-31')}, + {meta: {a: +1, b: -1}, x: -1, time: ISODate('2020-02-01')}, + {meta: {a: +1, b: -1}, x: -1, time: ISODate('2019-12-31')}, + {meta: {a: +1, b: -1}, x: +1, time: ISODate('2020-02-01')}, + {meta: {a: +1, b: -1}, x: +1, time: ISODate('2019-12-31')}, ]); // Test nested $and / $or where some leaf predicates cannot be pushed down. @@ -209,72 +209,72 @@ checkAllBucketings({ $or: [ { $and: [ - {'mt.a': {$gt: 0}}, + {'meta.a': {$gt: 0}}, {'x': {$exists: false}}, ] }, { $and: [ - {'mt.b': {$gte: 0}}, + {'meta.b': {$gte: 0}}, {time: {$gt: ISODate('2020-01-01')}}, ] }, ] }, [ - {mt: {a: -1, b: -1}, time: ISODate('2020-02-01')}, - {mt: {a: -1, b: -1}, time: ISODate('2019-12-31')}, - {mt: {a: -1, b: -1}, x: 'asdf', time: ISODate('2020-02-01')}, - {mt: {a: -1, b: -1}, x: 'asdf', time: ISODate('2019-12-31')}, + {meta: {a: -1, b: -1}, time: ISODate('2020-02-01')}, + {meta: {a: -1, b: -1}, time: ISODate('2019-12-31')}, + {meta: {a: -1, b: -1}, x: 'asdf', time: ISODate('2020-02-01')}, + {meta: {a: -1, b: -1}, x: 'asdf', time: ISODate('2019-12-31')}, - {mt: {a: +1, b: -1}, time: ISODate('2020-02-01')}, - {mt: {a: +1, b: -1}, time: ISODate('2019-12-31')}, - {mt: {a: +1, b: -1}, x: 'asdf', time: ISODate('2020-02-01')}, - {mt: {a: +1, b: -1}, x: 'asdf', time: ISODate('2019-12-31')}, + {meta: {a: +1, b: -1}, time: ISODate('2020-02-01')}, + {meta: {a: +1, b: -1}, time: ISODate('2019-12-31')}, + {meta: {a: +1, b: -1}, x: 'asdf', time: ISODate('2020-02-01')}, + {meta: {a: +1, b: -1}, x: 'asdf', time: ISODate('2019-12-31')}, ]); -// Test $exists on mt, inside $or. +// Test $exists on meta, inside $or. checkAllBucketings({ $or: [ - {"mt.a": {$exists: true}}, + {"meta.a": {$exists: true}}, {"x": {$gt: 2}}, ] }, [ - {mt: {a: 1}, x: 1}, - {mt: {a: 2}, x: 2}, - {mt: {a: 3}, x: 3}, - {mt: {a: 4}, x: 4}, - {mt: {}, x: 1}, - {mt: {}, x: 2}, - {mt: {}, x: 3}, - {mt: {}, x: 4}, + {meta: {a: 1}, x: 1}, + {meta: {a: 2}, x: 2}, + {meta: {a: 3}, x: 3}, + {meta: {a: 4}, x: 4}, + {meta: {}, x: 1}, + {meta: {}, x: 2}, + {meta: {}, x: 3}, + {meta: {}, x: 4}, ]); -// Test $in on mt, inside $or. +// Test $in on meta, inside $or. checkAllBucketings({ $or: [ - {"mt.a": {$in: [1, 3]}}, + {"meta.a": {$in: [1, 3]}}, {"x": {$gt: 2}}, ] }, [ - {mt: {a: 1}, x: 1}, - {mt: {a: 2}, x: 2}, - {mt: {a: 3}, x: 3}, - {mt: {a: 4}, x: 4}, - {mt: {}, x: 1}, - {mt: {}, x: 2}, - {mt: {}, x: 3}, - {mt: {}, x: 4}, + {meta: {a: 1}, x: 1}, + {meta: {a: 2}, x: 2}, + {meta: {a: 3}, x: 3}, + {meta: {a: 4}, x: 4}, + {meta: {}, x: 1}, + {meta: {}, x: 2}, + {meta: {}, x: 3}, + {meta: {}, x: 4}, ]); -// Test geo predicates on mt, inside $or. +// Test geo predicates on meta, inside $or. for (const pred of ['$geoWithin', '$geoIntersects']) { checkAllBucketings({ $or: [ { - "mt.location": { + "meta.location": { [pred]: { $geometry: { type: "Polygon", @@ -293,90 +293,66 @@ for (const pred of ['$geoWithin', '$geoIntersects']) { ] }, [ - {mt: {location: [1, 1]}, x: 1}, - {mt: {location: [1, 1]}, x: 2}, - {mt: {location: [1, 1]}, x: 3}, - {mt: {location: [1, 1]}, x: 4}, - {mt: {location: [5, 5]}, x: 1}, - {mt: {location: [5, 5]}, x: 2}, - {mt: {location: [5, 5]}, x: 3}, - {mt: {location: [5, 5]}, x: 4}, + {meta: {location: [1, 1]}, x: 1}, + {meta: {location: [1, 1]}, x: 2}, + {meta: {location: [1, 1]}, x: 3}, + {meta: {location: [1, 1]}, x: 4}, + {meta: {location: [5, 5]}, x: 1}, + {meta: {location: [5, 5]}, x: 2}, + {meta: {location: [5, 5]}, x: 3}, + {meta: {location: [5, 5]}, x: 4}, ]); } -// Test $mod on mt, inside $or. +// Test $mod on meta, inside $or. // $mod is an example of a predicate that we don't handle specially in time-series optimizations: // it can be pushed down if and only if it's on a metadata field. checkAllBucketings({ $or: [ - {"mt.a": {$mod: [2, 0]}}, + {"meta.a": {$mod: [2, 0]}}, {"x": {$gt: 4}}, ] }, [ - {mt: {a: 1}, x: 1}, - {mt: {a: 2}, x: 2}, - {mt: {a: 3}, x: 3}, - {mt: {a: 4}, x: 4}, - {mt: {a: 5}, x: 5}, - {mt: {a: 6}, x: 6}, - {mt: {a: 7}, x: 7}, - {mt: {a: 8}, x: 8}, + {meta: {a: 1}, x: 1}, + {meta: {a: 2}, x: 2}, + {meta: {a: 3}, x: 3}, + {meta: {a: 4}, x: 4}, + {meta: {a: 5}, x: 5}, + {meta: {a: 6}, x: 6}, + {meta: {a: 7}, x: 7}, + {meta: {a: 8}, x: 8}, ]); -// Test $elemMatch on mt, inside $or. +// Test $elemMatch on meta, inside $or. checkAllBucketings({ $or: [ - {"mt.a": {$elemMatch: {b: 3}}}, + {"meta.a": {$elemMatch: {b: 3}}}, {"x": {$gt: 4}}, ] }, [ - {x: 1, mt: {a: []}}, - {x: 2, mt: {a: [{b: 2}]}}, - {x: 3, mt: {a: [{b: 3}]}}, - {x: 4, mt: {a: [{b: 2}, {b: 3}]}}, - {x: 5, mt: {a: []}}, - {x: 6, mt: {a: [{b: 2}]}}, - {x: 7, mt: {a: [{b: 3}]}}, - {x: 8, mt: {a: [{b: 2}, {b: 3}]}}, + {x: 1, meta: {a: []}}, + {x: 2, meta: {a: [{b: 2}]}}, + {x: 3, meta: {a: [{b: 3}]}}, + {x: 4, meta: {a: [{b: 2}, {b: 3}]}}, + {x: 5, meta: {a: []}}, + {x: 6, meta: {a: [{b: 2}]}}, + {x: 7, meta: {a: [{b: 3}]}}, + {x: 8, meta: {a: [{b: 2}, {b: 3}]}}, ]); checkAllBucketings({ $or: [ - {"mt.a": {$elemMatch: {b: 2, c: 3}}}, + {"meta.a": {$elemMatch: {b: 2, c: 3}}}, {"x": {$gt: 3}}, ] }, [ - {x: 1, mt: {a: []}}, - {x: 2, mt: {a: [{b: 2, c: 3}]}}, - {x: 3, mt: {a: [{b: 2}, {c: 3}]}}, - {x: 4, mt: {a: []}}, - {x: 5, mt: {a: [{b: 2, c: 3}]}}, - {x: 6, mt: {a: [{b: 2}, {c: 3}]}}, + {x: 1, meta: {a: []}}, + {x: 2, meta: {a: [{b: 2, c: 3}]}}, + {x: 3, meta: {a: [{b: 2}, {c: 3}]}}, + {x: 4, meta: {a: []}}, + {x: 5, meta: {a: [{b: 2, c: 3}]}}, + {x: 6, meta: {a: [{b: 2}, {c: 3}]}}, ]); - -// Test a standalone $elemMatch on mt. -checkAllBucketings({"mt.a": {$elemMatch: {b: 3}}}, [ - {mt: {a: []}}, - {mt: {a: [{b: 2}]}}, - {mt: {a: [{b: 3}]}}, - {mt: {a: [{b: 2}, {b: 3}]}}, - {mt: {a: []}}, - {mt: {a: [{b: 2}]}}, - {mt: {a: [{b: 3}]}}, - {mt: {a: [{b: 2}, {b: 3}]}}, -]); - -// Test a standalone $size on mt. -checkAllBucketings({"mt.a": {$size: 1}}, [ - {mt: {a: []}}, - {mt: {a: [{b: 2}]}}, - {mt: {a: [{b: 3}]}}, - {mt: {a: [{b: 2}, {b: 3}]}}, - {mt: {a: []}}, - {mt: {a: [{b: 2}]}}, - {mt: {a: [{b: 3}]}}, - {mt: {a: [{b: 2}, {b: 3}]}}, -]); })(); diff --git a/jstests/core/timeseries/timeseries_project.js b/jstests/core/timeseries/timeseries_project.js index b735a8a3b66..9375f06240d 100644 --- a/jstests/core/timeseries/timeseries_project.js +++ b/jstests/core/timeseries/timeseries_project.js @@ -4,7 +4,7 @@ * @tags: [ * does_not_support_stepdowns, * does_not_support_transactions, - * requires_fcv_60, + * requires_fcv_53, * ] */ (function() { @@ -97,7 +97,6 @@ const doc = { time: new Date("2019-10-11T14:39:18.670Z"), x: 5, a: 3, - obj: {a: 3}, }; assert.commandWorked(tsColl.insert(doc)); assert.commandWorked(regColl.insert(doc)); @@ -108,192 +107,10 @@ let tsDoc = tsColl.aggregate(pipeline).toArray(); let regDoc = regColl.aggregate(pipeline).toArray(); assert.docEq(tsDoc, regDoc); -pipeline = [{$project: {_id: 0, obj: "$x", b: {$add: ["$obj.a", 1]}}}]; -tsDoc = tsColl.aggregate(pipeline).toArray(); -regDoc = regColl.aggregate(pipeline).toArray(); -assert.docEq(tsDoc, regDoc); - // Test $addFields. pipeline = [{$addFields: {a: "$x", b: "$a"}}, {$project: {_id: 0}}]; tsDoc = tsColl.aggregate(pipeline).toArray(); regDoc = regColl.aggregate(pipeline).toArray(); assert.docEq(tsDoc, regDoc); - -pipeline = [{$addFields: {obj: "$x", b: {$add: ["$obj.a", 1]}}}, {$project: {_id: 0}}]; -tsDoc = tsColl.aggregate(pipeline).toArray(); -regDoc = regColl.aggregate(pipeline).toArray(); -assert.docEq(tsDoc, regDoc); - -pipeline = [{$project: {a: 1, _id: 0}}, {$project: {newMeta: "$x"}}]; -tsDoc = tsColl.aggregate(pipeline).toArray(); -regDoc = regColl.aggregate(pipeline).toArray(); -assert.docEq(tsDoc, regDoc); -})(); - -// Test that an includes projection that doesn't include previously included computed meta fields -// outputs correctly. -(function testIncludeComputedMetaFieldThenProjectWithoutComputedMetaField() { - coll.drop(); - assert.commandWorked( - db.createCollection(coll.getName(), {timeseries: {timeField: 'time', metaField: 'tag'}})); - - const doc = {_id: 0, time: ISODate("2024-01-01T00:00:00.000Z"), tag: {field: "A"}}; - assert.commandWorked(coll.insert(doc)); - let result = {}; - - // Added field which uses meta should not be in the result. - result = - coll.aggregate( - [{$addFields: {time: {$dateFromParts: {year: "$tag.none"}}}}, {$project: {tag: 1}}]) - .toArray(); - assert.docEq([{"tag": {"field": "A"}, "_id": 0}], result); - - // Test with non-null value. - result = coll.aggregate([{$addFields: {time: {$toLower: "$tag.field"}}}, {$project: {tag: 1}}]) - .toArray(); - assert.docEq([{"tag": {"field": "A"}, "_id": 0}], result); - - // Testing with $match between $addFields and $project. - result = coll.aggregate([ - {$addFields: {time: {$dateFromParts: {year: "$tag.none"}}}}, - {$match: {tag: {field: "A"}}}, - {$project: {tag: 1}} - ]) - .toArray(); - assert.docEq([{"tag": {field: "A"}, "_id": 0}], result); - - // Test with non-null value. - result = - coll.aggregate( - [{$addFields: {time: "$tag.field"}}, {$match: {time: "A"}}, {$project: {tag: 1}}]) - .toArray(); - assert.docEq([{"tag": {field: "A"}, "_id": 0}], result); - - // Testing with $project after $project. - result = coll.aggregate([{$project: {tag: 1}}, {$project: {time: 1}}]).toArray(); - assert.docEq([{"_id": 0}], result); - - // Testing with $set. - result = coll.aggregate([{$set: {tag: "$tag.field"}}, {$project: {time: 1}}]).toArray(); - assert.docEq([{"_id": 0, "time": ISODate("2024-01-01T00:00:00Z")}], result); - - // Test that computedMetaFields that are included by the $project are still included. - result = coll.aggregate([ - {$addFields: {hi: {$dateFromParts: {year: "$tag.none"}}}}, - {$addFields: {bye: {$dateFromParts: {year: "$tag.none"}}}}, - {$project: {hi: 1}} - ]) - .toArray(); - assert.docEq([{"_id": 0, "hi": null}], result); - - // Test with non-null value. - result = coll.aggregate([ - {$addFields: {hi: "$tag.field"}}, - {$addFields: {bye: "$tag.field"}}, - {$project: {hi: 1}} - ]) - .toArray(); - assert.docEq([{"_id": 0, "hi": "A"}], result); - - // Test that $project that replaces field that is used works. - result = coll.aggregate([ - {$addFields: {hello: "$tag.field"}}, - {$project: {newTime: "$time", time: "$tag.field"}} - ]) - .toArray(); - - assert.docEq([{"_id": 0, newTime: ISODate("2024-01-01T00:00:00.000Z"), time: "A"}], result); - - // Test adding fields and including one of them. - result = coll.aggregate([ - {$addFields: {a: {$toLower: "$tag.field"}}}, - {$addFields: {b: {$toUpper: "$tag.field"}}}, - {$project: {b: 1}} - ]) - .toArray(); - assert.docEq([{b: "A", "_id": 0}], result); -})(); - -// Test that an excludes projection that excludes previously included computed meta fields -// outputs correctly. -(function testIncludeComputedMetaFieldThenExcludeComputedMetaField() { - coll.drop(); - assert.commandWorked( - db.createCollection(coll.getName(), {timeseries: {timeField: 'time', metaField: 'tag'}})); - - const doc = {_id: 0, time: ISODate("2024-01-01T00:00:00.000Z"), tag: {field: "A"}}; - assert.commandWorked(coll.insert(doc)); - let result = {}; - - // Excluded '_computedMetaProjField' should not be included. - result = coll.aggregate([ - {$addFields: {hello: {$dateFromParts: {year: "$tag.none"}}}}, - {$project: {hello: 0}} - ]) - .toArray(); - assert.docEq([{"time": ISODate("2024-01-01T00:00:00Z"), "tag": {"field": "A"}, "_id": 0}], - result); - - // Test with non-null value. - result = coll.aggregate([{$addFields: {time: {$toLower: "$tag.field"}}}, {$project: {time: 0}}]) - .toArray(); - assert.docEq([{"tag": {"field": "A"}, "_id": 0}], result); - - // Testing with $match between $addFields and $project. - result = coll.aggregate([ - {$addFields: {time: {$dateFromParts: {year: "$tag.none"}}}}, - {$match: {tag: {field: "A"}}}, - {$project: {time: 0}} - ]) - .toArray(); - assert.docEq([{"tag": {field: "A"}, "_id": 0}], result); - - // Test with non-null value. - result = - coll.aggregate( - [{$addFields: {time: "$tag.field"}}, {$match: {time: "A"}}, {$project: {time: 0}}]) - .toArray(); - assert.docEq([{"tag": {field: "A"}, "_id": 0}], result); - - // Testing with exclude $project after include $project. - result = coll.aggregate([{$project: {tag: 1}}, {$project: {tag: 0}}]).toArray(); - assert.docEq([{"_id": 0}], result); - - // Testing with $set. - result = coll.aggregate([{$set: {tag: "$tag.field"}}, {$project: {tag: 0}}]).toArray(); - assert.docEq([{"time": ISODate("2024-01-01T00:00:00Z"), "_id": 0}], result); - - // Exclude one added field. - result = coll.aggregate([ - {$addFields: {hi: {$dateFromParts: {year: "$tag.none"}}}}, - {$addFields: {bye: {$dateFromParts: {year: "$tag.none"}}}}, - {$project: {hi: 0}} - ]) - .toArray(); - assert.docEq( - [{"time": ISODate("2024-01-01T00:00:00Z"), "tag": {"field": "A"}, "_id": 0, "bye": null}], - result); - - // Test with non-null value. - result = coll.aggregate([ - {$addFields: {hi: "$tag.field"}}, - {$addFields: {bye: "$tag.field"}}, - {$project: {hi: 0}} - ]) - .toArray(); - assert.docEq( - [{"time": ISODate("2024-01-01T00:00:00Z"), "tag": {"field": "A"}, "_id": 0, "bye": "A"}], - result); - - // Test adding fields and excluding one of them. - result = coll.aggregate([ - {$addFields: {a: {$toLower: "$tag.field"}}}, - {$addFields: {b: {$toUpper: "$tag.field"}}}, - {$project: {b: 0}} - ]) - .toArray(); - assert.docEq( - [{"time": ISODate("2024-01-01T00:00:00Z"), "tag": {"field": "A"}, "_id": 0, "a": "a"}], - result); })(); })(); diff --git a/jstests/core/timeseries/timeseries_project_pushdown.js b/jstests/core/timeseries/timeseries_project_pushdown.js deleted file mode 100644 index 91b009aa00d..00000000000 --- a/jstests/core/timeseries/timeseries_project_pushdown.js +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Test the behavior of $project with pipelines that require the whole document. Specifically, we - * are targeting that rewrites for $project with $getField, or '$$ROOT'/'$$CURRENT' should only - * occur in certain situations. - * - * @tags: [ - * requires_timeseries, - * does_not_support_stepdowns, - * does_not_support_transactions, - * ] - */ -(function() { -"use strict"; - -const timeField = "t"; -const metaField = "meta1"; -const coll = db.timeseries_project_pushdown; - -function runTest({docs, pipeline, expectedResults}) { - coll.drop(); - assert.commandWorked(db.createCollection( - coll.getName(), {timeseries: {timeField: timeField, metaField: metaField}})); - assert.commandWorked(coll.insertMany(docs)); - const results = coll.aggregate(pipeline).toArray(); - assert.sameMembers(expectedResults, results, () => { - return `Pipeline: ${tojson(pipeline)}. Explain: ${ - tojson(coll.explain().aggregate(pipeline))}`; - }); -} - -// -// The following tests confirm the behavior of queries in the form: {$getField: "fieldName"}. -// -(function testMeta_OnlyStringField() { - runTest({ - docs: [{_id: 1, [timeField]: new Date(), [metaField]: 2}], - pipeline: [{$project: {new: {$getField: metaField}, _id: 0}}], - expectedResults: [{new: 2}] - }); -})(); - -// $getField does not traverse objects, and should not be rewritten when it relies on a mix of -// metaField and measurement fields. Let's validate that behavior. -(function testDottedPath_OnlyStringField() { - runTest({ - docs: [ - {_id: 1, [timeField]: new Date(), [metaField]: 2, "a.b": 3, a: {b: 2}}, - {_id: 2, [timeField]: new Date(), [metaField]: 2, a: {b: 2}} // missing field. - ], - pipeline: [{$project: {new: {$add: [`$${metaField}`, {$getField: "a.b"}]}}}], - expectedResults: [{_id: 1, new: 5}, {_id: 2, new: null}] - }); -})(); - -// -// The following tests confirm the behavior of queries in the form: {$getField: {$literal: -// "fieldName"}}. -// -(function testDottedPath_LiteralExpr() { - runTest({ - docs: [ - {_id: 1, [timeField]: new Date(), [metaField]: 2, "a.$b": 3, a: {b: 4}}, - {_id: 2, [timeField]: new Date(), [metaField]: 2, a: {b: 2}} // missing field. - ], - pipeline: [{$project: {new: {$add: [`$${metaField}`, {$getField: {$literal: "a.$b"}}]}}}], - expectedResults: [{_id: 1, new: 5}, {_id: 2, new: null}] - }); -}()); - -// There is a difference between the metaField "meta1", and "$meta1". Field paths are allowed to -// have a '$' and are accessed by $getField. We need to validate we return the correct results when -// we have both the metaField and "$meta1". -(function testDollarMeta_LiteralExpr() { - runTest({ - docs: [ - {_id: 1, [timeField]: new Date(), [metaField]: 2}, - {_id: 2, [timeField]: new Date(), [metaField]: 2, "$meta1": 3} // missing field. - ], - pipeline: [{$project: {new: {$add: [`$${metaField}`, {$getField: {$literal: "$meta1"}}]}}}], - expectedResults: [{_id: 1, new: null}, {_id: 2, new: 5}] - }); -})(); - -// The following tests confirm the behavior of queries in the form: {$getField: {input: "string", -// field: "string"}}. -// -(function testMetaField_FieldAndInputString() { - runTest({ - docs: [ - {_id: 1, [timeField]: new Date(), [metaField]: {b: 4}}, - {_id: 2, [timeField]: new Date(), [metaField]: {c: 5}} // missing subfield. - ], - pipeline: [{$project: {new: {$getField: {input: `$${metaField}`, field: "b"}}}}], - expectedResults: [{_id: 1, new: 4}, {_id: 2}] - }); -})(); - -// Validate the correct results are returned when there is a field with '$' inside the metaField. -// The rewrite should be valid and return the correct field. -(function testMetaFieldObj_FieldAndInputString() { - runTest({ - docs: [ - {_id: 1, [timeField]: new Date(), [metaField]: {"a.$b": 4}}, - {_id: 2, [timeField]: new Date(), [metaField]: {c: 5}} // missing subfield. - ], - pipeline: [{$project: {new: {$getField: {input: `$${metaField}`, field: "a.$b"}}}}], - expectedResults: [{_id: 1, new: 4}, {_id: 2}] - }); -})(); - -// When we rely on both the metaField and a measurementField we should not perform the rewrite and -// return the correct result. -(function testDollarMeta_FieldAndInputString() { - runTest({ - docs: [ - {_id: 1, [timeField]: new Date(), [metaField]: 2, a: {"$meta1": 4}}, - {_id: 2, [timeField]: new Date(), [metaField]: 2, a: {c: 5}} // missing subfield. - ], - - pipeline: [{ - $project: { - new: { - $add: [`$${metaField}`, {$getField: {input: "$a", field: {$literal: "$meta1"}}}] - } - } - }], - expectedResults: [{_id: 1, new: 6}, {_id: 2, new: null}] - }); -})(); - -// same test as above but with $addFields and not $project. -(function testDollarMeta_FieldAndInputString_AddFields() { - const time = new Date(); - runTest({ - docs: [ - {_id: 1, [timeField]: time, [metaField]: 2, a: {"$meta1": 4}}, - {_id: 2, [timeField]: time, [metaField]: 2, a: {c: 5}} // missing subfield. - ], - pipeline: [{ - $addFields: { - new: { - $add: [`$${metaField}`, {$getField: {input: "$a", field: {$literal: "$meta1"}}}] - } - } - }], - expectedResults: [ - {[timeField]: time, [metaField]: 2, a: {"$meta1": 4}, _id: 1, new: 6}, - {[timeField]: time, [metaField]: 2, a: {c: 5}, _id: 2, new: null} - ] - }); -})(); - -// This test validates that $project with '$$ROOT' which requires the whole document returns the -// correct results. -(function testProject_WithROOT() { - const time = new Date(); - runTest({ - docs: [ - {_id: 1, [timeField]: time, [metaField]: 2, a: 2}, - {_id: 2, [timeField]: time, [metaField]: 2, b: 3} - ], - pipeline: [{$project: {new: "$$ROOT", _id: 1}}], - expectedResults: [ - {_id: 1, new: {_id: 1, [timeField]: time, [metaField]: 2, a: 2}}, - {_id: 2, new: {_id: 2, [timeField]: time, [metaField]: 2, b: 3}} - ] - }); -})(); - -(function testAddFields_WithROOT() { - const time = new Date(); - runTest({ - docs: [ - {_id: 1, [timeField]: time, [metaField]: 2, a: 2}, - {_id: 2, [timeField]: time, [metaField]: 2, b: 3} - ], - pipeline: [{$addFields: {new: "$$ROOT"}}], - expectedResults: [ - { - _id: 1, - [timeField]: time, - [metaField]: 2, - a: 2, - new: {_id: 1, [timeField]: time, [metaField]: 2, a: 2} - }, - { - _id: 2, - [timeField]: time, - [metaField]: 2, - b: 3, - new: {_id: 2, [timeField]: time, [metaField]: 2, b: 3} - } - ] - }); -})(); -})(); diff --git a/jstests/core/timeseries/timeseries_resume_after.js b/jstests/core/timeseries/timeseries_resume_after.js index 66d020d43b4..a2b31972c85 100644 --- a/jstests/core/timeseries/timeseries_resume_after.js +++ b/jstests/core/timeseries/timeseries_resume_after.js @@ -8,7 +8,6 @@ * does_not_support_stepdowns, * does_not_support_transactions, * requires_getmore, - * requires_fcv_60 * ] */ (function() { @@ -92,25 +91,5 @@ TimeseriesTest.run((insert) => { resumeToken = res.cursor.postBatchResumeToken; jsTestLog("Got resume token " + tojson(resumeToken)); - - // Test that '$_resumeAfter' fails if the recordId is Long. - assert.commandFailedWithCode(db.runCommand({ - find: bucketsColl.getName(), - filter: {}, - $_requestResumeToken: true, - $_resumeAfter: {'$recordId': NumberLong(10)}, - hint: {$natural: 1} - }), - 7738600); - - // Test that '$_resumeAfter' fails if querying the time-series view. - assert.commandFailedWithCode(db.runCommand({ - find: coll.getName(), - filter: {}, - $_requestResumeToken: true, - $_resumeAfter: {'$recordId': BinData(5, '1234')}, - hint: {$natural: 1} - }), - ErrorCodes.InvalidPipelineOperator); }); })(); diff --git a/jstests/core/timeseries/timeseries_sparse_index.js b/jstests/core/timeseries/timeseries_sparse_index.js index 7c9f7556e38..9ffd008ff62 100644 --- a/jstests/core/timeseries/timeseries_sparse_index.js +++ b/jstests/core/timeseries/timeseries_sparse_index.js @@ -13,9 +13,8 @@ "use strict"; load("jstests/core/timeseries/libs/timeseries.js"); -load("jstests/libs/feature_flag_util.js"); -if (!FeatureFlagUtil.isEnabled(db, "TimeseriesMetricIndexes")) { +if (!TimeseriesTest.timeseriesMetricIndexesEnabled(db.getMongo())) { jsTestLog( "Skipped test as the featureFlagTimeseriesMetricIndexes feature flag is not enabled."); return; diff --git a/jstests/core/timeseries/timeseries_streaming_group.js b/jstests/core/timeseries/timeseries_streaming_group.js deleted file mode 100644 index 6d97eb51621..00000000000 --- a/jstests/core/timeseries/timeseries_streaming_group.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Tests that the $group stage will be replaced with $_internalStreamingGroup when group id is - * monotonic on time and documents are sorted on time. - * - * @tags: [ - * # Explain of a resolved view must be executed by mongos. - * directly_against_shardsvrs_incompatible, - * does_not_support_stepdowns, - * does_not_support_transactions, - * requires_fcv_60, - * requires_timeseries, - * ] - */ -(function() { -"use strict"; - -load("jstests/libs/analyze_plan.js"); // For getAggPlanStages -load("jstests/libs/fail_point_util.js"); // For configureFailPoint - -const ts = db.timeseries_streaming_group; -ts.drop(); - -const coll = db.timeseires_streaming_group_regular_collection; -coll.drop(); - -assert.commandWorked( - db.createCollection(ts.getName(), {timeseries: {timeField: "time", metaField: "meta"}})); - -const numTimes = 100; -const numSymbols = 10; -const minPrice = 100; -const maxPrice = 200; -const minAmount = 1; -const maxAmount = 20; - -Random.setRandomSeed(1); - -const randRange = function(min, max) { - return min + Random.randInt(max - min); -}; - -const symbols = []; -for (let i = 0; i < numSymbols; i++) { - let randomName = ""; - const randomStrLen = 5; - for (let j = 0; j < randomStrLen; j++) { - randomName += String.fromCharCode("A".charCodeAt(0) + Random.randInt(26)); - } - symbols.push(randomName); -} - -const documents = []; -const startTime = 1641027600000; -for (let i = 0; i < numTimes; i++) { - for (const symbol of symbols) { - documents.push({ - time: new Date(startTime + i * 1000), - price: randRange(minPrice, maxPrice), - amount: randRange(minAmount, maxAmount), - meta: {"symbol": symbol} - }); - } -} - -assert.commandWorked(ts.insert(documents)); -assert.commandWorked(coll.insert(documents)); - -// Incorrect use of $_internalStreamingGroup should return error -assert.commandFailedWithCode(db.runCommand({ - aggregate: "timeseires_streaming_group_regular_collection", - pipeline: [{ - $_internalStreamingGroup: { - _id: {symbol: "$symbol", time: "$time"}, - count: {$sum: 1}, - $monotonicIdFields: ["price"] - } - }], - cursor: {}, -}), - 7026705); -assert.commandFailedWithCode(db.runCommand({ - aggregate: "timeseires_streaming_group_regular_collection", - pipeline: [{$_internalStreamingGroup: {_id: null, count: {$sum: 1}}}], - cursor: {}, -}), - 7026702); -assert.commandFailedWithCode(db.runCommand({ - aggregate: "timeseires_streaming_group_regular_collection", - pipeline: - [{$_internalStreamingGroup: {_id: null, count: {$sum: 1}, $monotonicIdFields: ["_id"]}}], - cursor: {}, -}), - 7026708); - -const runTest = function(pipeline, expectedMonotonicIdFields) { - const explain = assert.commandWorked(ts.explain().aggregate(pipeline)); - const streamingGroupStage = getAggPlanStage(explain, "$_internalStreamingGroup"); - assert.neq(streamingGroupStage, null); - assert.eq(streamingGroupStage.$_internalStreamingGroup.$monotonicIdFields, - expectedMonotonicIdFields); - - const found = ts.aggregate(pipeline).toArray(); - const expected = coll.aggregate(pipeline).toArray(); - assert.eq(expected, found); -}; - -runTest( - [ - {$sort: {time: 1}}, - { - $group: { - _id: { - symbol: "$meta.symbol", - minute: { - $subtract: [ - {$dateTrunc: {date: "$time", unit: "minute"}}, - {$dateTrunc: {date: new Date(startTime), unit: "minute"}}, - ] - } - }, - "average_price": {$avg: {$multiply: ["$price", "$amount"]}}, - "average_amount": {$avg: "$amount"} - } - }, - {$addFields: {"average_price": {$divide: ["$average_price", "$average_amount"]}}}, - {$sort: {_id: 1}} - ], - ["minute"]); - -runTest( - [ - {$sort: {time: 1}}, - { - $group: { - _id: {$dateTrunc: {date: "$time", unit: "minute"}}, - "average_price": {$avg: {$multiply: ["$price", "$amount"]}}, - "average_amount": {$avg: "$amount"} - } - }, - {$addFields: {"average_price": {$divide: ["$average_price", "$average_amount"]}}}, - {$sort: {_id: 1}} - ], - ["_id"]); - -runTest( - [ - {$sort: {time: 1}}, - { - $group: { - _id: "$time", - "average_price": {$avg: {$multiply: ["$price", "$amount"]}}, - "average_amount": {$avg: "$amount"} - } - }, - {$addFields: {"average_price": {$divide: ["$average_price", "$average_amount"]}}}, - {$sort: {_id: 1}} - ], - ["_id"]); -})(); diff --git a/jstests/core/timeseries/timeseries_union_with.js b/jstests/core/timeseries/timeseries_union_with.js index e92e4c4ca37..b78bbd6937c 100644 --- a/jstests/core/timeseries/timeseries_union_with.js +++ b/jstests/core/timeseries/timeseries_union_with.js @@ -4,11 +4,6 @@ * @tags: [ * does_not_support_transactions, * requires_timeseries, - * # This test depends on certain writes ending up in the same bucket. Stepdowns and tenant - * # migrations may result in writes splitting between two primaries, and thus different buckets. - * does_not_support_stepdowns, - * tenant_migration_incompatible, - * references_foreign_collection, * ] */ (function() { diff --git a/jstests/core/timeseries/timeseries_update_mixed_schema_bucket.js b/jstests/core/timeseries/timeseries_update_mixed_schema_bucket.js deleted file mode 100644 index 0211daa29d8..00000000000 --- a/jstests/core/timeseries/timeseries_update_mixed_schema_bucket.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Tests directly updating a time-series bucket to contain mixed schema. - * - * @tags: [ - * # $listCatalog does not include the tenant prefix in its results. - * command_not_supported_in_serverless, - * requires_timeseries, - * # $listCatalog not supported inside of a multi-document transaction - * does_not_support_transactions, - * # $listCatalog only exists since v6 - * multiversion_incompatible, - * ] - */ -(function() { -"use strict"; -load("jstests/core/timeseries/libs/timeseries.js"); // For 'TimeseriesTest'. - -TestData.skipEnforceTimeseriesBucketsAreAlwaysCompressedOnValidate = true; - -const testDB = db.getSiblingDB(jsTestName()); -const collName = "ts"; - -assert.commandWorkedOrFailedWithCode(testDB.runCommand({drop: collName}), - ErrorCodes.NamespaceNotFound); -assert.commandWorked( - testDB.createCollection(collName, {timeseries: {timeField: "t", metaField: "m"}})); -const coll = testDB[collName]; -const bucketsColl = testDB["system.buckets." + collName]; - -const bucket = { - _id: ObjectId("65a6eb806ffc9fa4280ecac4"), - control: { - version: NumberInt(1), - min: { - _id: ObjectId("65a6eba7e6d2e848e08c3750"), - t: ISODate("2024-01-16T20:48:00Z"), - a: 0, - }, - max: { - _id: ObjectId("65a6eba7e6d2e848e08c3751"), - t: ISODate("2024-01-16T20:48:39.448Z"), - a: 1, - }, - }, - meta: 0, - data: { - _id: { - 0: ObjectId("65a6eba7e6d2e848e08c3750"), - 1: ObjectId("65a6eba7e6d2e848e08c3751"), - }, - t: { - 0: ISODate("2024-01-16T20:48:39.448Z"), - 1: ISODate("2024-01-16T20:48:39.448Z"), - }, - a: { - 0: 0, - 1: 1, - }, - } -}; - -const update = function() { - return bucketsColl.update({_id: bucket._id}, - {$set: {"control.min.a": 1, "control.max.a": "a", "data.a.0": "a"}}); -}; - -assert.commandWorked(bucketsColl.insert(bucket)); -assert.commandFailedWithCode(update(), ErrorCodes.CannotInsertTimeseriesBucketsWithMixedSchema); -assert.eq(TimeseriesTest.bucketsMayHaveMixedSchemaData(bucketsColl), false); -assert.commandWorked( - testDB.runCommand({collMod: collName, timeseriesBucketsMayHaveMixedSchemaData: true})); -assert.eq(TimeseriesTest.bucketsMayHaveMixedSchemaData(bucketsColl), true); -assert.commandWorked(update()); -assert.commandWorked(bucketsColl.deleteOne({_id: bucket._id})); -assert.eq(TimeseriesTest.bucketsMayHaveMixedSchemaData(bucketsColl), true); -assert.commandWorked( - testDB.runCommand({collMod: collName, timeseriesBucketsMayHaveMixedSchemaData: false})); -assert.eq(TimeseriesTest.bucketsMayHaveMixedSchemaData(bucketsColl), false); -})(); diff --git a/jstests/core/transaction_too_large_for_cache.js b/jstests/core/transaction_too_large_for_cache.js deleted file mode 100644 index 224c23eedaf..00000000000 --- a/jstests/core/transaction_too_large_for_cache.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Tests that an operation requiring more cache than available fails instead of retrying infinitely. - * - * @tags: [ - * does_not_support_config_fuzzer, - * requires_fcv_60, - * requires_persistence, - * requires_non_retryable_writes, - * requires_wiredtiger, - * no_selinux - * ] - */ - -(function() { -load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers. -load("jstests/libs/storage_engine_utils.js"); - -// TODO (SERVER-39362): remove once parallel suite respects tags properly. -if (!storageEngineIsWiredTiger()) { - jsTestLog("Skipping test because storage engine is not WiredTiger."); - return; -} - -const doc = { - x: [] -}; -for (var j = 0; j < 334000; j++) { - doc.x.push("" + Math.random() + Math.random()); -} - -const coll = db[jsTestName()]; -coll.drop(); - -// Maximum amount of indexes is 64. _id is implicit, and sharded collections also have an index on -// the shard key. -assert.commandWorked(coll.createIndex({x: "text"})); -for (let i = 0; i < 61; i++) { - assert.commandWorked(coll.createIndex({x: 1, ["field" + i]: 1})); -} - -// Retry the operation until we eventually hit the TransactionTooLargeForCache. Retry on -// WriteConflict or TemporarilyUnavailable errors, as those are expected to be returned if the -// threshold for TransactionTooLargeForCache is not reached, possibly due to concurrent operations. -assert.soon(() => { - let result; - try { - result = coll.insert(doc); - assert.commandFailedWithCode(result, ErrorCodes.TransactionTooLargeForCache); - return true; - } catch (e) { - assert.commandFailedWithCode(result, - [ErrorCodes.WriteConflict, ErrorCodes.TemporarilyUnavailable]); - return false; - } -}, "Expected operation to eventually fail with TransactionTooLargeForCache error."); -}()); diff --git a/jstests/core/ttl_index_options.js b/jstests/core/ttl_index_options.js index b136680edc4..47ae2709073 100644 --- a/jstests/core/ttl_index_options.js +++ b/jstests/core/ttl_index_options.js @@ -1,10 +1,7 @@ /** * Ensures that the options passed in for TTL indexes are validated during index creation. * - * @tags: [ - * requires_fcv_60, - * requires_ttl_index, - * ] + * @tags: [requires_ttl_index] */ (function() { 'use strict'; @@ -19,10 +16,11 @@ assert.commandFailedWithCode( assert.commandFailedWithCode(coll.createIndexes([{x: 1}], {expireAfterSeconds: 9999999999999999}), ErrorCodes.CannotCreateIndex); -// Ensure that we can provide a time that is larger than the current epoch time. -let secondsSinceEpoch = Math.floor(Date.now() / 1000); -assert.commandWorked( - coll.createIndexes([{x_before_epoch: 1}], {expireAfterSeconds: secondsSinceEpoch + 1000})); +// Ensure that we cannot provide a time that is larger than the current epoch time. +let secondsSinceEpoch = Date.now() / 1000; +assert.commandFailedWithCode( + coll.createIndexes([{x: 1}], {expireAfterSeconds: secondsSinceEpoch + 1000}), + ErrorCodes.CannotCreateIndex); // 'expireAfterSeconds' cannot be less than 0. assert.commandFailedWithCode(coll.createIndexes([{x: 1}], {expireAfterSeconds: -1}), diff --git a/jstests/core/txns/abort_expired_transaction.js b/jstests/core/txns/abort_expired_transaction.js index 54418596967..b211ef7b132 100644 --- a/jstests/core/txns/abort_expired_transaction.js +++ b/jstests/core/txns/abort_expired_transaction.js @@ -33,11 +33,6 @@ try { const session = db.getMongo().startSession(sessionOptions); const sessionDb = session.getDatabase(testDBName); - // Number of passes made by the "abortExpiredTransactions" thread before the transaction - // expires. - const abortExpiredTransactionsPassesPreAbort = - db.serverStatus().metrics.abortExpiredTransactions.passes; - let txnNumber = 0; jsTest.log("Insert a document starting a transaction."); @@ -69,14 +64,6 @@ try { "currentOp reports that the idle transaction still exists, it has not been " + "aborted as expected."); - assert.soon(() => { - // For this expired transaction to abort, the "abortExpiredTransactions" thread has to - // perform at least one pass. - const serverStatus = db.serverStatus(); - return abortExpiredTransactionsPassesPreAbort < - serverStatus.metrics.abortExpiredTransactions.passes; - }); - jsTest.log( "Attempt to do a write in the transaction, which should fail because the transaction " + "was aborted"); diff --git a/jstests/core/txns/aggregation_in_transaction.js b/jstests/core/txns/aggregation_in_transaction.js index 40d7030fe1f..ec1c62133bb 100644 --- a/jstests/core/txns/aggregation_in_transaction.js +++ b/jstests/core/txns/aggregation_in_transaction.js @@ -1,5 +1,5 @@ // Tests that aggregation is supported in transactions. -// @tags: [uses_transactions, uses_snapshot_read_concern, references_foreign_collection] +// @tags: [uses_transactions, uses_snapshot_read_concern] (function() { "use strict"; diff --git a/jstests/core/txns/out_not_blocked_by_txn.js b/jstests/core/txns/out_not_blocked_by_txn.js deleted file mode 100644 index a48097c9eab..00000000000 --- a/jstests/core/txns/out_not_blocked_by_txn.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Test that renaming a collection through $out takes database IX lock and will not be blocked by - * transactions. This test is created to ensure that the scenario occuring in the case linked in - * SERVER-72703, where a compact operation (which took an IX DB lock) blocked a rename with $out, - * which required an exclusive DB lock. Now that $out should take an IX DB lock, renaming with - * $out should not be blocked by operations taking an IX lock. Renaming with $out across - * different databases still takes an X lock. - * - * @tags: [uses_transactions, requires_db_locking, assumes_unsharded_collection] - */ -let dbName = jsTestName(); -let mydb = db.getSiblingDB(dbName); - -// Drop the collections that we will be using in this test, both for the transaction and for the -// rename operation, and wait for majority confirmation. -mydb.txn.drop({writeConcern: {w: "majority"}}); -mydb.a.drop({writeConcern: {w: "majority"}}); -mydb.b.drop({writeConcern: {w: "majority"}}); -mydb.c.drop({writeConcern: {w: "majority"}}); - -// Populate our collections. -assert.commandWorked(mydb.txn.insert({x: 1})); -assert.commandWorked(mydb.a.insert([{x: 1}])); -assert.commandWorked(mydb.c.insert({x: 1})); - -// Begin a session for our transaction. -const session = mydb.getMongo().startSession(); -const sessionDb = session.getDatabase(dbName); - -session.startTransaction(); -// This holds a database IX lock and a collection IX lock on "test.txn". -sessionDb.t.insert({y: 1}); - -// $out should now also only require an IX lock. -// Test the scenario where we rename collection 'a' to collection 'b', which doesn't exist. -assert.commandWorked( - mydb.runCommand({aggregate: "a", pipeline: [{$out: {db: dbName, coll: "b"}}], cursor: {}})); -// Now test the scenario where we rename collection 'b' to collection 'c', which does exist. -// This should drop collection 'c'. -assert.commandWorked( - mydb.runCommand({aggregate: "b", pipeline: [{$out: {db: dbName, coll: "c"}}], cursor: {}})); - -// Now commit the transaction. -assert.commandWorked(session.commitTransaction_forTesting()); diff --git a/jstests/core/txns/prepare_conflict_aggregation_behavior.js b/jstests/core/txns/prepare_conflict_aggregation_behavior.js index c07f96e2b54..c62b7370dc6 100644 --- a/jstests/core/txns/prepare_conflict_aggregation_behavior.js +++ b/jstests/core/txns/prepare_conflict_aggregation_behavior.js @@ -3,12 +3,7 @@ * should not block on prepare conflicts, but writing out to a collection as a part of an aggregate * pipeline should block on prepare conflicts. * - * The test runs commands that are not allowed with security token: endSession, prepareTransaction. - * @tags: [ - * references_foreign_collection, - * uses_transactions, - * uses_prepare_transaction, - * ] + * @tags: [uses_transactions, uses_prepare_transaction] */ (function() { "use strict"; diff --git a/jstests/core/txns/transaction_too_large_for_cache.js b/jstests/core/txns/transaction_too_large_for_cache.js deleted file mode 100644 index 47171a0ee73..00000000000 --- a/jstests/core/txns/transaction_too_large_for_cache.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Tests a multi-document transaction requiring more cache than available fails with the expected - * error code instead of a generic WriteConflictException. - * - * @tags: [ - * does_not_support_config_fuzzer, - * requires_fcv_60, - * requires_persistence, - * requires_non_retryable_writes, - * requires_wiredtiger, - * uses_transactions, - * ] - */ - -(function() { -load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers. -load('jstests/libs/transactions_util.js'); - -function getWtCacheSizeBytes() { - let serverStatus; - if (FixtureHelpers.isReplSet(db) || FixtureHelpers.isMongos(db)) { - serverStatus = FixtureHelpers.getPrimaries(db)[0].getDB('admin').serverStatus(); - } else { - serverStatus = db.serverStatus(); - } - - assert.commandWorked(serverStatus); - return serverStatus.wiredTiger.cache["maximum bytes configured"]; -} - -const doc1 = { - x: [] -}; -for (var j = 0; j < 100000; j++) { - doc1.x.push("" + Math.random() + Math.random()); -} - -const session = db.getMongo().startSession(); -const sessionDb = session.getDatabase(db.getName()); -const coll = sessionDb[jsTestName()]; -coll.drop(); - -// Scale the load in proportion to WT cache size, to reduce test run time. -// A single collection can only have up to 64 indexes. Cap at _id + 1 text index + 62 indexes. -const nIndexes = Math.min(Math.ceil(getWtCacheSizeBytes() * 2 / (1024 * 1024 * 1024)), 62); -assert.commandWorked(coll.createIndex({x: "text"})); -for (let i = 0; i < nIndexes; i++) { - assert.commandWorked(coll.createIndex({x: 1, ["field" + i]: 1})); -} - -// Retry the transaction until we eventually hit the TransactionTooLargeForCache. Only retry on -// WriteConflict error, which is the only expected error besides TransactionTooLargeForCache. -assert.soon(() => { - session.startTransaction(); - - // Keep inserting documents in the transaction until we eventually hit the cache limit. - let insertCount = 0; - let result; - try { - while (true) { - try { - ++insertCount; - result = coll.insert(doc1); - assert.commandWorked(result); - } catch (e) { - session.abortTransaction(); - assert.commandFailedWithCode(result, ErrorCodes.TransactionTooLargeForCache); - break; - } - } - } catch (e) { - assert.commandFailedWithCode(result, ErrorCodes.WriteConflict); - return false; - } - - // The error should not have a transient transaction error label. At this point the error must - // have been TransactionTooLargeForCache. We do this check here to avoid having to check - // exception types in the outermost catch, in case this assertion fires. - assert(!TransactionsUtil.isTransientTransactionError(result), result); - - jsTestLog("Iterations until TransactionTooLargeForCache occured: " + insertCount); - - return true; -}, "Expected a transaction to eventually fail with TransactionTooLargeForCache error."); -}()); diff --git a/jstests/core/views/invalid_system_views.js b/jstests/core/views/invalid_system_views.js index 15b4ab6bc3b..25e973ed9a1 100644 --- a/jstests/core/views/invalid_system_views.js +++ b/jstests/core/views/invalid_system_views.js @@ -13,8 +13,6 @@ * requires_replication, * # The drop of offending views may not happen on the donor after a committed migration. * tenant_migration_incompatible, - * uses_compact, - * references_foreign_collection, * ] */ diff --git a/jstests/core/views/views_aggregation.js b/jstests/core/views/views_aggregation.js index 287bc5af667..537ec3ee008 100644 --- a/jstests/core/views/views_aggregation.js +++ b/jstests/core/views/views_aggregation.js @@ -9,7 +9,6 @@ * requires_non_retryable_commands, * # Explain of a resolved view must be executed by mongos. * directly_against_shardsvrs_incompatible, - * references_foreign_collection, * ] */ (function() { diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js index 7884b8fb6b2..45f19278511 100644 --- a/jstests/core/views/views_all_commands.js +++ b/jstests/core/views/views_all_commands.js @@ -12,7 +12,6 @@ // tenant_migration_incompatible, // # Explain of a resolved view must be executed by mongos. // directly_against_shardsvrs_incompatible, -// uses_compact // ] /* @@ -127,7 +126,6 @@ let viewsCommandTests = { _configsvrShardCollection: {skip: isAnInternalCommand}, // TODO SERVER-58843: Remove once 6.0 becomes last LTS _configsvrUpdateZoneKeyRange: {skip: isAnInternalCommand}, - _dropConnectionsToMongot: {skip: isAnInternalCommand}, _flushDatabaseCacheUpdates: {skip: isUnrelated}, _flushDatabaseCacheUpdatesWithWriteConcern: {skip: isUnrelated}, _flushReshardingStateChange: {skip: isUnrelated}, @@ -141,7 +139,6 @@ let viewsCommandTests = { _killOperations: {skip: isUnrelated}, _mergeAuthzCollections: {skip: isAnInternalCommand}, _migrateClone: {skip: isAnInternalCommand}, - _mongotConnPoolStats: {skip: isAnInternalCommand}, _movePrimary: {skip: isAnInternalCommand}, _recvChunkAbort: {skip: isAnInternalCommand}, _recvChunkCommit: {skip: isAnInternalCommand}, @@ -283,7 +280,6 @@ let viewsCommandTests = { assert.commandWorked(conn.runCommand({dropAllRolesFromDatabase: 1})); } }, - createSearchIndexes: {skip: isUnrelated}, createUser: { command: {createUser: "testuser", pwd: "testpass", roles: []}, setup: function(conn) { @@ -349,7 +345,6 @@ let viewsCommandTests = { assert.commandWorked(conn.runCommand({dropAllRolesFromDatabase: 1})); } }, - dropSearchIndex: {skip: isUnrelated}, dropUser: {skip: isUnrelated}, echo: {skip: isUnrelated}, emptycapped: { @@ -376,6 +371,7 @@ let viewsCommandTests = { getCmdLineOpts: {skip: isUnrelated}, getDefaultRWConcern: {skip: isUnrelated}, getDiagnosticData: {skip: isUnrelated}, + getFreeMonitoringStatus: {skip: isUnrelated}, getLastError: {skip: isUnrelated}, getLog: {skip: isUnrelated}, getMore: { @@ -478,7 +474,6 @@ let viewsCommandTests = { listCommands: {skip: isUnrelated}, listDatabases: {skip: isUnrelated}, listIndexes: {command: {listIndexes: "view"}, expectFailure: true}, - listSearchIndexes: {skip: isUnrelated}, listShards: {skip: isUnrelated}, lockInfo: {skip: isUnrelated}, logApplicationMessage: {skip: isUnrelated}, @@ -546,6 +541,7 @@ let viewsCommandTests = { skipSharded: true, } ], + repairDatabase: {skip: isUnrelated}, repairShardedCollectionChunksHistory: { command: {repairShardedCollectionChunksHistory: "test.view"}, skipStandalone: true, @@ -609,7 +605,6 @@ let viewsCommandTests = { rotateCertificates: {skip: isUnrelated}, saslContinue: {skip: isUnrelated}, saslStart: {skip: isUnrelated}, - sbe: {skip: isAnInternalCommand}, serverStatus: {command: {serverStatus: 1}, skip: isUnrelated}, setChangeStreamOptions: {skip: isUnrelated}, // TODO SERVER-65353 remove in 6.1. setIndexCommitQuorum: {skip: isUnrelated}, @@ -617,7 +612,7 @@ let viewsCommandTests = { setCommittedSnapshot: {skip: isAnInternalCommand}, setDefaultRWConcern: {skip: isUnrelated}, setFeatureCompatibilityVersion: {skip: isUnrelated}, - setProfilingFilterGlobally: {skip: isUnrelated}, + setFreeMonitoring: {skip: isUnrelated}, setParameter: {skip: isUnrelated}, setShardVersion: {skip: isUnrelated}, setClusterParameter: {skip: isUnrelated}, @@ -690,7 +685,6 @@ let viewsCommandTests = { assert.commandWorked(conn.runCommand({dropAllRolesFromDatabase: 1})); } }, - updateSearchIndex: {skip: isUnrelated}, updateUser: {skip: isUnrelated}, updateZoneKeyRange: {skip: isUnrelated}, usersInfo: {skip: isUnrelated}, diff --git a/jstests/core/views/views_collation.js b/jstests/core/views/views_collation.js index 65deb11edbc..169e9309740 100644 --- a/jstests/core/views/views_collation.js +++ b/jstests/core/views/views_collation.js @@ -6,7 +6,6 @@ // requires_non_retryable_commands, // # Explain of a resolved view must be executed by mongos. // directly_against_shardsvrs_incompatible, -// references_foreign_collection, // ] /** diff --git a/jstests/core/views/views_creation.js b/jstests/core/views/views_creation.js index c24a008342d..9528199996a 100644 --- a/jstests/core/views/views_creation.js +++ b/jstests/core/views/views_creation.js @@ -11,7 +11,6 @@ * requires_non_retryable_commands, * # Tenant migrations don't support applyOps. * tenant_migration_incompatible, - * references_foreign_collection, * ] */ (function() { diff --git a/jstests/core/views/views_validation.js b/jstests/core/views/views_validation.js index 4dea7c9491b..02c060fd50a 100644 --- a/jstests/core/views/views_validation.js +++ b/jstests/core/views/views_validation.js @@ -2,7 +2,6 @@ // # Running getCollection on views in sharded suites tries to shard views, which fails. // assumes_unsharded_collection, // requires_non_retryable_commands, -// references_foreign_collection, // ] (function() { diff --git a/jstests/core/wildcard_index_validindex.js b/jstests/core/wildcard_index_validindex.js index c4a2ea9c2ac..24ecdcf77cf 100644 --- a/jstests/core/wildcard_index_validindex.js +++ b/jstests/core/wildcard_index_validindex.js @@ -98,18 +98,6 @@ assert.commandFailedWithCode(coll.createIndex({"$**": "wildcard"}), ErrorCodes.C // Cannot create a compound wildcard index. assert.commandFailedWithCode(coll.createIndex({"$**": 1, "a": 1}), ErrorCodes.CannotCreateIndex); assert.commandFailedWithCode(coll.createIndex({"a": 1, "$**": 1}), ErrorCodes.CannotCreateIndex); -assert.commandFailedWithCode(coll.createIndex({"obj.$**": 1, "a": 1}), - ErrorCodes.CannotCreateIndex); -assert.commandFailedWithCode(db.runCommand({ - createIndexes: kCollectionName, - indexes: [{wildcardProjection: {a: 1, c: 1, d: 1}, key: {"$**": 1, b: 1}}] -}), - ErrorCodes.CannotCreateIndex); -assert.commandFailedWithCode(db.runCommand({ - createIndexes: kCollectionName, - indexes: [{key: {"$**": 1, b: 1}, wildcardProjection: {a: 1, c: 1, d: 1}}] -}), - ErrorCodes.CannotCreateIndex); // Cannot create an wildcard index with an invalid spec. assert.commandFailedWithCode(coll.createIndex({"a.$**.$**": 1}), ErrorCodes.CannotCreateIndex); @@ -153,9 +141,4 @@ assert.commandFailedWithCode( assert.commandFailedWithCode( createIndexHelper({"a.$**": 1}, {name: kIndexName, wildcardProjection: {b: 0}}), ErrorCodes.FailedToParse); -assert.commandFailedWithCode(db.runCommand({ - createIndexes: kCollectionName, - indexes: [{wildcardProjection: {b: 1}, key: {"a.$**": 1}}] -}), - ErrorCodes.FailedToParse); })(); |
