Splits some assertions about explain out of the test to fix some build failures. Also renames the test and moves it to the sharding suite and cleans up some old TODOs and makeshift passthroughs.
228 lines
8.9 KiB
JavaScript
228 lines
8.9 KiB
JavaScript
// 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'}}}
|
|
])
|
|
.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);
|
|
})();
|