247 lines
7.3 KiB
JavaScript
247 lines
7.3 KiB
JavaScript
/**
|
|
* Test that redundant sorts are removed/swapped.
|
|
*
|
|
* @tags: [
|
|
* assumes_unsharded_collection,
|
|
* do_not_wrap_aggregations_in_facets,
|
|
* requires_pipeline_optimization,
|
|
* # This feature flag adjusts the desugaring a bit - requesting 'outputSortKeyMetadata' from the
|
|
* # $sort stage.
|
|
* featureFlagRankFusionBasic,
|
|
* requires_fcv_81,
|
|
* ]
|
|
*/
|
|
import {aggPlanHasStage, getAggPlanStages} from "jstests/libs/query/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(
|
|
[{$sort: {sortKey: {a: 1, b: 1}, outputSortKeyMetadata: true}}],
|
|
getAggPlanStages(explain1, "$sort"),
|
|
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(
|
|
[{$sort: {sortKey: {a: 1, b: -1}, outputSortKeyMetadata: true}}],
|
|
getAggPlanStages(explain3, "$sort"),
|
|
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(
|
|
[{$sort: {sortKey: {a: 1, b: 1, c: 1}, outputSortKeyMetadata: true}}],
|
|
getAggPlanStages(explain10, "$sort"),
|
|
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(
|
|
[{$sort: {sortKey: {a: 1, b: 1, c: 1}, outputSortKeyMetadata: true}}],
|
|
getAggPlanStages(explain11, "$sort"),
|
|
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);
|