229 lines
6.8 KiB
JavaScript
229 lines
6.8 KiB
JavaScript
/**
|
|
* Test that redundant sorts are removed/swapped.
|
|
*
|
|
* @tags: [
|
|
* assumes_unsharded_collection,
|
|
* do_not_wrap_aggregations_in_facets,
|
|
* requires_pipeline_optimization,
|
|
* ]
|
|
*/
|
|
(function() {
|
|
"use strict";
|
|
|
|
load('jstests/libs/analyze_plan.js');
|
|
|
|
// Find how many stages of the plan are 'stageName'.
|
|
function numberOfStages(explain, stageName) {
|
|
return getAggPlanStages(explain, stageName).length;
|
|
}
|
|
|
|
const coll = db[jsTestName()];
|
|
coll.drop();
|
|
|
|
assert.commandWorked(coll.insert([
|
|
{a: {b: 3, c: 3}, b: 2, c: 1},
|
|
{a: {b: 3, c: 3}, b: 4, c: 1},
|
|
{a: {b: 1, c: 1}, b: 4, c: 1},
|
|
{a: {b: 2, c: 2}, b: 1, c: 1},
|
|
{a: {b: 1, c: 1}, b: 4, c: 1},
|
|
{a: {b: 3, c: 3}, b: 2, c: 1},
|
|
{a: {b: 2, c: 2}, b: 1, c: 1},
|
|
{a: {b: 2, c: 2}, b: 1, c: 1},
|
|
{a: {b: 2, c: 2}, b: 3, c: 1},
|
|
{a: {b: 1, c: 1}, b: 4, c: 1},
|
|
]));
|
|
|
|
const explain1 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {a: 1, b: 1},
|
|
output: {sum: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {a: 1}}
|
|
]);
|
|
|
|
// Redundant $sort should be removed.
|
|
assert.eq(1, numberOfStages(explain1, '$sort'), explain1);
|
|
// We keep the more specific sort.
|
|
assert.docEq(getAggPlanStages(explain1, '$sort'), [{$sort: {sortKey: {a: 1, b: 1}}}], explain1);
|
|
|
|
const explain2 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {a: 1, b: 1},
|
|
output: {sum: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {a: -1}}
|
|
]);
|
|
|
|
// $sort is not redundant, should not be removed.
|
|
assert.eq(2, numberOfStages(explain2, '$sort'), explain2);
|
|
|
|
const explain3 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {a: 1},
|
|
output: {sum: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {a: 1, b: -1}}
|
|
]);
|
|
|
|
// $sort should be swapped with $_internalSetWindowFields, and the extra one removed.
|
|
assert.eq(1, numberOfStages(explain3, '$sort'), explain3);
|
|
// The sort we keep should be the more specific one.
|
|
assert.docEq(getAggPlanStages(explain3, '$sort'), [{$sort: {sortKey: {a: 1, b: -1}}}], explain3);
|
|
|
|
const explain4 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {a: 1},
|
|
output: {a: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {a: 1}}
|
|
]);
|
|
|
|
// Sort field is modified, can't be swapped or removed.
|
|
assert.eq(2, numberOfStages(explain4, '$sort'), explain4);
|
|
|
|
const explain5 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {a: 1},
|
|
output: {'a.b': {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {a: 1}}
|
|
]);
|
|
|
|
// Sort field is modified, can't be swapped or removed.
|
|
assert.eq(2, numberOfStages(explain5, '$sort'), explain5);
|
|
|
|
const explain6 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {a: 1},
|
|
output: {sum: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {a: 1}},
|
|
{$limit: 5}
|
|
]);
|
|
|
|
// $sort + $limit should not be merged.
|
|
assert(aggPlanHasStage(explain6, "$limit"), explain6);
|
|
// The $limit should not prevent us from removing the redundant $sort.
|
|
assert.eq(1, numberOfStages(explain6, '$sort'), explain6);
|
|
|
|
const explain7 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{$setWindowFields: {partitionBy: "$a.b", output: {sum: {$sum: "$c"}}}},
|
|
{$sort: {'a.b': 1}}
|
|
]);
|
|
|
|
// Sort should be removed if sorting and partitioning on same field.
|
|
assert.eq(1, numberOfStages(explain7, '$sort'), explain7);
|
|
|
|
const explain8 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
partitionBy: "$a.c",
|
|
sortBy: {'a.b': 1},
|
|
output: {sum: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {'a.b': 1}}
|
|
]);
|
|
|
|
// Sort should not be removed since primary sort field is "a.c".
|
|
assert.eq(2, numberOfStages(explain8, '$sort'), explain8);
|
|
|
|
const explain9 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
partitionBy: "$a.c",
|
|
sortBy: {'a.b': 1},
|
|
output: {sum: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {'a.c': 1}}
|
|
]);
|
|
|
|
// Sort should be removed since primary sort field is "a.c".
|
|
assert.eq(1, numberOfStages(explain9, '$sort'), explain9);
|
|
|
|
// Multiple redundant sorts are dropped.
|
|
const explain10 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {a: 1, b: 1, c: 1},
|
|
output: {sum: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {a: 1, b: 1}},
|
|
{$sort: {a: 1}},
|
|
{$sort: {a: 1, b: 1}},
|
|
]);
|
|
assert.eq(1, numberOfStages(explain10, '$sort'), explain10);
|
|
assert.docEq(
|
|
getAggPlanStages(explain10, '$sort'), [{$sort: {sortKey: {a: 1, b: 1, c: 1}}}], explain10);
|
|
|
|
// Multiple compatible sorts are pushed down.
|
|
const explain11 = coll.explain().aggregate([
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {a: 1},
|
|
output: {sum: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {a: 1, b: 1, c: 1}},
|
|
{$sort: {a: 1, b: 1}},
|
|
{$sort: {a: 1, b: 1, c: 1}},
|
|
]);
|
|
assert.eq(1, numberOfStages(explain11, '$sort'), explain11);
|
|
assert.docEq(
|
|
getAggPlanStages(explain11, '$sort'), [{$sort: {sortKey: {a: 1, b: 1, c: 1}}}], explain11);
|
|
|
|
// An incompatible $meta sort should not be dropped or pushed down.
|
|
coll.createIndex({'$**': 'text'});
|
|
const explain12 = coll.explain().aggregate([
|
|
{$match: {$text: {$search: 'hi'}}},
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {a: 1},
|
|
output: {sum: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {a: {$meta: "textScore"}}},
|
|
]);
|
|
assert.eq(2, numberOfStages(explain12, '$sort'), explain12);
|
|
|
|
// For now, we don't handle $meta at all: it won't be optimized even if it's compatible.
|
|
const explain13 = coll.explain().aggregate([
|
|
{$match: {$text: {$search: 'hi'}}},
|
|
{$_internalInhibitOptimization: {}},
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {a: {$meta: "textScore"}},
|
|
output: {sum: {$sum: "$c", window: {documents: ["unbounded", "current"]}}}
|
|
}
|
|
},
|
|
{$sort: {a: {$meta: "textScore"}}},
|
|
]);
|
|
assert.eq(2, numberOfStages(explain13, '$sort'), explain13);
|
|
})();
|