mongo/jstests/aggregation/optimization/expression_swap_limit.js

73 lines
2.6 KiB
JavaScript

/**
* Pushing down computed projection before sort+limit can cause the projection to be evaluated on
* invalid documents and throw exceptions. This test checks that we do not push computed projection
* before sort+limit, preventing valid queries from failing.
*
* This test was intended to reproduce a bug, SERVER-54128.
*/
const coll = db.expression_swap_limit;
coll.drop();
const NUM_INVALID_DOCS = 10;
const docs = Array.from({length: NUM_INVALID_DOCS}, (_, i) => ({_id: i, a: ""}));
docs.push({_id: 99, a: 123});
coll.insert(docs);
coll.createIndex({some_other_field: 1});
const predicate = {
$or: [
// One branch of the $or needs to have more than one relevant index.
{_id: {$lt: 9999}, some_other_field: {$ne: 3}},
// The other branch doesn't matter: it's only here to prevent the $or being
// optimized out.
{this_predicate_matches_nothing: true},
],
};
const sortSpec = {
_id: -1,
};
const oppositeSortSpec = {
_id: +1,
};
const projection = {
_id: 1,
b: {$round: "$a"},
};
const pipeline1 = [
// We need a rooted $or to trigger the SubplanStage.
// Instead of one MultiPlanStage for the whole query, we get one
// per branch of the $or.
{$match: predicate},
// Next we need a $sort+$limit. These two stages unambiguously select _id: 99.
// From the user's point of view, the $limit returns a bag of 1 document.
{$sort: sortSpec},
{$limit: 1},
// This projection raises an error if we evaluate it on any document other than
// _id: 99, because that is the only document where 'a' is numeric.
{$project: projection},
];
{
// The pipeline should succeed.
const aggResult = coll.aggregate(pipeline1).toArray();
assert.docEq([{_id: 99, b: 123}], aggResult);
// The pipeline should succeed without pushing down to find.
const noOptResult = coll.aggregate([{$_internalInhibitOptimization: {}}].concat(pipeline1)).toArray();
assert.docEq([{_id: 99, b: 123}], noOptResult);
}
// Similarly, we can select the 1 valid document by flipping the sort and skipping
// all but one document.
const pipeline2 = [{$match: predicate}, {$sort: oppositeSortSpec}, {$skip: NUM_INVALID_DOCS}, {$project: projection}];
{
// The pipeline should succeed.
const aggResult = coll.aggregate(pipeline2).toArray();
assert.docEq([{_id: 99, b: 123}], aggResult);
// The pipeline should succeed without pushing down to find.
const noOptResult = coll.aggregate([{$_internalInhibitOptimization: {}}].concat(pipeline2)).toArray();
assert.docEq([{_id: 99, b: 123}], noOptResult);
}