diff --git a/jstests/query_golden/expected_output/featureFlagSbeFull/basic_joins.md b/jstests/query_golden/expected_output/featureFlagSbeFull/basic_joins.md index e6f83f5098c..f62a6af8233 100644 --- a/jstests/query_golden/expected_output/featureFlagSbeFull/basic_joins.md +++ b/jstests/query_golden/expected_output/featureFlagSbeFull/basic_joins.md @@ -1604,3 +1604,328 @@ rightEmbeddingField: "k.y.z" COLLSCAN [test.basic_joins_md] direction: "forward" ``` +## 6. Basic example with a $project excluding a field from the base collection +### No join opt +### Pipeline +```json +[ + { + "$project" : { + "_id" : false + } + }, + { + "$lookup" : { + "from" : "basic_joins_md_foreign1", + "as" : "x", + "localField" : "a", + "foreignField" : "a" + } + }, + { + "$unwind" : "$x" + }, + { + "$lookup" : { + "from" : "basic_joins_md_foreign2", + "as" : "y", + "localField" : "b", + "foreignField" : "b" + } + }, + { + "$unwind" : "$y" + } +] +``` +### Results +```json +{ "a" : 1, "b" : "bar", "x" : { "_id" : 0, "a" : 1, "c" : "zoo", "d" : 1 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 1, "b" : "bar", "x" : { "_id" : 0, "a" : 1, "c" : "zoo", "d" : 1 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 1, "a" : 2, "c" : "blah", "d" : 2 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 1, "a" : 2, "c" : "blah", "d" : 2 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 2, "a" : 2, "c" : "x", "d" : 3 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 2, "a" : 2, "c" : "x", "d" : 3 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +``` +### Summarized explain +Execution Engine: sbe +```json +{ + "queryShapeHash" : "9A2C9BDFA638F0C7F703251AC894A297CDDB8AF81B931E490B488BCF87386796", + "rejectedPlans" : [ ], + "winningPlan" : { + "asField" : "y", + "foreignCollection" : "test.basic_joins_md_foreign2", + "foreignField" : "b", + "inputStage" : { + "asField" : "x", + "foreignCollection" : "test.basic_joins_md_foreign1", + "foreignField" : "a", + "inputStage" : { + "inputStage" : { + "direction" : "forward", + "filter" : { + + }, + "nss" : "test.basic_joins_md", + "stage" : "COLLSCAN" + }, + "stage" : "PROJECTION_SIMPLE", + "transformBy" : { + "_id" : false + } + }, + "localField" : "a", + "scanDirection" : "forward", + "stage" : "EQ_LOOKUP_UNWIND", + "strategy" : "HashJoin" + }, + "localField" : "b", + "planNodeId" : 4, + "scanDirection" : "forward", + "stage" : "EQ_LOOKUP_UNWIND", + "strategy" : "HashJoin" + } +} +``` + +### With bottom-up plan enumeration (left-deep) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` +### With bottom-up plan enumeration (right-deep) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "y" +rightEmbeddingField: "none" + | | + | HASH_JOIN_EMBEDDING [a = a] + | leftEmbeddingField: "none" + | rightEmbeddingField: "x" + | | | + | | COLLSCAN [test.basic_joins_md_foreign1] + | | direction: "forward" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With bottom-up plan enumeration (zig-zag) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` +### With random order, seed 44, nested loop joins +usedJoinOptimization: true + +``` +NESTED_LOOP_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + NESTED_LOOP_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "x" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign1] + direction: "forward" +``` +### With random order, seed 44, hash join enabled +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "x" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign1] + direction: "forward" +``` +### With random order, seed 45, nested loop joins +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With random order, seed 45, hash join enabled +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With fixed order, index join +usedJoinOptimization: true + +``` +INDEXED_NESTED_LOOP_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | FETCH [test.basic_joins_md_foreign1] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign1] + | keyPattern: { "a" : 1 } + | indexName: "a_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + NESTED_LOOP_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With bottom-up plan enumeration and indexes +usedJoinOptimization: true + +``` +INDEXED_NESTED_LOOP_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | FETCH [test.basic_joins_md_foreign2] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign2] + | keyPattern: { "b" : 1 } + | indexName: "b_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + INDEXED_NESTED_LOOP_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | FETCH [test.basic_joins_md_foreign1] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign1] + | keyPattern: { "a" : 1 } + | indexName: "a_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` diff --git a/jstests/query_golden/expected_output/sbeDisabled/basic_joins.md b/jstests/query_golden/expected_output/sbeDisabled/basic_joins.md index 5d45005d75e..92ecae8d3fc 100644 --- a/jstests/query_golden/expected_output/sbeDisabled/basic_joins.md +++ b/jstests/query_golden/expected_output/sbeDisabled/basic_joins.md @@ -3233,3 +3233,335 @@ rightEmbeddingField: "k.y.z" COLLSCAN [test.basic_joins_md] direction: "forward" ``` +## 10. Basic example with a $project excluding a field from the base collection +### No join opt +### Pipeline +```json +[ + { + "$project" : { + "_id" : false + } + }, + { + "$lookup" : { + "from" : "basic_joins_md_foreign1", + "as" : "x", + "localField" : "a", + "foreignField" : "a" + } + }, + { + "$unwind" : "$x" + }, + { + "$lookup" : { + "from" : "basic_joins_md_foreign2", + "as" : "y", + "localField" : "b", + "foreignField" : "b" + } + }, + { + "$unwind" : "$y" + } +] +``` +### Results +```json +{ "a" : 1, "b" : "bar", "x" : { "_id" : 0, "a" : 1, "c" : "zoo", "d" : 1 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 1, "b" : "bar", "x" : { "_id" : 0, "a" : 1, "c" : "zoo", "d" : 1 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 1, "a" : 2, "c" : "blah", "d" : 2 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 1, "a" : 2, "c" : "blah", "d" : 2 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 2, "a" : 2, "c" : "x", "d" : 3 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 2, "a" : 2, "c" : "x", "d" : 3 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +``` +### Summarized explain +Execution Engine: classic +```json +{ + "queryShapeHash" : "9A2C9BDFA638F0C7F703251AC894A297CDDB8AF81B931E490B488BCF87386796", + "stages" : [ + { + "$cursor" : { + "rejectedPlans" : [ ], + "winningPlan" : { + "inputStage" : { + "direction" : "forward", + "nss" : "test.basic_joins_md", + "stage" : "COLLSCAN" + }, + "isCached" : false, + "stage" : "PROJECTION_SIMPLE", + "transformBy" : { + "_id" : false + } + } + } + }, + { + "$lookup" : { + "as" : "x", + "foreignField" : "a", + "from" : "basic_joins_md_foreign1", + "localField" : "a", + "unwinding" : { + "preserveNullAndEmptyArrays" : false + } + } + }, + { + "$lookup" : { + "as" : "y", + "foreignField" : "b", + "from" : "basic_joins_md_foreign2", + "localField" : "b", + "unwinding" : { + "preserveNullAndEmptyArrays" : false + } + } + } + ] +} +``` + +### With bottom-up plan enumeration (left-deep) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` +### With bottom-up plan enumeration (right-deep) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "y" +rightEmbeddingField: "none" + | | + | HASH_JOIN_EMBEDDING [a = a] + | leftEmbeddingField: "none" + | rightEmbeddingField: "x" + | | | + | | COLLSCAN [test.basic_joins_md_foreign1] + | | direction: "forward" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With bottom-up plan enumeration (zig-zag) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` +### With random order, seed 44, nested loop joins +usedJoinOptimization: true + +``` +NESTED_LOOP_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + NESTED_LOOP_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "x" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign1] + direction: "forward" +``` +### With random order, seed 44, hash join enabled +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "x" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign1] + direction: "forward" +``` +### With random order, seed 45, nested loop joins +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With random order, seed 45, hash join enabled +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With fixed order, index join +usedJoinOptimization: true + +``` +INDEXED_NESTED_LOOP_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | FETCH [test.basic_joins_md_foreign1] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign1] + | keyPattern: { "a" : 1 } + | indexName: "a_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + NESTED_LOOP_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With bottom-up plan enumeration and indexes +usedJoinOptimization: true + +``` +INDEXED_NESTED_LOOP_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | FETCH [test.basic_joins_md_foreign2] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign2] + | keyPattern: { "b" : 1 } + | indexName: "b_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + INDEXED_NESTED_LOOP_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | FETCH [test.basic_joins_md_foreign1] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign1] + | keyPattern: { "a" : 1 } + | indexName: "a_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` diff --git a/jstests/query_golden/expected_output/sbeFull/basic_joins.md b/jstests/query_golden/expected_output/sbeFull/basic_joins.md index 3d1891c747d..2b7252aa023 100644 --- a/jstests/query_golden/expected_output/sbeFull/basic_joins.md +++ b/jstests/query_golden/expected_output/sbeFull/basic_joins.md @@ -3193,3 +3193,328 @@ rightEmbeddingField: "k.y.z" COLLSCAN [test.basic_joins_md] direction: "forward" ``` +## 10. Basic example with a $project excluding a field from the base collection +### No join opt +### Pipeline +```json +[ + { + "$project" : { + "_id" : false + } + }, + { + "$lookup" : { + "from" : "basic_joins_md_foreign1", + "as" : "x", + "localField" : "a", + "foreignField" : "a" + } + }, + { + "$unwind" : "$x" + }, + { + "$lookup" : { + "from" : "basic_joins_md_foreign2", + "as" : "y", + "localField" : "b", + "foreignField" : "b" + } + }, + { + "$unwind" : "$y" + } +] +``` +### Results +```json +{ "a" : 1, "b" : "bar", "x" : { "_id" : 0, "a" : 1, "c" : "zoo", "d" : 1 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 1, "b" : "bar", "x" : { "_id" : 0, "a" : 1, "c" : "zoo", "d" : 1 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 1, "a" : 2, "c" : "blah", "d" : 2 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 1, "a" : 2, "c" : "blah", "d" : 2 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 2, "a" : 2, "c" : "x", "d" : 3 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 2, "a" : 2, "c" : "x", "d" : 3 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +``` +### Summarized explain +Execution Engine: sbe +```json +{ + "queryShapeHash" : "9A2C9BDFA638F0C7F703251AC894A297CDDB8AF81B931E490B488BCF87386796", + "rejectedPlans" : [ ], + "winningPlan" : { + "asField" : "y", + "foreignCollection" : "test.basic_joins_md_foreign2", + "foreignField" : "b", + "inputStage" : { + "asField" : "x", + "foreignCollection" : "test.basic_joins_md_foreign1", + "foreignField" : "a", + "inputStage" : { + "inputStage" : { + "direction" : "forward", + "filter" : { + + }, + "nss" : "test.basic_joins_md", + "stage" : "COLLSCAN" + }, + "stage" : "PROJECTION_SIMPLE", + "transformBy" : { + "_id" : false + } + }, + "localField" : "a", + "scanDirection" : "forward", + "stage" : "EQ_LOOKUP_UNWIND", + "strategy" : "HashJoin" + }, + "localField" : "b", + "planNodeId" : 4, + "scanDirection" : "forward", + "stage" : "EQ_LOOKUP_UNWIND", + "strategy" : "HashJoin" + } +} +``` + +### With bottom-up plan enumeration (left-deep) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` +### With bottom-up plan enumeration (right-deep) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "y" +rightEmbeddingField: "none" + | | + | HASH_JOIN_EMBEDDING [a = a] + | leftEmbeddingField: "none" + | rightEmbeddingField: "x" + | | | + | | COLLSCAN [test.basic_joins_md_foreign1] + | | direction: "forward" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With bottom-up plan enumeration (zig-zag) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` +### With random order, seed 44, nested loop joins +usedJoinOptimization: true + +``` +NESTED_LOOP_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + NESTED_LOOP_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "x" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign1] + direction: "forward" +``` +### With random order, seed 44, hash join enabled +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "x" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign1] + direction: "forward" +``` +### With random order, seed 45, nested loop joins +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With random order, seed 45, hash join enabled +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With fixed order, index join +usedJoinOptimization: true + +``` +INDEXED_NESTED_LOOP_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | FETCH [test.basic_joins_md_foreign1] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign1] + | keyPattern: { "a" : 1 } + | indexName: "a_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + NESTED_LOOP_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With bottom-up plan enumeration and indexes +usedJoinOptimization: true + +``` +INDEXED_NESTED_LOOP_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | FETCH [test.basic_joins_md_foreign2] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign2] + | keyPattern: { "b" : 1 } + | indexName: "b_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + INDEXED_NESTED_LOOP_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | FETCH [test.basic_joins_md_foreign1] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign1] + | keyPattern: { "a" : 1 } + | indexName: "a_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` diff --git a/jstests/query_golden/expected_output/sbeRestricted/basic_joins.md b/jstests/query_golden/expected_output/sbeRestricted/basic_joins.md index 5d45005d75e..92ecae8d3fc 100644 --- a/jstests/query_golden/expected_output/sbeRestricted/basic_joins.md +++ b/jstests/query_golden/expected_output/sbeRestricted/basic_joins.md @@ -3233,3 +3233,335 @@ rightEmbeddingField: "k.y.z" COLLSCAN [test.basic_joins_md] direction: "forward" ``` +## 10. Basic example with a $project excluding a field from the base collection +### No join opt +### Pipeline +```json +[ + { + "$project" : { + "_id" : false + } + }, + { + "$lookup" : { + "from" : "basic_joins_md_foreign1", + "as" : "x", + "localField" : "a", + "foreignField" : "a" + } + }, + { + "$unwind" : "$x" + }, + { + "$lookup" : { + "from" : "basic_joins_md_foreign2", + "as" : "y", + "localField" : "b", + "foreignField" : "b" + } + }, + { + "$unwind" : "$y" + } +] +``` +### Results +```json +{ "a" : 1, "b" : "bar", "x" : { "_id" : 0, "a" : 1, "c" : "zoo", "d" : 1 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 1, "b" : "bar", "x" : { "_id" : 0, "a" : 1, "c" : "zoo", "d" : 1 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 1, "a" : 2, "c" : "blah", "d" : 2 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 1, "a" : 2, "c" : "blah", "d" : 2 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 2, "a" : 2, "c" : "x", "d" : 3 }, "y" : { "_id" : 0, "b" : "bar", "d" : 2 } } +{ "a" : 2, "b" : "bar", "x" : { "_id" : 2, "a" : 2, "c" : "x", "d" : 3 }, "y" : { "_id" : 1, "b" : "bar", "d" : 6 } } +``` +### Summarized explain +Execution Engine: classic +```json +{ + "queryShapeHash" : "9A2C9BDFA638F0C7F703251AC894A297CDDB8AF81B931E490B488BCF87386796", + "stages" : [ + { + "$cursor" : { + "rejectedPlans" : [ ], + "winningPlan" : { + "inputStage" : { + "direction" : "forward", + "nss" : "test.basic_joins_md", + "stage" : "COLLSCAN" + }, + "isCached" : false, + "stage" : "PROJECTION_SIMPLE", + "transformBy" : { + "_id" : false + } + } + } + }, + { + "$lookup" : { + "as" : "x", + "foreignField" : "a", + "from" : "basic_joins_md_foreign1", + "localField" : "a", + "unwinding" : { + "preserveNullAndEmptyArrays" : false + } + } + }, + { + "$lookup" : { + "as" : "y", + "foreignField" : "b", + "from" : "basic_joins_md_foreign2", + "localField" : "b", + "unwinding" : { + "preserveNullAndEmptyArrays" : false + } + } + } + ] +} +``` + +### With bottom-up plan enumeration (left-deep) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` +### With bottom-up plan enumeration (right-deep) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "y" +rightEmbeddingField: "none" + | | + | HASH_JOIN_EMBEDDING [a = a] + | leftEmbeddingField: "none" + | rightEmbeddingField: "x" + | | | + | | COLLSCAN [test.basic_joins_md_foreign1] + | | direction: "forward" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With bottom-up plan enumeration (zig-zag) +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` +### With random order, seed 44, nested loop joins +usedJoinOptimization: true + +``` +NESTED_LOOP_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + NESTED_LOOP_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "x" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign1] + direction: "forward" +``` +### With random order, seed 44, hash join enabled +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | COLLSCAN [test.basic_joins_md_foreign2] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "x" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign1] + direction: "forward" +``` +### With random order, seed 45, nested loop joins +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With random order, seed 45, hash join enabled +usedJoinOptimization: true + +``` +HASH_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | COLLSCAN [test.basic_joins_md_foreign1] + | direction: "forward" + | + HASH_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With fixed order, index join +usedJoinOptimization: true + +``` +INDEXED_NESTED_LOOP_JOIN_EMBEDDING [a = a] +leftEmbeddingField: "none" +rightEmbeddingField: "x" + | | + | FETCH [test.basic_joins_md_foreign1] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign1] + | keyPattern: { "a" : 1 } + | indexName: "a_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + NESTED_LOOP_JOIN_EMBEDDING [b = b] + leftEmbeddingField: "y" + rightEmbeddingField: "none" + | | + | PROJECTION_SIMPLE + | transformBy: { "_id" : false } + | | + | COLLSCAN [test.basic_joins_md] + | direction: "forward" + | + COLLSCAN [test.basic_joins_md_foreign2] + direction: "forward" +``` +### With bottom-up plan enumeration and indexes +usedJoinOptimization: true + +``` +INDEXED_NESTED_LOOP_JOIN_EMBEDDING [b = b] +leftEmbeddingField: "none" +rightEmbeddingField: "y" + | | + | FETCH [test.basic_joins_md_foreign2] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign2] + | keyPattern: { "b" : 1 } + | indexName: "b_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + INDEXED_NESTED_LOOP_JOIN_EMBEDDING [a = a] + leftEmbeddingField: "none" + rightEmbeddingField: "x" + | | + | FETCH [test.basic_joins_md_foreign1] + | + | | + | INDEX_PROBE_NODE [test.basic_joins_md_foreign1] + | keyPattern: { "a" : 1 } + | indexName: "a_1" + | isMultiKey: false + | isUnique: false + | isSparse: false + | isPartial: false + | + PROJECTION_SIMPLE + transformBy: { "_id" : false } + | + COLLSCAN [test.basic_joins_md] + direction: "forward" +``` diff --git a/jstests/query_golden/join_opt/basic_joins_md.js b/jstests/query_golden/join_opt/basic_joins_md.js index 1fdb89d49aa..7c6cb427fe2 100644 --- a/jstests/query_golden/join_opt/basic_joins_md.js +++ b/jstests/query_golden/join_opt/basic_joins_md.js @@ -315,3 +315,12 @@ runBasicJoinTest([ {$lookup: {from: foreignColl2.getName(), as: "k.y.z", localField: "w.y.d", foreignField: "d"}}, {$unwind: "$k.y.z"}, ]); + +section("Basic example with a $project excluding a field from the base collection"); +runBasicJoinTest([ + {$project: {_id: false}}, + {$lookup: {from: foreignColl1.getName(), as: "x", localField: "a", foreignField: "a"}}, + {$unwind: "$x"}, + {$lookup: {from: foreignColl2.getName(), as: "y", localField: "b", foreignField: "b"}}, + {$unwind: "$y"}, +]); diff --git a/src/mongo/db/query/stage_builder/sbe/gen_lookup.cpp b/src/mongo/db/query/stage_builder/sbe/gen_lookup.cpp index 173f1f7b7f8..653ade7c398 100644 --- a/src/mongo/db/query/stage_builder/sbe/gen_lookup.cpp +++ b/src/mongo/db/query/stage_builder/sbe/gen_lookup.cpp @@ -1655,8 +1655,11 @@ std::pair generateJoinResult(const BinaryJoinEmbeddingN nodes.emplace_back(leftOutputs.get(SlotBasedStageBuilder::kResult)); } for (const auto& field : fieldEffect.getFieldList()) { - if ((!node->rightEmbeddingField || field != node->rightEmbeddingField->fullPath()) && - (!node->leftEmbeddingField || field != node->leftEmbeddingField->fullPath())) { + if (!fieldEffect.isAllowedField(field)) { + paths.emplace_back(field); + nodes.emplace_back(ProjectNode::Drop{}); + } else if ((!node->rightEmbeddingField || field != node->rightEmbeddingField->fullPath()) && + (!node->leftEmbeddingField || field != node->leftEmbeddingField->fullPath())) { paths.emplace_back(field); // TODO: SERVER-113230 in case of conflict, we give priority to the left side. Depending // on the join graph, an embedding having the same name of a top-level field in the base