summaryrefslogtreecommitdiff
path: root/jstests/core/index/hashed/hashed_index_covered_queries.js
blob: fa7753a89a56e6801e7bc58c8eaa2e803bae40e7 (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
/**
 * Test to verify that hashed indexes can cover projections when appropriate. The queries can be
 * covered when neither the query predicate nor projection uses a hashed field.
 *
 * @tags: [
 *   assumes_unsharded_collection,
 *   # Assumes some queries will use a collection scan.
 *   assumes_no_implicit_index_creation,
 * ]
 */
(function() {
"use strict";

load("jstests/aggregation/extras/utils.js");  // For arrayEq().
load("jstests/libs/analyze_plan.js");         // For assertStagesForExplainOfCommand().

const coll = db.compound_hashed_index;
coll.drop();

for (let i = 0; i < 100; i++) {
    assert.commandWorked(coll.insert({a: i, b: (i % 13), c: NumberInt(i % 10)}));
}

/**
 * Runs find command with the 'filter' and 'projection' provided in the input, then validates
 * that the output returned matches 'expectedOutput'. Also runs explain() command on the same find
 * command and validates that all the 'expectedStages' are present in the plan returned.
 */
function validateFindCmdOutputAndPlan({filter, projection, expectedOutput, expectedStages}) {
    const cmdObj = {find: coll.getName(), filter: filter, projection: projection};
    if (expectedOutput) {
        const res = assert.commandWorked(coll.runCommand(cmdObj));
        const ouputArray = new DBCommandCursor(coll.getDB(), res).toArray();

        // We ignore the order since hashed index order is not predictable.
        assert(arrayEq(expectedOutput, ouputArray), ouputArray);
    }

    return assertStagesForExplainOfCommand(
        {coll: coll, cmdObj: cmdObj, expectedStages: expectedStages});
}

/**
 * Runs count command with the 'filter' and 'projection' provided in the input, then validates
 * that the output returned matches 'expectedOutput'. Also runs explain() command on the same count
 * command and validates that all the 'expectedStages' are present in the plan returned.
 */
function validateCountCmdOutputAndPlan({filter, expectedOutput, expectedStages}) {
    const cmdObj = {count: coll.getName(), query: filter};
    const res = assert.commandWorked(coll.runCommand(cmdObj));
    assert.eq(res.n, expectedOutput);
    assertStagesForExplainOfCommand({coll: coll, cmdObj: cmdObj, expectedStages: expectedStages});
}

/**
 * Tests when hashed field is a prefix.
 */
assert.commandWorked(coll.createIndex({b: "hashed", c: -1, a: 1}));

// Verify that queries cannot be covered with hashed field is a prefix.
validateFindCmdOutputAndPlan(
    {filter: {c: 1}, projection: {a: 1, _id: 0}, expectedStages: ['COLLSCAN']});

/**
 * Tests when hashed field is not a prefix.
 */
assert.commandWorked(coll.createIndex({a: 1, b: "hashed", c: -1}));

// Verify that query doesn't get covered when projecting a hashed field.
validateFindCmdOutputAndPlan({
    filter: {a: 26},
    projection: {b: 1, _id: 0},
    expectedOutput: [{b: 0}],
    expectedStages: ['FETCH', 'IXSCAN']
});

// Verify that query doesn't get covered when query is on a hashed field. This is to avoid the
// possibility of hash collision. If two different fields produce the same hash value, there is no
// way to distinguish them without fetching the document.
validateFindCmdOutputAndPlan({
    filter: {a: 26, b: 0},
    projection: {c: 1, _id: 0},
    expectedOutput: [{c: 6}],
    expectedStages: ['FETCH', 'IXSCAN']
});

// Verify that query gets covered when neither query nor project use hashed field.
validateFindCmdOutputAndPlan({
    filter: {a: {$gt: 24, $lt: 27}},
    projection: {c: 1, _id: 0},
    expectedOutput: [{c: 5}, {c: 6}],
    expectedStages: ['IXSCAN', 'PROJECTION_COVERED']
});

// Verify that an empty query with a coverable projection always uses a COLLSCAN.
validateFindCmdOutputAndPlan(
    {filter: {}, projection: {a: 1, _id: 0}, expectedStages: ['COLLSCAN']});

// Verify that COUNT_SCAN cannot be used when query is on a hashed field.
validateCountCmdOutputAndPlan(
    {filter: {a: 26, b: 0}, expectedStages: ['FETCH', 'IXSCAN'], expectedOutput: 1});

// Verify that a count operation with range query on a non-hashed prefix field can use
// COUNT_SCAN.
validateCountCmdOutputAndPlan(
    {filter: {a: {$gt: 25, $lt: 29}}, expectedStages: ["COUNT_SCAN"], expectedOutput: 3});
})();