278 lines
11 KiB
JavaScript
278 lines
11 KiB
JavaScript
// Tests for the $in/sort/limit optimization combined with inequality bounds. SERVER-5777
|
|
|
|
(function() {
|
|
"use strict";
|
|
|
|
var t = db.jstests_sorth;
|
|
t.drop();
|
|
|
|
// These can be set to modify the query run by the helper find().
|
|
var _sort;
|
|
var _limit;
|
|
var _hint;
|
|
|
|
/**
|
|
* Generate a cursor using global parameters '_sort', '_hint', and '_limit'.
|
|
*/
|
|
function find(query) {
|
|
return t.find(query, {_id: 0}).sort(_sort).limit(_limit).hint(_hint);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the elements of 'expectedMatches' match element by element with
|
|
* 'actualMatches', only considering the fields 'a' and 'b'.
|
|
*
|
|
* @param {Array} expectedMatches - expected results from a query.
|
|
* @param {Array} actualMatches - the actual results from that query.
|
|
*/
|
|
function resultsMatch(expectedMatches, actualMatches) {
|
|
if (expectedMatches.length !== actualMatches.length) {
|
|
return false;
|
|
}
|
|
|
|
for (var i = 0; i < expectedMatches.length; ++i) {
|
|
if ((expectedMatches[i].a !== actualMatches[i].a) ||
|
|
(expectedMatches[i].b !== actualMatches[i].b)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Asserts that the given query returns results that are expected.
|
|
*
|
|
* @param {Object} options.query - the query to run.
|
|
* @param {Array.<Object>} options.expectedQueryResults - the expected results from the query.
|
|
* @param {Array.<Array>} [options.acceptableQueryResults=[options.expectedQueryResults]] - An
|
|
* array of acceptable outcomes of the query. This can be used if there are multiple results
|
|
* that are considered correct for the query.
|
|
*/
|
|
function assertMatches(options) {
|
|
const results = find(options.query).toArray();
|
|
const acceptableQueryResults =
|
|
options.acceptableQueryResults || [options.expectedQueryResults];
|
|
assert.gte(acceptableQueryResults.length, 1);
|
|
for (var i = 0; i < acceptableQueryResults.length; ++i) {
|
|
const validResultSet = acceptableQueryResults[i];
|
|
|
|
// All results should have the same number of results.
|
|
assert.eq(validResultSet.length,
|
|
results.length,
|
|
"Expected " + results.length + " results from query " +
|
|
tojson(options.query) + " but found " + validResultSet.length);
|
|
|
|
if (resultsMatch(validResultSet, results)) {
|
|
return;
|
|
}
|
|
}
|
|
throw new Error("Unexpected results for query " + tojson(options.query) + ": " +
|
|
tojson(results) + ", acceptable results were: " +
|
|
tojson(acceptableQueryResults));
|
|
}
|
|
|
|
/**
|
|
* Reset data, index, and _sort and _hint globals.
|
|
*/
|
|
function reset(sort, index) {
|
|
t.drop();
|
|
t.save({a: 1, b: 1});
|
|
t.save({a: 1, b: 2});
|
|
t.save({a: 1, b: 3});
|
|
t.save({a: 2, b: 0});
|
|
t.save({a: 2, b: 3});
|
|
t.save({a: 2, b: 5});
|
|
t.ensureIndex(index);
|
|
_sort = sort;
|
|
_hint = index;
|
|
}
|
|
|
|
function checkForwardDirection(options) {
|
|
// All callers specify a sort that is prefixed by b, ascending.
|
|
assert.eq(Object.keys(options.sort)[0], "b");
|
|
assert.eq(options.sort.b, 1);
|
|
|
|
// None of the callers specify a sort on "a".
|
|
assert(!options.sort.hasOwnProperty("a"));
|
|
|
|
reset(options.sort, options.index);
|
|
|
|
_limit = -1;
|
|
|
|
// Lower bound checks.
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 0}], query: {a: {$in: [1, 2]}, b: {$gte: 0}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 1, b: 1}], query: {a: {$in: [1, 2]}, b: {$gt: 0}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 1, b: 1}], query: {a: {$in: [1, 2]}, b: {$gte: 1}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 1, b: 2}], query: {a: {$in: [1, 2]}, b: {$gt: 1}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 1, b: 2}], query: {a: {$in: [1, 2]}, b: {$gte: 2}}});
|
|
|
|
// Since we are sorting on the field "b", and the sort specification doesn't include the
|
|
// field "a", any query that is expected to result in a document with a value of 3 for "b"
|
|
// has two acceptable results, since there are two documents with a value of 3 for "b". The
|
|
// same argument applies for all assertions below involving a result with a value of 3 for
|
|
// the field "b".
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 3}], [{a: 2, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$gt: 2}}
|
|
});
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 3}], [{a: 2, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$gte: 3}}
|
|
});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 5}], query: {a: {$in: [1, 2]}, b: {$gt: 3}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 5}], query: {a: {$in: [1, 2]}, b: {$gte: 4}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 5}], query: {a: {$in: [1, 2]}, b: {$gt: 4}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 5}], query: {a: {$in: [1, 2]}, b: {$gte: 5}}});
|
|
|
|
// Upper bound checks.
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 0}], query: {a: {$in: [1, 2]}, b: {$lte: 0}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 0}], query: {a: {$in: [1, 2]}, b: {$lt: 1}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 0}], query: {a: {$in: [1, 2]}, b: {$lte: 1}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 0}], query: {a: {$in: [1, 2]}, b: {$lt: 3}}});
|
|
|
|
// Lower and upper bounds checks.
|
|
assertMatches({
|
|
expectedQueryResults: [{a: 2, b: 0}],
|
|
query: {a: {$in: [1, 2]}, b: {$gte: 0, $lte: 0}}
|
|
});
|
|
assertMatches({
|
|
expectedQueryResults: [{a: 2, b: 0}],
|
|
query: {a: {$in: [1, 2]}, b: {$gte: 0, $lt: 1}}
|
|
});
|
|
assertMatches({
|
|
expectedQueryResults: [{a: 2, b: 0}],
|
|
query: {a: {$in: [1, 2]}, b: {$gte: 0, $lte: 1}}
|
|
});
|
|
assertMatches({
|
|
expectedQueryResults: [{a: 1, b: 1}],
|
|
query: {a: {$in: [1, 2]}, b: {$gt: 0, $lte: 1}}
|
|
});
|
|
assertMatches({
|
|
expectedQueryResults: [{a: 1, b: 2}],
|
|
query: {a: {$in: [1, 2]}, b: {$gte: 2, $lt: 3}}
|
|
});
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 3}], [{a: 2, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$gte: 2.5, $lte: 3}}
|
|
});
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 3}], [{a: 2, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$gt: 2.5, $lte: 3}}
|
|
});
|
|
|
|
// Limit is -2.
|
|
_limit = -2;
|
|
assertMatches({
|
|
expectedQueryResults: [{a: 2, b: 0}, {a: 1, b: 1}],
|
|
query: {a: {$in: [1, 2]}, b: {$gte: 0}}
|
|
});
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 2}, {a: 2, b: 3}], [{a: 1, b: 2}, {a: 1, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$gt: 1}}
|
|
});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 5}], query: {a: {$in: [1, 2]}, b: {$gt: 4}}});
|
|
|
|
// With an additional document between the $in values.
|
|
t.save({a: 1.5, b: 3});
|
|
assertMatches({
|
|
expectedQueryResults: [{a: 2, b: 0}, {a: 1, b: 1}],
|
|
query: {a: {$in: [1, 2]}, b: {$gte: 0}}
|
|
});
|
|
}
|
|
|
|
// Basic test with an index suffix order.
|
|
checkForwardDirection({sort: {b: 1}, index: {a: 1, b: 1}});
|
|
// With an additional index field.
|
|
checkForwardDirection({sort: {b: 1}, index: {a: 1, b: 1, c: 1}});
|
|
// With an additional reverse direction index field.
|
|
checkForwardDirection({sort: {b: 1}, index: {a: 1, b: 1, c: -1}});
|
|
// With an additional ordered index field.
|
|
checkForwardDirection({sort: {b: 1, c: 1}, index: {a: 1, b: 1, c: 1}});
|
|
// With an additional reverse direction ordered index field.
|
|
checkForwardDirection({sort: {b: 1, c: -1}, index: {a: 1, b: 1, c: -1}});
|
|
|
|
function checkReverseDirection(options) {
|
|
// All callers specify a sort that is prefixed by "b", descending.
|
|
assert.eq(Object.keys(options.sort)[0], "b");
|
|
assert.eq(options.sort.b, -1);
|
|
// None of the callers specify a sort on "a".
|
|
assert(!options.sort.hasOwnProperty("a"));
|
|
|
|
reset(options.sort, options.index);
|
|
_limit = -1;
|
|
|
|
// For matching documents, highest value of 'b' is 5.
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 5}], query: {a: {$in: [1, 2]}, b: {$gte: 0}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 5}], query: {a: {$in: [1, 2]}, b: {$gte: 5}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 2, b: 5}], query: {a: {$in: [1, 2]}, b: {$lte: 5}}});
|
|
assertMatches({
|
|
expectedQueryResults: [{a: 2, b: 5}],
|
|
query: {a: {$in: [1, 2]}, b: {$lte: 5, $gte: 5}}
|
|
});
|
|
|
|
// For matching documents, highest value of 'b' is 2.
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 1, b: 2}], query: {a: {$in: [1, 2]}, b: {$lt: 3}}});
|
|
assertMatches(
|
|
{expectedQueryResults: [{a: 1, b: 2}], query: {a: {$in: [1, 2]}, b: {$lt: 3, $gt: 1}}});
|
|
|
|
// For matching documents, highest value of 'b' is 1.
|
|
assertMatches({
|
|
expectedQueryResults: [{a: 1, b: 1}],
|
|
query: {a: {$in: [1, 2]}, b: {$lt: 2, $gte: 1}}
|
|
});
|
|
|
|
// These queries expect 3 as the highest value of 'b' among matching documents, but there
|
|
// are two documents with a value of 3 for the field 'b'. Either document is acceptable,
|
|
// since there is no sort order on any other existing fields.
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 3}], [{a: 2, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$lt: 5}}
|
|
});
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 3}], [{a: 2, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$lt: 3.1}}
|
|
});
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 3}], [{a: 2, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$lt: 3.5}}
|
|
});
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 3}], [{a: 2, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$lte: 3}}
|
|
});
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 3}], [{a: 2, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$lt: 3.5, $gte: 3}}
|
|
});
|
|
assertMatches({
|
|
acceptableQueryResults: [[{a: 1, b: 3}], [{a: 2, b: 3}]],
|
|
query: {a: {$in: [1, 2]}, b: {$lte: 3, $gt: 0}}
|
|
});
|
|
}
|
|
|
|
// With a descending order index.
|
|
checkReverseDirection({sort: {b: -1}, index: {a: 1, b: -1}});
|
|
checkReverseDirection({sort: {b: -1}, index: {a: 1, b: -1, c: 1}});
|
|
checkReverseDirection({sort: {b: -1}, index: {a: 1, b: -1, c: -1}});
|
|
checkReverseDirection({sort: {b: -1, c: 1}, index: {a: 1, b: -1, c: 1}});
|
|
checkReverseDirection({sort: {b: -1, c: -1}, index: {a: 1, b: -1, c: -1}});
|
|
}());
|