diff options
Diffstat (limited to 'jstests/core/sort_array.js')
-rw-r--r-- | jstests/core/sort_array.js | 398 |
1 files changed, 183 insertions, 215 deletions
diff --git a/jstests/core/sort_array.js b/jstests/core/sort_array.js index 48ccdea93c4..20ae0187693 100644 --- a/jstests/core/sort_array.js +++ b/jstests/core/sort_array.js @@ -4,225 +4,193 @@ * Tests for sorting documents by fields that contain arrays. */ (function() { - "use strict"; +"use strict"; - load("jstests/libs/analyze_plan.js"); +load("jstests/libs/analyze_plan.js"); - let coll = db.jstests_array_sort; +let coll = db.jstests_array_sort; - /** - * Runs a $match-$sort-$project query as both a find and then an aggregate. Asserts that the - * result set, after being converted to an array, is equal to 'expected'. Also asserts that the - * find plan uses the SORT stage and the agg plan uses the "$sort" agg stage. - */ - function testAggAndFindSort({filter, sort, project, hint, expected}) { - let cursor = coll.find(filter, project).sort(sort); - assert.eq(cursor.toArray(), expected); - if (hint) { - // If there was a hint specified, make sure we get the same results with the hint. - cursor = coll.find(filter, project).sort(sort).hint(hint); - assert.eq(cursor.toArray(), expected); - } - let explain = coll.find(filter, project).sort(sort).explain(); - assert(planHasStage(db, explain, "SORT")); - - let pipeline = [ - {$_internalInhibitOptimization: {}}, - {$match: filter}, - {$sort: sort}, - {$project: project}, - ]; - cursor = coll.aggregate(pipeline); +/** + * Runs a $match-$sort-$project query as both a find and then an aggregate. Asserts that the + * result set, after being converted to an array, is equal to 'expected'. Also asserts that the + * find plan uses the SORT stage and the agg plan uses the "$sort" agg stage. + */ +function testAggAndFindSort({filter, sort, project, hint, expected}) { + let cursor = coll.find(filter, project).sort(sort); + assert.eq(cursor.toArray(), expected); + if (hint) { + // If there was a hint specified, make sure we get the same results with the hint. + cursor = coll.find(filter, project).sort(sort).hint(hint); assert.eq(cursor.toArray(), expected); - explain = coll.explain().aggregate(pipeline); - assert(aggPlanHasStage(explain, "$sort")); } - - coll.drop(); - assert.writeOK(coll.insert({_id: 0, a: [3, 0, 1]})); - assert.writeOK(coll.insert({_id: 1, a: [8, 4, -1]})); - - // Sanity check that a sort on "_id" is usually pushed down into the query layer, but that - // $_internalInhibitOptimization prevents this from happening. This makes sure that this test is - // actually exercising the agg blocking sort implementation. - let explain = coll.explain().aggregate([{$sort: {_id: 1}}]); - assert(!aggPlanHasStage(explain, "$sort")); - explain = coll.explain().aggregate([{$_internalInhibitOptimization: {}}, {$sort: {_id: 1}}]); - assert(aggPlanHasStage(explain, "$sort")); - - // Ascending sort, without an index. - testAggAndFindSort({ - filter: {a: {$gte: 2}}, - sort: {a: 1}, - project: {_id: 1, a: 1}, - expected: [{_id: 1, a: [8, 4, -1]}, {_id: 0, a: [3, 0, 1]}] - }); - - assert.writeOK(coll.remove({})); - assert.writeOK(coll.insert({_id: 0, a: [3, 0, 1]})); - assert.writeOK(coll.insert({_id: 1, a: [0, 4, -1]})); - - // Descending sort, without an index. - testAggAndFindSort({ - filter: {a: {$gte: 2}}, - sort: {a: -1}, - project: {_id: 1, a: 1}, - expected: [{_id: 1, a: [0, 4, -1]}, {_id: 0, a: [3, 0, 1]}] - }); - - assert.writeOK(coll.remove({})); - assert.writeOK(coll.insert({_id: 0, a: [3, 0, 1]})); - assert.writeOK(coll.insert({_id: 1, a: [8, 4, -1]})); - assert.commandWorked(coll.createIndex({a: 1})); - - // Ascending sort, in the presence of an index. The multikey index should not be used to provide - // the sort. - testAggAndFindSort({ - filter: {a: {$gte: 2}}, - sort: {a: 1}, - project: {_id: 1, a: 1}, - expected: [{_id: 1, a: [8, 4, -1]}, {_id: 0, a: [3, 0, 1]}] - }); - - assert.writeOK(coll.remove({})); - assert.writeOK(coll.insert({_id: 0, a: [3, 0, 1]})); - assert.writeOK(coll.insert({_id: 1, a: [0, 4, -1]})); - - // Descending sort, in the presence of an index. - testAggAndFindSort({ - filter: {a: {$gte: 2}}, - sort: {a: -1}, - project: {_id: 1, a: 1}, - expected: [{_id: 1, a: [0, 4, -1]}, {_id: 0, a: [3, 0, 1]}] - }); - - assert.writeOK(coll.remove({})); - assert.writeOK(coll.insert({_id: 0, x: [{y: [4, 0, 1], z: 7}, {y: 0, z: 9}]})); - assert.writeOK(coll.insert({_id: 1, x: [{y: 1, z: 7}, {y: 0, z: [8, 6]}]})); - - // Compound mixed ascending/descending sorts, without an index. Sort key for doc with _id: 0 is - // {'': 0, '': 9}. Sort key for doc with _id: 1 is {'': 0, '': 8}. - testAggAndFindSort({ - filter: {}, - sort: {"x.y": 1, "x.z": -1}, - project: {_id: 1}, - expected: [{_id: 0}, {_id: 1}] - }); - - // Sort key for doc with _id: 0 is {'': 4, '': 7}. Sort key for doc with _id: 1 is {'': 1, '': - // 7}. - testAggAndFindSort({ - filter: {}, - sort: {"x.y": -1, "x.z": 1}, - project: {_id: 1}, - expected: [{_id: 0}, {_id: 1}] - }); - - assert.commandWorked(coll.createIndex({"x.y": 1, "x.z": -1})); - - // Compound mixed ascending/descending sorts, with an index. - testAggAndFindSort({ - filter: {}, - sort: {"x.y": 1, "x.z": -1}, - project: {_id: 1}, - expected: [{_id: 0}, {_id: 1}] - }); - testAggAndFindSort({ - filter: {}, - sort: {"x.y": -1, "x.z": 1}, - project: {_id: 1}, - expected: [{_id: 0}, {_id: 1}] - }); - - // Test that a multikey index can provide a sort over a non-multikey field. - coll.drop(); - assert.commandWorked(coll.createIndex({a: 1, "b.c": 1})); - assert.writeOK(coll.insert({a: [1, 2, 3], b: {c: 9}})); - explain = coll.find({a: 2}).sort({"b.c": -1}).explain(); - assert(planHasStage(db, explain, "IXSCAN")); - assert(!planHasStage(db, explain, "SORT")); - - const pipeline = [{$match: {a: 2}}, {$sort: {"b.c": -1}}]; + let explain = coll.find(filter, project).sort(sort).explain(); + assert(planHasStage(db, explain, "SORT")); + + let pipeline = [ + {$_internalInhibitOptimization: {}}, + {$match: filter}, + {$sort: sort}, + {$project: project}, + ]; + cursor = coll.aggregate(pipeline); + assert.eq(cursor.toArray(), expected); explain = coll.explain().aggregate(pipeline); - assert(isQueryPlan(explain)); - assert(planHasStage(db, explain, "IXSCAN")); - assert(!planHasStage(db, explain, "SORT")); - - // Test that we can correctly sort by an array field in agg when there are additional fields not - // involved in the sort pattern. - coll.drop(); - assert.writeOK(coll.insert( - {_id: 0, a: 1, b: {c: 1}, d: [{e: {f: 1, g: [6, 5, 4]}}, {e: {g: [3, 2, 1]}}]})); - assert.writeOK(coll.insert( - {_id: 1, a: 2, b: {c: 2}, d: [{e: {f: 2, g: [5, 4, 3]}}, {e: {g: [2, 1, 0]}}]})); - - testAggAndFindSort( - {filter: {}, sort: {"d.e.g": 1}, project: {_id: 1}, expected: [{_id: 1}, {_id: 0}]}); - - // Test a sort over the trailing field of a compound index, where the two fields of the index - // share a path prefix. This is designed as a regression test for SERVER-31858. - coll.drop(); - assert.writeOK(coll.insert({_id: 2, a: [{b: 1, c: 2}, {b: 2, c: 3}]})); - assert.writeOK(coll.insert({_id: 0, a: [{b: 2, c: 0}, {b: 1, c: 4}]})); - assert.writeOK(coll.insert({_id: 1, a: [{b: 1, c: 5}, {b: 2, c: 1}]})); - assert.commandWorked(coll.createIndex({"a.b": 1, "a.c": 1})); - testAggAndFindSort({ - filter: {"a.b": 1}, - project: {_id: 1}, - sort: {"a.c": 1}, - expected: [{_id: 0}, {_id: 1}, {_id: 2}] - }); - - // Test that an indexed and unindexed sort return the same thing for a path "a.x" which - // traverses through an array. - coll.drop(); - assert.commandWorked(coll.insert({_id: 0, a: [{x: 2}]})); - assert.commandWorked(coll.insert({_id: 1, a: [{x: 1}]})); - assert.commandWorked(coll.insert({_id: 2, a: [{x: 3}]})); - testAggAndFindSort({ - filter: {}, - project: {_id: 1}, - sort: {"a.x": 1}, - expected: [{_id: 1}, {_id: 0}, {_id: 2}] - }); - assert.commandWorked(coll.createIndex({"a.x": 1})); - testAggAndFindSort({ - filter: {}, - project: {_id: 1}, - sort: {"a.x": 1}, - expected: [{_id: 1}, {_id: 0}, {_id: 2}] - }); - testAggAndFindSort({ - filter: {}, - project: {_id: 1}, - sort: {"a.x": 1}, - hint: {"a.x": 1}, - expected: [{_id: 1}, {_id: 0}, {_id: 2}] - }); - - // Now repeat the test with multiple entries along the path "a.x". - coll.drop(); - assert.commandWorked(coll.insert({_id: 0, a: [{x: 2}, {x: 3}]})); - assert.commandWorked(coll.insert({_id: 1, a: [{x: 1}, {x: 4}]})); - assert.commandWorked(coll.insert({_id: 2, a: [{x: 3}, {x: 4}]})); - testAggAndFindSort({ - filter: {}, - project: {_id: 1}, - sort: {"a.x": 1}, - expected: [{_id: 1}, {_id: 0}, {_id: 2}] - }); - assert.commandWorked(coll.createIndex({"a.x": 1})); - testAggAndFindSort({ - filter: {}, - project: {_id: 1}, - sort: {"a.x": 1}, - expected: [{_id: 1}, {_id: 0}, {_id: 2}] - }); - testAggAndFindSort({ - filter: {}, - project: {_id: 1}, - sort: {"a.x": 1}, - hint: {"a.x": 1}, - expected: [{_id: 1}, {_id: 0}, {_id: 2}] - }); + assert(aggPlanHasStage(explain, "$sort")); +} + +coll.drop(); +assert.writeOK(coll.insert({_id: 0, a: [3, 0, 1]})); +assert.writeOK(coll.insert({_id: 1, a: [8, 4, -1]})); + +// Sanity check that a sort on "_id" is usually pushed down into the query layer, but that +// $_internalInhibitOptimization prevents this from happening. This makes sure that this test is +// actually exercising the agg blocking sort implementation. +let explain = coll.explain().aggregate([{$sort: {_id: 1}}]); +assert(!aggPlanHasStage(explain, "$sort")); +explain = coll.explain().aggregate([{$_internalInhibitOptimization: {}}, {$sort: {_id: 1}}]); +assert(aggPlanHasStage(explain, "$sort")); + +// Ascending sort, without an index. +testAggAndFindSort({ + filter: {a: {$gte: 2}}, + sort: {a: 1}, + project: {_id: 1, a: 1}, + expected: [{_id: 1, a: [8, 4, -1]}, {_id: 0, a: [3, 0, 1]}] +}); + +assert.writeOK(coll.remove({})); +assert.writeOK(coll.insert({_id: 0, a: [3, 0, 1]})); +assert.writeOK(coll.insert({_id: 1, a: [0, 4, -1]})); + +// Descending sort, without an index. +testAggAndFindSort({ + filter: {a: {$gte: 2}}, + sort: {a: -1}, + project: {_id: 1, a: 1}, + expected: [{_id: 1, a: [0, 4, -1]}, {_id: 0, a: [3, 0, 1]}] +}); + +assert.writeOK(coll.remove({})); +assert.writeOK(coll.insert({_id: 0, a: [3, 0, 1]})); +assert.writeOK(coll.insert({_id: 1, a: [8, 4, -1]})); +assert.commandWorked(coll.createIndex({a: 1})); + +// Ascending sort, in the presence of an index. The multikey index should not be used to provide +// the sort. +testAggAndFindSort({ + filter: {a: {$gte: 2}}, + sort: {a: 1}, + project: {_id: 1, a: 1}, + expected: [{_id: 1, a: [8, 4, -1]}, {_id: 0, a: [3, 0, 1]}] +}); + +assert.writeOK(coll.remove({})); +assert.writeOK(coll.insert({_id: 0, a: [3, 0, 1]})); +assert.writeOK(coll.insert({_id: 1, a: [0, 4, -1]})); + +// Descending sort, in the presence of an index. +testAggAndFindSort({ + filter: {a: {$gte: 2}}, + sort: {a: -1}, + project: {_id: 1, a: 1}, + expected: [{_id: 1, a: [0, 4, -1]}, {_id: 0, a: [3, 0, 1]}] +}); + +assert.writeOK(coll.remove({})); +assert.writeOK(coll.insert({_id: 0, x: [{y: [4, 0, 1], z: 7}, {y: 0, z: 9}]})); +assert.writeOK(coll.insert({_id: 1, x: [{y: 1, z: 7}, {y: 0, z: [8, 6]}]})); + +// Compound mixed ascending/descending sorts, without an index. Sort key for doc with _id: 0 is +// {'': 0, '': 9}. Sort key for doc with _id: 1 is {'': 0, '': 8}. +testAggAndFindSort( + {filter: {}, sort: {"x.y": 1, "x.z": -1}, project: {_id: 1}, expected: [{_id: 0}, {_id: 1}]}); + +// Sort key for doc with _id: 0 is {'': 4, '': 7}. Sort key for doc with _id: 1 is {'': 1, '': +// 7}. +testAggAndFindSort( + {filter: {}, sort: {"x.y": -1, "x.z": 1}, project: {_id: 1}, expected: [{_id: 0}, {_id: 1}]}); + +assert.commandWorked(coll.createIndex({"x.y": 1, "x.z": -1})); + +// Compound mixed ascending/descending sorts, with an index. +testAggAndFindSort( + {filter: {}, sort: {"x.y": 1, "x.z": -1}, project: {_id: 1}, expected: [{_id: 0}, {_id: 1}]}); +testAggAndFindSort( + {filter: {}, sort: {"x.y": -1, "x.z": 1}, project: {_id: 1}, expected: [{_id: 0}, {_id: 1}]}); + +// Test that a multikey index can provide a sort over a non-multikey field. +coll.drop(); +assert.commandWorked(coll.createIndex({a: 1, "b.c": 1})); +assert.writeOK(coll.insert({a: [1, 2, 3], b: {c: 9}})); +explain = coll.find({a: 2}).sort({"b.c": -1}).explain(); +assert(planHasStage(db, explain, "IXSCAN")); +assert(!planHasStage(db, explain, "SORT")); + +const pipeline = [{$match: {a: 2}}, {$sort: {"b.c": -1}}]; +explain = coll.explain().aggregate(pipeline); +assert(isQueryPlan(explain)); +assert(planHasStage(db, explain, "IXSCAN")); +assert(!planHasStage(db, explain, "SORT")); + +// Test that we can correctly sort by an array field in agg when there are additional fields not +// involved in the sort pattern. +coll.drop(); +assert.writeOK( + coll.insert({_id: 0, a: 1, b: {c: 1}, d: [{e: {f: 1, g: [6, 5, 4]}}, {e: {g: [3, 2, 1]}}]})); +assert.writeOK( + coll.insert({_id: 1, a: 2, b: {c: 2}, d: [{e: {f: 2, g: [5, 4, 3]}}, {e: {g: [2, 1, 0]}}]})); + +testAggAndFindSort( + {filter: {}, sort: {"d.e.g": 1}, project: {_id: 1}, expected: [{_id: 1}, {_id: 0}]}); + +// Test a sort over the trailing field of a compound index, where the two fields of the index +// share a path prefix. This is designed as a regression test for SERVER-31858. +coll.drop(); +assert.writeOK(coll.insert({_id: 2, a: [{b: 1, c: 2}, {b: 2, c: 3}]})); +assert.writeOK(coll.insert({_id: 0, a: [{b: 2, c: 0}, {b: 1, c: 4}]})); +assert.writeOK(coll.insert({_id: 1, a: [{b: 1, c: 5}, {b: 2, c: 1}]})); +assert.commandWorked(coll.createIndex({"a.b": 1, "a.c": 1})); +testAggAndFindSort({ + filter: {"a.b": 1}, + project: {_id: 1}, + sort: {"a.c": 1}, + expected: [{_id: 0}, {_id: 1}, {_id: 2}] +}); + +// Test that an indexed and unindexed sort return the same thing for a path "a.x" which +// traverses through an array. +coll.drop(); +assert.commandWorked(coll.insert({_id: 0, a: [{x: 2}]})); +assert.commandWorked(coll.insert({_id: 1, a: [{x: 1}]})); +assert.commandWorked(coll.insert({_id: 2, a: [{x: 3}]})); +testAggAndFindSort( + {filter: {}, project: {_id: 1}, sort: {"a.x": 1}, expected: [{_id: 1}, {_id: 0}, {_id: 2}]}); +assert.commandWorked(coll.createIndex({"a.x": 1})); +testAggAndFindSort( + {filter: {}, project: {_id: 1}, sort: {"a.x": 1}, expected: [{_id: 1}, {_id: 0}, {_id: 2}]}); +testAggAndFindSort({ + filter: {}, + project: {_id: 1}, + sort: {"a.x": 1}, + hint: {"a.x": 1}, + expected: [{_id: 1}, {_id: 0}, {_id: 2}] +}); + +// Now repeat the test with multiple entries along the path "a.x". +coll.drop(); +assert.commandWorked(coll.insert({_id: 0, a: [{x: 2}, {x: 3}]})); +assert.commandWorked(coll.insert({_id: 1, a: [{x: 1}, {x: 4}]})); +assert.commandWorked(coll.insert({_id: 2, a: [{x: 3}, {x: 4}]})); +testAggAndFindSort( + {filter: {}, project: {_id: 1}, sort: {"a.x": 1}, expected: [{_id: 1}, {_id: 0}, {_id: 2}]}); +assert.commandWorked(coll.createIndex({"a.x": 1})); +testAggAndFindSort( + {filter: {}, project: {_id: 1}, sort: {"a.x": 1}, expected: [{_id: 1}, {_id: 0}, {_id: 2}]}); +testAggAndFindSort({ + filter: {}, + project: {_id: 1}, + sort: {"a.x": 1}, + hint: {"a.x": 1}, + expected: [{_id: 1}, {_id: 0}, {_id: 2}] +}); }()); |