summaryrefslogtreecommitdiff
path: root/jstests/aggregation/bugs/server11675.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/aggregation/bugs/server11675.js')
-rw-r--r--jstests/aggregation/bugs/server11675.js441
1 files changed, 219 insertions, 222 deletions
diff --git a/jstests/aggregation/bugs/server11675.js b/jstests/aggregation/bugs/server11675.js
index 759b4393b30..2d02a1ff53e 100644
--- a/jstests/aggregation/bugs/server11675.js
+++ b/jstests/aggregation/bugs/server11675.js
@@ -1,227 +1,224 @@
// SERVER-11675 Text search integration with aggregation
(function() {
- load('jstests/aggregation/extras/utils.js'); // For 'assertErrorCode'.
- load('jstests/libs/fixture_helpers.js'); // For 'FixtureHelpers'
-
- const coll = db.server11675;
- coll.drop();
-
- assert.writeOK(coll.insert({_id: 1, text: "apple", words: 1}));
- assert.writeOK(coll.insert({_id: 2, text: "banana", words: 1}));
- assert.writeOK(coll.insert({_id: 3, text: "apple banana", words: 2}));
- assert.writeOK(coll.insert({_id: 4, text: "cantaloupe", words: 1}));
-
- assert.commandWorked(coll.createIndex({text: "text"}));
-
- // query should have subfields query, project, sort, skip and limit. All but query are optional.
- const assertSameAsFind = function(query) {
- let cursor = coll.find(query.query);
- const pipeline = [{$match: query.query}];
-
- if ('project' in query) {
- cursor = coll.find(query.query, query.project); // no way to add to constructed cursor
- pipeline.push({$project: query.project});
- }
-
- if ('sort' in query) {
- cursor = cursor.sort(query.sort);
- pipeline.push({$sort: query.sort});
- }
-
- if ('skip' in query) {
- cursor = cursor.skip(query.skip);
- pipeline.push({$skip: query.skip});
- }
-
- if ('limit' in query) {
- cursor = cursor.limit(query.limit);
- pipeline.push({$limit: query.limit});
- }
-
- const findRes = cursor.toArray();
- const aggRes = coll.aggregate(pipeline).toArray();
-
- // If the query doesn't specify its own sort, there is a possibility that find() and
- // aggregate() will return the same results in different orders. We sort by _id on the
- // client side, so that the results still count as equal.
- if (!query.hasOwnProperty("sort")) {
- findRes.sort(function(a, b) {
- return a._id - b._id;
- });
- aggRes.sort(function(a, b) {
- return a._id - b._id;
- });
- }
-
- assert.docEq(aggRes, findRes);
- };
-
- assertSameAsFind({query: {}}); // sanity check
- assertSameAsFind({query: {$text: {$search: "apple"}}});
- assertSameAsFind({query: {_id: 1, $text: {$search: "apple"}}});
- assertSameAsFind(
- {query: {$text: {$search: "apple"}}, project: {_id: 1, score: {$meta: "textScore"}}});
- assertSameAsFind({
- query: {$text: {$search: "apple banana"}},
- project: {_id: 1, score: {$meta: "textScore"}}
- });
- assertSameAsFind({
- query: {$text: {$search: "apple banana"}},
- project: {_id: 1, score: {$meta: "textScore"}},
- sort: {score: {$meta: "textScore"}}
- });
- assertSameAsFind({
- query: {$text: {$search: "apple banana"}},
- project: {_id: 1, score: {$meta: "textScore"}},
- sort: {score: {$meta: "textScore"}},
- limit: 1
- });
- assertSameAsFind({
- query: {$text: {$search: "apple banana"}},
- project: {_id: 1, score: {$meta: "textScore"}},
- sort: {score: {$meta: "textScore"}},
- skip: 1
- });
- assertSameAsFind({
- query: {$text: {$search: "apple banana"}},
- project: {_id: 1, score: {$meta: "textScore"}},
- sort: {score: {$meta: "textScore"}},
- skip: 1,
- limit: 1
- });
-
- // $meta sort specification should be rejected if it has additional keys.
- assert.throws(function() {
- coll.aggregate([
- {$match: {$text: {$search: 'apple banana'}}},
- {$sort: {textScore: {$meta: 'textScore', extra: 1}}}
- ])
- .itcount();
- });
-
- // $meta sort specification should be rejected if the type of meta sort is not known.
- assert.throws(function() {
- coll.aggregate([
- {$match: {$text: {$search: 'apple banana'}}},
- {$sort: {textScore: {$meta: 'unknown'}}}
- ])
- .itcount();
- });
-
- // Sort specification should be rejected if a $-keyword other than $meta is used.
- assert.throws(function() {
- coll.aggregate([
- {$match: {$text: {$search: 'apple banana'}}},
- {$sort: {textScore: {$notMeta: 'textScore'}}}
- ])
- .itcount();
- });
-
- // Sort specification should be rejected if it is a string, not an object with $meta.
- assert.throws(function() {
- coll.aggregate(
- [{$match: {$text: {$search: 'apple banana'}}}, {$sort: {textScore: 'textScore'}}])
- .itcount();
- });
-
- // sharded find requires projecting the score to sort, but sharded agg does not.
- var findRes = coll.find({$text: {$search: "apple banana"}}, {textScore: {$meta: 'textScore'}})
- .sort({textScore: {$meta: 'textScore'}})
- .map(function(obj) {
- delete obj.textScore; // remove it to match agg output
- return obj;
- });
- let res = coll.aggregate([
- {$match: {$text: {$search: 'apple banana'}}},
- {$sort: {textScore: {$meta: 'textScore'}}}
- ])
- .toArray();
- assert.eq(res, findRes);
-
- // Make sure {$meta: 'textScore'} can be used as a sub-expression
- res = coll.aggregate([
- {$match: {_id: 1, $text: {$search: 'apple'}}},
- {
- $project: {
- words: 1,
- score: {$meta: 'textScore'},
- wordsTimesScore: {$multiply: ['$words', {$meta: 'textScore'}]}
- }
- }
- ])
- .toArray();
- assert.eq(res[0].wordsTimesScore, res[0].words * res[0].score, tojson(res));
-
- // And can be used in $group
- res = coll.aggregate([
- {$match: {_id: 1, $text: {$search: 'apple banana'}}},
- {$group: {_id: {$meta: 'textScore'}, score: {$first: {$meta: 'textScore'}}}}
- ])
- .toArray();
- assert.eq(res[0]._id, res[0].score, tojson(res));
-
- // Make sure metadata crosses shard -> merger boundary
- res = coll.aggregate([
- {$match: {_id: 1, $text: {$search: 'apple'}}},
- {$project: {scoreOnShard: {$meta: 'textScore'}}},
- {$limit: 1}, // force a split. later stages run on merger
- {$project: {scoreOnShard: 1, scoreOnMerger: {$meta: 'textScore'}}}
- ])
- .toArray();
- assert.eq(res[0].scoreOnMerger, res[0].scoreOnShard);
- let score = res[0].scoreOnMerger; // save for later tests
-
- // Make sure metadata crosses shard -> merger boundary even if not used on shard
- res = coll.aggregate([
- {$match: {_id: 1, $text: {$search: 'apple'}}},
- {$limit: 1}, // force a split. later stages run on merger
- {$project: {scoreOnShard: 1, scoreOnMerger: {$meta: 'textScore'}}}
+load('jstests/aggregation/extras/utils.js'); // For 'assertErrorCode'.
+load('jstests/libs/fixture_helpers.js'); // For 'FixtureHelpers'
+
+const coll = db.server11675;
+coll.drop();
+
+assert.writeOK(coll.insert({_id: 1, text: "apple", words: 1}));
+assert.writeOK(coll.insert({_id: 2, text: "banana", words: 1}));
+assert.writeOK(coll.insert({_id: 3, text: "apple banana", words: 2}));
+assert.writeOK(coll.insert({_id: 4, text: "cantaloupe", words: 1}));
+
+assert.commandWorked(coll.createIndex({text: "text"}));
+
+// query should have subfields query, project, sort, skip and limit. All but query are optional.
+const assertSameAsFind = function(query) {
+ let cursor = coll.find(query.query);
+ const pipeline = [{$match: query.query}];
+
+ if ('project' in query) {
+ cursor = coll.find(query.query, query.project); // no way to add to constructed cursor
+ pipeline.push({$project: query.project});
+ }
+
+ if ('sort' in query) {
+ cursor = cursor.sort(query.sort);
+ pipeline.push({$sort: query.sort});
+ }
+
+ if ('skip' in query) {
+ cursor = cursor.skip(query.skip);
+ pipeline.push({$skip: query.skip});
+ }
+
+ if ('limit' in query) {
+ cursor = cursor.limit(query.limit);
+ pipeline.push({$limit: query.limit});
+ }
+
+ const findRes = cursor.toArray();
+ const aggRes = coll.aggregate(pipeline).toArray();
+
+ // If the query doesn't specify its own sort, there is a possibility that find() and
+ // aggregate() will return the same results in different orders. We sort by _id on the
+ // client side, so that the results still count as equal.
+ if (!query.hasOwnProperty("sort")) {
+ findRes.sort(function(a, b) {
+ return a._id - b._id;
+ });
+ aggRes.sort(function(a, b) {
+ return a._id - b._id;
+ });
+ }
+
+ assert.docEq(aggRes, findRes);
+};
+
+assertSameAsFind({query: {}}); // sanity check
+assertSameAsFind({query: {$text: {$search: "apple"}}});
+assertSameAsFind({query: {_id: 1, $text: {$search: "apple"}}});
+assertSameAsFind(
+ {query: {$text: {$search: "apple"}}, project: {_id: 1, score: {$meta: "textScore"}}});
+assertSameAsFind(
+ {query: {$text: {$search: "apple banana"}}, project: {_id: 1, score: {$meta: "textScore"}}});
+assertSameAsFind({
+ query: {$text: {$search: "apple banana"}},
+ project: {_id: 1, score: {$meta: "textScore"}},
+ sort: {score: {$meta: "textScore"}}
+});
+assertSameAsFind({
+ query: {$text: {$search: "apple banana"}},
+ project: {_id: 1, score: {$meta: "textScore"}},
+ sort: {score: {$meta: "textScore"}},
+ limit: 1
+});
+assertSameAsFind({
+ query: {$text: {$search: "apple banana"}},
+ project: {_id: 1, score: {$meta: "textScore"}},
+ sort: {score: {$meta: "textScore"}},
+ skip: 1
+});
+assertSameAsFind({
+ query: {$text: {$search: "apple banana"}},
+ project: {_id: 1, score: {$meta: "textScore"}},
+ sort: {score: {$meta: "textScore"}},
+ skip: 1,
+ limit: 1
+});
+
+// $meta sort specification should be rejected if it has additional keys.
+assert.throws(function() {
+ coll.aggregate([
+ {$match: {$text: {$search: 'apple banana'}}},
+ {$sort: {textScore: {$meta: 'textScore', extra: 1}}}
+ ])
+ .itcount();
+});
+
+// $meta sort specification should be rejected if the type of meta sort is not known.
+assert.throws(function() {
+ coll.aggregate([
+ {$match: {$text: {$search: 'apple banana'}}},
+ {$sort: {textScore: {$meta: 'unknown'}}}
+ ])
+ .itcount();
+});
+
+// Sort specification should be rejected if a $-keyword other than $meta is used.
+assert.throws(function() {
+ coll.aggregate([
+ {$match: {$text: {$search: 'apple banana'}}},
+ {$sort: {textScore: {$notMeta: 'textScore'}}}
+ ])
+ .itcount();
+});
+
+// Sort specification should be rejected if it is a string, not an object with $meta.
+assert.throws(function() {
+ coll.aggregate(
+ [{$match: {$text: {$search: 'apple banana'}}}, {$sort: {textScore: 'textScore'}}])
+ .itcount();
+});
+
+// sharded find requires projecting the score to sort, but sharded agg does not.
+var findRes = coll.find({$text: {$search: "apple banana"}}, {textScore: {$meta: 'textScore'}})
+ .sort({textScore: {$meta: 'textScore'}})
+ .map(function(obj) {
+ delete obj.textScore; // remove it to match agg output
+ return obj;
+ });
+let res = coll.aggregate([
+ {$match: {$text: {$search: 'apple banana'}}},
+ {$sort: {textScore: {$meta: 'textScore'}}}
])
.toArray();
- assert.eq(res[0].scoreOnMerger, score);
-
- // Make sure metadata works if first $project doesn't use it.
- res = coll.aggregate([
- {$match: {_id: 1, $text: {$search: 'apple'}}},
- {$project: {_id: 1}},
- {$project: {_id: 1, score: {$meta: 'textScore'}}}
- ])
- .toArray();
- assert.eq(res[0].score, score);
-
- // Make sure the pipeline fails if it tries to reference the text score and it doesn't exist.
- res = coll.runCommand(
- {aggregate: coll.getName(), pipeline: [{$project: {_id: 1, score: {$meta: 'textScore'}}}]});
- assert.commandFailed(res);
-
- // Make sure the metadata is 'missing()' when it doesn't exist because the document changed
- res = coll.aggregate([
- {$match: {_id: 1, $text: {$search: 'apple banana'}}},
- {$group: {_id: 1, score: {$first: {$meta: 'textScore'}}}},
- {$project: {_id: 1, scoreAgain: {$meta: 'textScore'}}},
- ])
- .toArray();
- assert(!("scoreAgain" in res[0]));
-
- // Make sure metadata works after a $unwind
- assert.writeOK(coll.insert({_id: 5, text: 'mango', words: [1, 2, 3]}));
- res = coll.aggregate([
- {$match: {$text: {$search: 'mango'}}},
- {$project: {score: {$meta: "textScore"}, _id: 1, words: 1}},
- {$unwind: '$words'},
- {$project: {scoreAgain: {$meta: "textScore"}, score: 1}}
- ])
- .toArray();
- assert.eq(res[0].scoreAgain, res[0].score);
-
- // Error checking
- // $match, but wrong position
- assertErrorCode(
- coll, [{$sort: {text: 1}}, {$match: {$text: {$search: 'apple banana'}}}], 17313);
-
- // wrong $stage, but correct position
- assertErrorCode(coll,
- [{$project: {searchValue: {$text: {$search: 'apple banana'}}}}],
- ErrorCodes.InvalidPipelineOperator);
- assertErrorCode(coll, [{$sort: {$text: {$search: 'apple banana'}}}], 17312);
+assert.eq(res, findRes);
+
+// Make sure {$meta: 'textScore'} can be used as a sub-expression
+res = coll.aggregate([
+ {$match: {_id: 1, $text: {$search: 'apple'}}},
+ {
+ $project: {
+ words: 1,
+ score: {$meta: 'textScore'},
+ wordsTimesScore: {$multiply: ['$words', {$meta: 'textScore'}]}
+ }
+ }
+ ])
+ .toArray();
+assert.eq(res[0].wordsTimesScore, res[0].words * res[0].score, tojson(res));
+
+// And can be used in $group
+res = coll.aggregate([
+ {$match: {_id: 1, $text: {$search: 'apple banana'}}},
+ {$group: {_id: {$meta: 'textScore'}, score: {$first: {$meta: 'textScore'}}}}
+ ])
+ .toArray();
+assert.eq(res[0]._id, res[0].score, tojson(res));
+
+// Make sure metadata crosses shard -> merger boundary
+res = coll.aggregate([
+ {$match: {_id: 1, $text: {$search: 'apple'}}},
+ {$project: {scoreOnShard: {$meta: 'textScore'}}},
+ {$limit: 1}, // force a split. later stages run on merger
+ {$project: {scoreOnShard: 1, scoreOnMerger: {$meta: 'textScore'}}}
+ ])
+ .toArray();
+assert.eq(res[0].scoreOnMerger, res[0].scoreOnShard);
+let score = res[0].scoreOnMerger; // save for later tests
+
+// Make sure metadata crosses shard -> merger boundary even if not used on shard
+res = coll.aggregate([
+ {$match: {_id: 1, $text: {$search: 'apple'}}},
+ {$limit: 1}, // force a split. later stages run on merger
+ {$project: {scoreOnShard: 1, scoreOnMerger: {$meta: 'textScore'}}}
+ ])
+ .toArray();
+assert.eq(res[0].scoreOnMerger, score);
+
+// Make sure metadata works if first $project doesn't use it.
+res = coll.aggregate([
+ {$match: {_id: 1, $text: {$search: 'apple'}}},
+ {$project: {_id: 1}},
+ {$project: {_id: 1, score: {$meta: 'textScore'}}}
+ ])
+ .toArray();
+assert.eq(res[0].score, score);
+
+// Make sure the pipeline fails if it tries to reference the text score and it doesn't exist.
+res = coll.runCommand(
+ {aggregate: coll.getName(), pipeline: [{$project: {_id: 1, score: {$meta: 'textScore'}}}]});
+assert.commandFailed(res);
+
+// Make sure the metadata is 'missing()' when it doesn't exist because the document changed
+res = coll.aggregate([
+ {$match: {_id: 1, $text: {$search: 'apple banana'}}},
+ {$group: {_id: 1, score: {$first: {$meta: 'textScore'}}}},
+ {$project: {_id: 1, scoreAgain: {$meta: 'textScore'}}},
+ ])
+ .toArray();
+assert(!("scoreAgain" in res[0]));
+
+// Make sure metadata works after a $unwind
+assert.writeOK(coll.insert({_id: 5, text: 'mango', words: [1, 2, 3]}));
+res = coll.aggregate([
+ {$match: {$text: {$search: 'mango'}}},
+ {$project: {score: {$meta: "textScore"}, _id: 1, words: 1}},
+ {$unwind: '$words'},
+ {$project: {scoreAgain: {$meta: "textScore"}, score: 1}}
+ ])
+ .toArray();
+assert.eq(res[0].scoreAgain, res[0].score);
+
+// Error checking
+// $match, but wrong position
+assertErrorCode(coll, [{$sort: {text: 1}}, {$match: {$text: {$search: 'apple banana'}}}], 17313);
+
+// wrong $stage, but correct position
+assertErrorCode(coll,
+ [{$project: {searchValue: {$text: {$search: 'apple banana'}}}}],
+ ErrorCodes.InvalidPipelineOperator);
+assertErrorCode(coll, [{$sort: {$text: {$search: 'apple banana'}}}], 17312);
})();