diff options
Diffstat (limited to 'jstests/aggregation/sources/lookup')
3 files changed, 112 insertions, 158 deletions
diff --git a/jstests/aggregation/sources/lookup/lookup_absorb_match.js b/jstests/aggregation/sources/lookup/lookup_absorb_match.js index be589615f5c..4220cbcb71a 100644 --- a/jstests/aggregation/sources/lookup/lookup_absorb_match.js +++ b/jstests/aggregation/sources/lookup/lookup_absorb_match.js @@ -136,7 +136,7 @@ expected = [{_id: "dog", locationId: "doghouse", location: {_id: "doghouse", coordinates: [25.0, 60.0]}}]; assert.eq(result, expected); -// Test that a $match with $jsonSchema works as expected although ineligable for absorbtion by a +// Test that a $match with $jsonSchema works as expected although ineligible for absorbtion by a // $lookup. result = testDB.animals .aggregate([ @@ -167,7 +167,7 @@ expected = [{_id: "bull", locationId: "bullpen", location: {_id: "bullpen", coordinates: [-25.0, -60.0]}}]; assert.eq(result, expected); -// Test that a more complex $match with $jsonSchema works as expected although ineligable for +// Test that a more complex $match with $jsonSchema works as expected although ineligible for // absorbtion by a $lookup. result = testDB.animals .aggregate([ @@ -194,8 +194,100 @@ expected = [{_id: "bull", locationId: "bullpen", location: {_id: "bullpen", coordinates: [-25.0, -60.0]}}]; assert.eq(result, expected); -// Test that a $match with $alwaysTrue works as expected although ineligable for absorbtion by a -// $lookup. +// Test that $match with a $jsonSchema property that will internally translate to a match +// expression node that has a path that is prefixed by the 'as' field in the lookup and that has +// children that can operate on that path (in this case, $_internalSchemaAllElemMatchFromIndex) +// works as expected although ineligible for absorbtion by a $lookup. Note that the jsonSchema below +// ensures that all elements of 'location.coordinates' are above 0 since the 'items' field is an +// object. +result = testDB.animals + .aggregate([ + { + $lookup: { + from: "locations", + localField: "locationId", + foreignField: "_id", + as: "location" + } + }, + {$unwind: "$location"}, + { + $match: { + $jsonSchema: { + properties: {"location.coordinates": {items: {minimum: 0}}} + } + } + }, + {$project: {"location.extra": false, "colors": false}} + ]) + .toArray(); + +expected = + [{_id: "dog", locationId: "doghouse", location: {_id: "doghouse", coordinates: [25.0, 60.0]}}]; +assert.eq(result, expected); + +// Test that $match with a $jsonSchema property that will internally translate to a match +// expression node that has a path that is prefixed by the 'as' field in the lookup and that has +// children that can operate on that path (in this case, $_internalSchemaMatchArrayIndex) works as +// expected although ineligible for absorbtion by a $lookup. Note that the jsonSchema below ensures +// that the first element of 'location.coordinates' is above 0 since the 'items' field is an array. +result = testDB.animals + .aggregate([ + { + $lookup: { + from: "locations", + localField: "locationId", + foreignField: "_id", + as: "location" + } + }, + {$unwind: "$location"}, + { + $match: { + $jsonSchema: { + properties: {"location.coordinates": {items: [{minimum: 0}]}} + } + } + }, + {$project: {"location.extra": false, "colors": false}} + ]) + .toArray(); + +expected = + [{_id: "dog", locationId: "doghouse", location: {_id: "doghouse", coordinates: [25.0, 60.0]}}]; +assert.eq(result, expected); + +// Test that $match with a $jsonSchema property that will internally translate to a match +// expression node that has a path that is prefixed by the 'as' field in the lookup and that has +// children that can operate that path (in this case, $_internalSchemaObjectMatch) works as expected +// although ineligible for absorbtion by a $lookup. +result = testDB.animals + .aggregate([ + { + $lookup: { + from: "locations", + localField: "locationId", + foreignField: "_id", + as: "location" + } + }, + {$unwind: "$location"}, + { + $match: { + $jsonSchema: { + properties: {"location.extra": {type: 'object', properties: {"breeds": {type: 'string'}}}} + } + } + }, + {$project: {"location.extra": false, "colors": false}} + ]) + .toArray(); +expected = + [{_id: "bull", locationId: "bullpen", location: {_id: "bullpen", coordinates: [-25.0, -60.0]}}]; +assert.eq(result, expected); + +// Test that a $match with $alwaysTrue works as expected although ineligible for absorbtion +// by a $lookup. The $sort is to guarantee records are returned in the expected order. result = testDB.animals .aggregate([ { @@ -220,7 +312,7 @@ expected = [ ]; assert.eq(result, expected); -// Test that a $match with $alwaysFalse works as expected although ineligable for absorbtion by a +// Test that a $match with $alwaysFalse works as expected although ineligible for absorbtion by a // $lookup. result = testDB.animals .aggregate([ @@ -242,7 +334,7 @@ result = testDB.animals expected = []; assert.eq(result, expected); -// Test that a $match with $expr works as expected although ineligable for absorbtion by a $lookup. +// Test that a $match with $expr works as expected although ineligible for absorbtion by a $lookup. result = testDB.animals .aggregate([ { diff --git a/jstests/aggregation/sources/lookup/lookup_collation.js b/jstests/aggregation/sources/lookup/lookup_collation.js index 50ec057ae79..36f18b7e44c 100644 --- a/jstests/aggregation/sources/lookup/lookup_collation.js +++ b/jstests/aggregation/sources/lookup/lookup_collation.js @@ -3,14 +3,11 @@ * when performing comparisons on a foreign collection with a different default collation. Exercises * the fix for SERVER-43350. * - * Collation can be set at three different levels for $lookup stage + * Collation can be set at two different levels for $lookup stage * 1. on the local collection (collation on the foreign collection is always ignored) - * 2. on the $lookup stage via '_internalCollation' property - * 3. on the aggregation command via 'collation' property in options + * 2. on the aggregation command via 'collation' property in options * - * The three settings have the following precedence: - * 1. '_internalCollation' overrides all others - * 2. 'collation' option overrides local collection's collation + * The 'collation' command option overrides local collection's collation. */ load("jstests/aggregation/extras/utils.js"); // For anyEq. load("jstests/libs/analyze_plan.js"); // For getAggPlanStages, getWinningPlan. @@ -137,29 +134,6 @@ let explain; } })(); -// Collation set on $lookup stage with '_internalCollation' should override collation of the local -// collection and on the command. -(function testStageCollationPrecedence() { - for (let lookupInto of [lookupWithPipeline, lookupNoPipeline]) { - let lookupStage = lookupInto(collAa); - lookupStage.$lookup._internalCollation = caseInsensitive; - results = collAa.aggregate([lookupStage], {collation: caseSensitive}).toArray(); - assertArrayEq({ - actual: results, - expected: resultCaseInsensitive, - extraErrorMsg: " Case-insensitive collation on stage, running: " + tojson(lookupInto) - }); - - lookupStage.$lookup._internalCollation = caseSensitive; - results = collAA.aggregate([lookupStage], {collation: caseInsensitive}).toArray(); - assertArrayEq({ - actual: results, - expected: resultCaseSensistive, - extraErrorMsg: " Case-sensitive collation on stage, running: " + tojson(lookupInto) - }); - } -})(); - // In presense of indexes lookup might choose a different strategy for the join, that relies on the // index (INLJ). It should respect the effective collation of $lookup. (function testCollationWithIndexes() { @@ -213,19 +187,6 @@ let explain; explain = collAa.explain().aggregate([lookupInto(collAa_indexed)], {collation: {locale: "fr"}, allowDiskUse: false}); assertNestedLoopJoinStrategy(explain); - - // Stage-level collation overrides collection-level and command-level collations. - let lookupStage = lookupInto(collAa_indexed); - lookupStage.$lookup._internalCollation = caseInsensitive; - results = collAa.aggregate([lookupStage], {collation: caseSensitive}).toArray(); - assertArrayEq({ - actual: results, - expected: resultCaseInsensitive, - extraErrorMsg: " Case-insensitive collation on stage, foreign is indexed, running: " + - tojson(lookupInto) - }); - explain = collAa.explain().aggregate([lookupStage], {collation: caseSensitive}); - assertIndexJoinStrategy(explain); } })(); })(); diff --git a/jstests/aggregation/sources/lookup/lookup_foreign_collation.js b/jstests/aggregation/sources/lookup/lookup_foreign_collation.js index 6937fe42854..7d6e9a4578e 100644 --- a/jstests/aggregation/sources/lookup/lookup_foreign_collation.js +++ b/jstests/aggregation/sources/lookup/lookup_foreign_collation.js @@ -96,12 +96,10 @@ function setup() { // localColl: Local Collection // foreignColl: Foreign Collection // commandCollation: Collation set on the aggregate command. Pass null for default collation. - // lookupCollation: Collation specified in the $lookup stage. Pass null for default - // collation. expectedResults: Results expected from the aggregate invocation + // expectedResults: Results expected from the aggregate invocation // - function assertExpectedResultSet( - localColl, foreignColl, commandCollation, lookupCollation, expectedResults) { - const lookupWithPipeline = {$lookup: {from: foreignColl.getName(), + function assertExpectedResultSet(localColl, foreignColl, commandCollation, expectedResults) { + const lookupWithPipeline = {$lookup: {from: foreignColl.getName(), as: "foreignMatch", let: {l_id: "$_id"}, pipeline: [{$match: {$expr: {$eq: ["$_id", "$$l_id"]}}}]}}; @@ -111,11 +109,6 @@ function setup() { foreignField: "_id", as: "foreignMatch"}}; - if (lookupCollation) { - lookupWithPipeline.$lookup._internalCollation = lookupCollation; - lookupWithLocalForeignField.$lookup._internalCollation = lookupCollation; - } - const aggOptions = {}; if (commandCollation) { aggOptions.collation = commandCollation; @@ -129,112 +122,20 @@ function setup() { } // Baseline test, confirming simple binary comparison when no collation has been specified on - // the command, $lookup stage or collections. - assertExpectedResultSet(localColl, foreignColl, null, null, resultSetCaseSensitive); + // the command or the collection. + assertExpectedResultSet(localColl, foreignColl, null, resultSetCaseSensitive); // When a collation has been specified on the $lookup stage, it will always be used to join // local and foreign collections. for (const local of [localColl, localCaseInsensitiveColl]) { for (const foreign of [foreignColl, foreignCaseInsensitiveColl]) { - for (const command of [null, simpleCollation, caseInsensitiveCollation]) { - // Case insensitive collation specified in the $lookup stage results in a case - // insensitive join. - assertExpectedResultSet( - local, foreign, command, caseInsensitiveCollation, resultSetCaseInsensitive); - - // Simple collation specified in the $lookup stage results in a case sensitive join. - assertExpectedResultSet( - local, foreign, command, simpleCollation, resultSetCaseSensitive); - } + // Case insensitive collation results in a case insensitive join. + assertExpectedResultSet( + local, foreign, caseInsensitiveCollation, resultSetCaseInsensitive); + + // Simple collation results in a case sensitive join. + assertExpectedResultSet(local, foreign, simpleCollation, resultSetCaseSensitive); } } })(); - -(function testNestedLookupStagesWithDifferentCollations() { - setup(); - - const lookupWithPipeline = {$lookup: {from: foreignColl.getName(), - as: "foreignMatch", - let: {l_id: "$_id"}, - pipeline: [{$match: {$expr: {$eq: ["$_id", "$$l_id"]}}}, - {$lookup: {from: localColl.getName(), - as: "foreignMatch2", - let: {l_id: "$_id"}, - pipeline: [{$match: {$expr: {$eq: ["$_id", "$$l_id"]}}}], - _internalCollation: simpleCollation}}], - _internalCollation: caseInsensitiveCollation}}; - - const resultSet = [ - {_id: "a", foreignMatch: [{_id: "a", "foreignMatch2": [{"_id": "a"}]}]}, - {_id: "b", foreignMatch: [{_id: "B", "foreignMatch2": []}]}, - {_id: "c", foreignMatch: [{_id: "c", "foreignMatch2": [{"_id": "c"}]}]}, - {_id: "d", foreignMatch: [{_id: "D", "foreignMatch2": []}]}, - {_id: "e", foreignMatch: [{_id: "e", "foreignMatch2": [{"_id": "e"}]}]} - ]; - - const results = localColl.aggregate([lookupWithPipeline]).toArray(); - assert(anyEq(results, resultSet), tojson(results)); -})(); - -(function testMatchOnUnwoundAsFieldAbsorptionOptimization() { - setup(); - - // A $lookup stage with a collation that differs from the collection and command collation - // will not absorb a $match on unwound results. - let pipeline = [{$lookup: {from: foreignColl.getName(), - as: "foreignMatch", - let: {l_id: "$_id"}, - pipeline: [{$match: {$expr: {$eq: ["$_id", "$$l_id"]}}}], - _internalCollation: caseInsensitiveCollation}}, - {$unwind: "$foreignMatch"}, - {$match: {"foreignMatch._id": "b"}}]; - - let results = localColl.aggregate(pipeline).toArray(); - assert.eq(0, results.length); - - let explain = localColl.explain().aggregate(pipeline); - let lastStage = explain.stages[explain.stages.length - 1]; - assert(lastStage.hasOwnProperty("$match"), tojson(explain)); - assert.eq({$match: {"foreignMatch._id": {$eq: "b"}}}, - lastStage, - "The $match stage should not be optimized into the $lookup stage" + tojson(explain)); - - // A $lookup stage with a collation that matches the command collation will absorb a $match - // stage. - pipeline = [{$lookup: {from: foreignColl.getName(), - as: "foreignMatch", - let: {l_id: "$_id"}, - pipeline: [{$match: {$expr: {$eq: ["$_id", "$$l_id"]}}}], - _internalCollation: caseInsensitiveCollation}}, - {$unwind: "$foreignMatch"}, - {$match: {"foreignMatch._id": "b"}}]; - - let expectedResults = [{"_id": "b", "foreignMatch": {"_id": "B"}}]; - - results = localColl.aggregate(pipeline, {collation: caseInsensitiveCollation}).toArray(); - assert(anyEq(results, expectedResults), tojson(results)); - - explain = localColl.explain().aggregate(pipeline, {collation: caseInsensitiveCollation}); - lastStage = explain.stages[explain.stages.length - 1]; - assert(lastStage.hasOwnProperty("$lookup"), tojson(explain)); - - // A $lookup stage with a collation that matches the local collection collation will absorb - // a $match stage. - pipeline = [{$lookup: {from: foreignColl.getName(), - as: "foreignMatch", - let: {l_id: "$_id"}, - pipeline: [{$match: {$expr: {$eq: ["$_id", "$$l_id"]}}}], - _internalCollation: caseInsensitiveCollation}}, - {$unwind: "$foreignMatch"}, - {$match: {"foreignMatch._id": "b"}}]; - - expectedResults = [{"_id": "b", "foreignMatch": {"_id": "B"}}]; - - results = localCaseInsensitiveColl.aggregate(pipeline).toArray(); - assert(anyEq(results, expectedResults), tojson(results)); - - explain = localCaseInsensitiveColl.explain().aggregate(pipeline); - lastStage = explain.stages[explain.stages.length - 1]; - assert(lastStage.hasOwnProperty("$lookup"), tojson(explain)); -})(); })(); |
