diff options
author | Charlie Swanson <cswanson310@gmail.com> | 2016-08-29 17:09:28 -0400 |
---|---|---|
committer | Charlie Swanson <cswanson310@gmail.com> | 2016-09-12 16:55:21 -0400 |
commit | 07e0d1ef934e54dc2d1df54b6f97a521b212b87d (patch) | |
tree | fa67d1d02e216f675afe9729e3e08d218a025816 | |
parent | 8383c8731ba18b8443e424783c5aa63a8aed9202 (diff) | |
download | mongo-07e0d1ef934e54dc2d1df54b6f97a521b212b87d.tar.gz |
SERVER-25757 Add a $facet passthrough aggregation suite.
-rw-r--r-- | buildscripts/resmokeconfig/suites/aggregation_facet_unwind_passthrough.yml | 25 | ||||
-rw-r--r-- | etc/evergreen.yml | 15 | ||||
-rw-r--r-- | jstests/aggregation/bugs/groupMissing.js | 4 | ||||
-rw-r--r-- | jstests/aggregation/bugs/server11675.js | 2 | ||||
-rw-r--r-- | jstests/aggregation/bugs/server22093.js | 4 | ||||
-rw-r--r-- | jstests/aggregation/bugs/server6192_server6193.js | 4 | ||||
-rw-r--r-- | jstests/libs/override_methods/implicitly_wrap_pipelines_in_facets.js | 83 |
7 files changed, 136 insertions, 1 deletions
diff --git a/buildscripts/resmokeconfig/suites/aggregation_facet_unwind_passthrough.yml b/buildscripts/resmokeconfig/suites/aggregation_facet_unwind_passthrough.yml new file mode 100644 index 00000000000..02b5e483b17 --- /dev/null +++ b/buildscripts/resmokeconfig/suites/aggregation_facet_unwind_passthrough.yml @@ -0,0 +1,25 @@ +selector: + js_test: + roots: + - jstests/aggregation/*.js + - jstests/aggregation/bugs/*.js + - jstests/aggregation/expressions/*.js + - jstests/aggregation/sources/*/*.js + exclude_with_any_tags: + - do_not_wrap_aggregations_in_facets + +executor: + js_test: + config: + shell_options: + readMode: commands + eval: load("jstests/libs/override_methods/implicitly_wrap_pipelines_in_facets.js"); + hooks: + - class: ValidateCollections + - class: CleanEveryN + n: 20 + fixture: + class: MongoDFixture + mongod_options: + set_parameters: + enableTestCommands: 1 diff --git a/etc/evergreen.yml b/etc/evergreen.yml index 98e467000ba..4dba4e02e60 100644 --- a/etc/evergreen.yml +++ b/etc/evergreen.yml @@ -1322,6 +1322,17 @@ tasks: run_multiple_jobs: true - <<: *task_template + name: aggregation_facet_unwind_passthrough_WT + depends_on: + - name: aggregation_WT + commands: + - func: "do setup" + - func: "run tests" + vars: + resmoke_args: --suites=aggregation_facet_unwind_passthrough --storageEngine=wiredTiger + run_multiple_jobs: true + +- <<: *task_template name: aggregation_read_concern_majority_passthrough_WT depends_on: - name: aggregation_WT @@ -4508,6 +4519,7 @@ buildvariants: - windows-64-vs2015-large - name: aggregation - name: aggregation_WT + - name: aggregation_facet_unwind_passthrough_WT - name: aggregation_read_concern_majority_passthrough_WT - name: auth - name: auth_WT @@ -5192,6 +5204,7 @@ buildvariants: - name: aggregation_WT - name: aggregation_WT_ese - name: aggregation_auth + - name: aggregation_facet_unwind_passthrough_WT - name: aggregation_read_concern_majority_passthrough_WT - name: audit - name: audit_WT @@ -7755,6 +7768,7 @@ buildvariants: - name: aggregation_WT - name: aggregation_WT_ese - name: aggregation_auth + - name: aggregation_facet_unwind_passthrough_WT - name: aggregation_read_concern_majority_passthrough_WT - name: audit - name: audit_WT @@ -7922,6 +7936,7 @@ buildvariants: - name: aggregation_WT - name: aggregation_WT_ese - name: aggregation_auth + - name: aggregation_facet_unwind_passthrough_WT - name: aggregation_read_concern_majority_passthrough_WT - name: audit - name: audit_WT diff --git a/jstests/aggregation/bugs/groupMissing.js b/jstests/aggregation/bugs/groupMissing.js index a66016f86ae..c08e70185b1 100644 --- a/jstests/aggregation/bugs/groupMissing.js +++ b/jstests/aggregation/bugs/groupMissing.js @@ -1,6 +1,10 @@ // $group has inconsistent behavior when differentiating between null and missing values, provided // this test passes. Here, we check the cases where it is correct, and those where it is currently // incorrect. +// +// This test issues some pipelines where it assumes an initial $sort will be absorbed and be +// covered, which will not happen if the $sort is within a $facet stage. +// @tags: [do_not_wrap_aggregations_in_facets] load('jstests/aggregation/extras/utils.js'); // For resultsEq. (function() { diff --git a/jstests/aggregation/bugs/server11675.js b/jstests/aggregation/bugs/server11675.js index 84540d6f3b3..72ab68f05cd 100644 --- a/jstests/aggregation/bugs/server11675.js +++ b/jstests/aggregation/bugs/server11675.js @@ -48,7 +48,7 @@ var server11675 = function() { assertSameAsFind({query: {}}); // sanity check assertSameAsFind({query: {$text: {$search: "apple"}}}); - assertSameAsFind({query: {$and: [{$text: {$search: "apple"}}, {_id: 1}]}}); + assertSameAsFind({query: {_id: 1, $text: {$search: "apple"}}}); assertSameAsFind( {query: {$text: {$search: "apple"}}, project: {_id: 1, score: {$meta: "textScore"}}}); assertSameAsFind({ diff --git a/jstests/aggregation/bugs/server22093.js b/jstests/aggregation/bugs/server22093.js index 61302bbf4cd..bf91eb89e43 100644 --- a/jstests/aggregation/bugs/server22093.js +++ b/jstests/aggregation/bugs/server22093.js @@ -1,6 +1,10 @@ // From the work done for SERVER-22093, an aggregation pipeline that does not require any fields // from the input documents will tell the query planner to use a count scan, which is faster than an // index scan. In this test file, we check this behavior through explain(). +// +// This test assumes that an initial $match will be absorbed by the query system, which will not +// happen if the $match is wrapped within a $facet stage. +// @tags: [do_not_wrap_aggregations_in_facets] load('jstests/libs/analyze_plan.js'); (function() { diff --git a/jstests/aggregation/bugs/server6192_server6193.js b/jstests/aggregation/bugs/server6192_server6193.js index f453a1e7060..8a3a5eb04ee 100644 --- a/jstests/aggregation/bugs/server6192_server6193.js +++ b/jstests/aggregation/bugs/server6192_server6193.js @@ -1,5 +1,9 @@ // test short-circuiting of $and and $or in // $project stages to a $const boolean +// +// This test makes assumptions about how the explain output will be formatted, so cannot be +// transformed to be put inside a $facet stage. +// @tags: [do_not_wrap_aggregations_in_facets] var t = db.jstests_aggregation_server6192; t.drop(); diff --git a/jstests/libs/override_methods/implicitly_wrap_pipelines_in_facets.js b/jstests/libs/override_methods/implicitly_wrap_pipelines_in_facets.js new file mode 100644 index 00000000000..2c5b4f0f0c5 --- /dev/null +++ b/jstests/libs/override_methods/implicitly_wrap_pipelines_in_facets.js @@ -0,0 +1,83 @@ +/** + * Loading this file overrides Mongo.prototype.runCommand() with a function that wraps any + * aggregate command's pipeline inside a $facet stage, then appends an $unwind stage. This will + * yield the same results, but stress the logic of the $facet stage. + */ + +(function() { + 'use strict'; + + // Set the batch size of the $facet stage's buffer to be lower. This will further stress the + // batching logic, since most pipelines will fall below the default size of 100MB. + assert.commandWorked( + db.adminCommand({setParameter: 1, internalQueryFacetBufferSizeBytes: 1000})); + + // Save a reference to the original runCommand method in the IIFE's scope. + // This scoping allows the original method to be called by the override below. + var originalRunCommand = Mongo.prototype.runCommand; + + Mongo.prototype.runCommand = function(dbName, cmdObj, options) { + // Skip wrapping the pipeline in a $facet stage if it's not an aggregation, or if it's + // possibly an invalid one without a pipeline. + if (typeof cmdObj !== 'object' || cmdObj === null || !cmdObj.hasOwnProperty('aggregate') || + !cmdObj.hasOwnProperty('pipeline') || !Array.isArray(cmdObj.pipeline)) { + print('Not wrapping invalid pipeline in a $facet stage'); + return originalRunCommand.apply(this, arguments); + } + + var originalPipeline = cmdObj.pipeline; + + if (originalPipeline.length === 0) { + // Empty pipelines are disallowed within a $facet stage. + print('Not wrapping empty pipeline in a $facet stage'); + return originalRunCommand.apply(this, arguments); + } + + const stagesDisallowedInsideFacet = [ + '$collStats', + '$facet', + '$geoNear', + '$indexStats', + '$out', + ]; + for (let stageSpec of originalPipeline) { + // Skip wrapping the pipeline in a $facet stage if it has an invalid stage + // specification. + if (typeof stageSpec !== 'object' || stageSpec === null) { + print('Not wrapping invalid pipeline in a $facet stage'); + return originalRunCommand.apply(this, arguments); + } + + if (stageSpec.hasOwnProperty('$match') && typeof stageSpec.$match === 'object' && + stageSpec.$match !== null) { + if (stageSpec.$match.hasOwnProperty('$text')) { + // A $text search is disallowed within a $facet stage. + print('Not wrapping $text in a $facet stage'); + return originalRunCommand.apply(this, arguments); + } + if (Object.keys(stageSpec.$match).length === 0) { + // Skip wrapping an empty $match stage, since it can be optimized out, resulting + // in an empty pipeline which is disallowed within a $facet stage. + print('Not wrapping empty $match in a $facet stage'); + return originalRunCommand.apply(this, arguments); + } + } + + // Skip wrapping the pipeline in a $facet stage if it contains a stage disallowed inside + // a $facet. + for (let disallowedStage of stagesDisallowedInsideFacet) { + if (stageSpec.hasOwnProperty(disallowedStage)) { + print('Not wrapping ' + disallowedStage + ' in a $facet stage'); + return originalRunCommand.apply(this, arguments); + } + } + } + + cmdObj.pipeline = [ + {$facet: {originalPipeline: originalPipeline}}, + {$unwind: '$originalPipeline'}, + {$replaceRoot: {newRoot: '$originalPipeline'}}, + ]; + return originalRunCommand.apply(this, arguments); + }; +}()); |