summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Swanson <cswanson310@gmail.com>2016-08-29 17:09:28 -0400
committerCharlie Swanson <cswanson310@gmail.com>2016-09-12 16:55:21 -0400
commit07e0d1ef934e54dc2d1df54b6f97a521b212b87d (patch)
treefa67d1d02e216f675afe9729e3e08d218a025816
parent8383c8731ba18b8443e424783c5aa63a8aed9202 (diff)
downloadmongo-07e0d1ef934e54dc2d1df54b6f97a521b212b87d.tar.gz
SERVER-25757 Add a $facet passthrough aggregation suite.
-rw-r--r--buildscripts/resmokeconfig/suites/aggregation_facet_unwind_passthrough.yml25
-rw-r--r--etc/evergreen.yml15
-rw-r--r--jstests/aggregation/bugs/groupMissing.js4
-rw-r--r--jstests/aggregation/bugs/server11675.js2
-rw-r--r--jstests/aggregation/bugs/server22093.js4
-rw-r--r--jstests/aggregation/bugs/server6192_server6193.js4
-rw-r--r--jstests/libs/override_methods/implicitly_wrap_pipelines_in_facets.js83
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);
+ };
+}());