summaryrefslogtreecommitdiff
path: root/jstests/core/sorth.js
blob: 4b5aa02c281b734c604d7b561909e5089d5990a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// Tests for the $in/sort/limit optimization combined with inequality bounds.  SERVER-5777
// @tags: [
//   sbe_incompatible,
// ]

(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}});
}());